-
Notifications
You must be signed in to change notification settings - Fork 3
About
Spring Social Evernote is a service provider implementation for Evernote. However, not only integrating to the "Connect Framework" from spring-social for OAuth process, Spring Social Evernote provides more java developer friendly programming model to your Evernote service application on top of the evernote-sdk-java.
In short, currently Spring Social Evernote provides:
- service provider implementation for evernote OAuth
- interface based programming for store clients
- unchecked exceptions for endpoint operations
- null safe collection for thrift based domain object
// you can use it without any of the spring-social or spring-framework classes at this level
Evernote evernote = new EvernoteTemplate(EvernoteService.SANDBOX, "access-token");
// NoteStoreOperations is an interface corresponds to NoteStoreClient
NoteStoreOperations noteStore = evernote.noteStoreOperations();
Notebook notebook = noteStore.getDefaultNotebook(); // no checked exception is thrown
for (SharedNotebook sharedNotebook : notebook.getSharedNotebooks()) { // no NPE when there is no shared notebooks
...
}
use EvernoteService enum
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
// ProviderID is "evernote"
registry.addConnectionFactory(new EvernoteConnectionFactory("consumerKey", "consumerSecret", EvernoteService.SANDBOX));
or, use environment specific ConnectionFactory
// ProviderID is "evernote-sandbox"
registry.addConnectionFactory(new EvernoteSandboxConnectionFactory("consumerKey", "consumerSecret"));
// ProviderID is "evernote-production"
registry.addConnectionFactory(new EvernoteProductionConnectionFactory("consumerKey", "consumerSecret"));
// ProviderID is "evernote-yinxiang"
registry.addConnectionFactory(new EvernoteYinXiangConnectionFactory("consumerKey", "consumerSecret"));
Please see more constructor options.
The evernote-sdk-java includes thrift generated edam classes and convenient store client classes. The store-client classes provide easy access to the evernote store endpoints by keeping authentication data passed only at initialization time rather than providing it to all the method calls. Even though store-client provides nice endpoint APIs as methods, they still have a few limitations. For example, all of store-client methods throws checked exceptions. Whenever calling store-client methods, your application needs to handle these checked exceptions. Also, store-client classes don't have interfaces correspond to themselves. It makes harder to write unit tests. Moreover, your service classes have tight coupling with store-client classes rather than thin POJO based services.
Spring Social Evernote brings spring-framework programming model to your application. The Evernote
interface and its implementation EvernoteTemplate
class are the core of the Spring Social Evernote. Evernote
interface returns ~Operations
interfaces. Each interface defineds a group of methods that maps to corresponding store-client class's methods. This allows your application to write interface based programming which provides benefit of simple POJO based services, easy unit tests, and custom logic injection via proxy mechanism.
Also, ~Operations
methods throws unchecked EvernoteException
which encapsulate checked exceptions thrown from underlying store-client methods. This way, service logic does not need to think about try-catch or rethrowing checked exceptions everywhere. Please reference detailed features in below sections.
Evernote
interface and its implementation EvernoteTemplate
can be used independently from other spring-social related classes. Even though your application doesn't use DI container feature from spring, you can still explicitly instantiate the EvernoteTemplate
and get all the benefit provided by Spring Social Evernote.
Evernote evernote = new EvernoteTemplate(EvernoteService.SANDBOX, "my-auth-token");
User user = evernote.userStoreOperations().getUser();
...
Introduced ~StoreClientOperations
interfaces that correspond to actual ~StoreClient
classes.
Since they are interfaces, it is easy to proxy and mock them.
// NoteStoreOperations is interface encapsulating NoteStoreClient
NoteStoreOperations noteStore = evernote.noteStoreClientOperations();
Notebook notebook = noteStore.getNotebook("..."); // no checked exceptions
...
// UserStoreOperations corresponds to UserStoreClient
UserStoreOperations userStore = evernote.userStoreOperations();
User user = userStore.getUser(); // no checked exceptions
...
If you need underlying store client implementation
NoteStoreClient noteStoreClient = ((StoreClientHolder) noteStore).getStoreClient();
UserStoreClient userStoreClient = evernote.userStoreClient(); // or from evernote API
try {
notebook = noteStoreClient.getNotebook("...");
user = userStoreClient.getUser();
} catch (...) { ... } // need to handle checked exceptions
...
In evernote-sdk-java, EDAM exceptions(ex:EDAMUserException
) and thrift exceptions(ex:EDAMUserException
) are all defined as checked exceptions, and ~StoreClient
class throws these checked exceptions.
Spring Social Evernote introduces a runtime exception, EvernoteException
which encapsulates EDAM and thrift exceptions with convenient methods: isEDAMUserException()
, isEDAMSystemException()
, etc.
All ~Operations
classes throw EvernoteException
. When an underlying ~Storeclient
instance throws one of the checked exception, it is converted to the EvernoteException
holding originally thrown checked exception.
// with sdk's UserStoreClient, need to handle checked exceptions
User user;
try {
user = userStoreClient.getUser(); // needs to handle EDAMUserException, EDAMSystemException, and TException
} catch(EDAMUserException e) { ...
} catch(EDAMSystemException e ) { ...
} catch(TException e) { ...
}
// with UserStoreOperations, no explicit exception handling is required
User user = evernote.userStoreOperations().getUser();
// or, explicitly
try {
User user = userStoreClient.getUser();
} catch(EvernoteException e) {
if (e.isEDAMUserException()) {
EDAMErrorCode errorCode = e.getEDAMErrorCode();
EDAMUserException originalException = e.getCause();
....
}
}
In thrift, null collection means that te field will not be transported over the wire. That is good, but this leaves a problem in java programming. When deserialized object has null in collection, it can easily lead the NullPointerException.
NullPointerException Example:
Note note = noteStoreClient.getNote(...); // when note doesn't have resources or tags,
for(Resource resource: note.getResources()) { // NullPointerException
...
}
for(String tagId: note.getTagGuids()) { // NullPointerException
...
}
One approach is to do null-check before for-loop, but is this code look clean??
if(note.isSetResources()) {
for(Resource resource: note.getResources()) {
...
}
}
if(note.getTagGuidsSize() != 0) {
for(String tagId: note.getTagGuids()) {
...
}
}
In spring-social-evernote, ~StoreClientOperations
classes provide null-safe-collection object.
In other word, domain objects returned from ~StoreClientOperations
doesn't have null in collection. It is safe to directly for-loop the collection without null check.
Note note = evernote.noteStoreOperations().getNote(...)
for(Resource resource: note.getResources()) { // it is safe even though the note doesn't have any resources
...
}
The semantics of not sending null collection field is still intact. When domain objects are about to be transported via thrift, thrift will not include the collection field if it is empty.
For newly created domain object, you can still make it null-safe by calling the static method ThriftWrapper.makeNullSafe()
Note note = new Note();
note = ThriftWrapper.makeNullSafe(note); // returns null-safe proxy object
Please see org.springframework.social.evernote.api.Impl.NullSafeThriftCollectionInterceptor
and org.springframework.social.evernote.api.Impl.ThriftWrapper
for implementation.