-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added generic Resource caching capabilities #183
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha @jklamer does this look like something you might find useful? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code looks great, thanks @lucaspcram, and this will be very useful! Things to add to my mind:
- More tests
- Including integration tests to make sure the local file system storage is deterministic
- Support for concurrency
- A short Readme.md file that has a basic explanation and a couple code snippets.
* @author lcram | ||
*/ | ||
@FunctionalInterface | ||
public interface ResourceFetchFunction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having an interface for that, could this just be a java.util.function.Function<URI, Resource>
?
{ | ||
if (!cachedFile.exists()) | ||
{ | ||
logger.info("Attempting to cache resource {} in temporary file {}", resourceURI, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This being a fairly low level operation, I would log it as debug or even trace, and only bubble up the errors as warn or error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice @lucaspcram 👍 Few suggestion inline
* @author lcram | ||
*/ | ||
@FunctionalInterface | ||
public interface ResourceFetchFunction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of creating a separate interface why not use the default Function interface Function<URI, Resource>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I guess @matthieun suggested the same
|
||
private URI resourceURI; | ||
private CachingStrategy cachingStrategy; | ||
private ResourceFetchFunction defaultFetcher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type of defaultFetcher
can be Function<URI, Resource>
instead of defining your own type. What do you think about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. @matthieun suggested this as well.
*/ | ||
public LocalFileInMemoryCache(final URI resourceURI) | ||
{ | ||
super(resourceURI, new ByteArrayCachingStrategy(), null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason you are setting null
defaultFetcher
in the super call and then using the setter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was just an oversight on my part. Good catch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great code, thanks @lucaspcram! +1 for multi-threaded support.
|
||
/** | ||
* The basic resource cache implementation. This cache loads a resource using a given {@link URI}, | ||
* {@link CachingStrategy}, and default {@link ResourceFetchFunction}. Since using raw URIs can |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: ResourceFetchFunction no longer exists.
|
||
private URI resourceURI; | ||
private CachingStrategy cachingStrategy; | ||
private Function<URI, Resource> defaultFetcher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: why default fetcher vs. fetcher?
*/ | ||
private Resource getResourceDirectly() | ||
{ | ||
if (this.defaultFetcher == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do a couple of checks for fetcher being null. To follow the pattern you introduced for checking to see if URI was null, does it make sense to wrap this in a private method as well? Something like throwCoreExceptionIfResourceFetcherIsNull
logger.trace("Cache hit on resource {}, returning local copy", resourceURI); | ||
return Optional.of(cachedFile); | ||
} | ||
else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can get rid of else here and just return Optional.empty()
with the logging above.
Talking offline - Guava Cache as a backend (handles thread safety) might be a option to consider. |
This PR is ready for merge. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nicely done, clean and tested! Thanks @lucaspcram!
} | ||
catch (final Exception exception) | ||
{ | ||
logger.error("Something went wrong moving {} to {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add the exception as the last parameter to the logger call, so we don't lose the stack trace if this ever happens.
} | ||
catch (final Exception exception) | ||
{ | ||
logger.error("Something went wrong copying {} to temporary local file {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment here.
/** | ||
* @author lcram | ||
*/ | ||
public class CachingTests |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice tests!
throw new UnsupportedOperationException("Operation not supported at this time."); | ||
} | ||
|
||
private void attemptToCacheFileLocally(final File cachedFile, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this code was copy/pasted from existing caching file, make sure to move author tags as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's loosely based off of part my version of fetchAtlas
from AtlasGeneratorHelper
, so I will add both your and Matthieu's author tags to this one.
Great work here, @lucaspcram! Merging. |
EDIT: Ready for merge
This PR adds extensible
Resource
caching capabilities to the atlas.The basic idea is that users can create a cache with a default population mechanism, choose a caching strategy, and then fetch desired resources (URIs) using the strategy. A very simple use case might look like:
See class
CachingTests
for test cases that demonstrate in detail how the caches can be used.In an upcoming PR to
AtlasGenerator
, I will add a subclass ofResourceCache
with an interface designed specifically for caching files with structured URIs from remote blob stores. SeeLocalFileInMemoryCache
for an example of how to modify the interface ofResourceCache
.