Skip to content
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

Merged
merged 26 commits into from
Aug 13, 2018
Merged

Added generic Resource caching capabilities #183

merged 26 commits into from
Aug 13, 2018

Conversation

lucaspcram
Copy link
Contributor

@lucaspcram lucaspcram commented Jul 31, 2018

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:

// Let's read a resource at an arbitrary URI into a ByteArrayResource
// notice the function provided to withFetcher conforms to the ResourceFetchFunction functional interface

final URI LOCAL_TEST_FILE_URI = URI.create("file:///path/to/some/file.txt");

// cache file contents into memory (a byte array)
final ConcurrentResourceCache resourceCache = new ConcurrentResourceCache(new ByteArrayCachingStrategy(), uri -> new File(uri.getPath()));

// this will cache miss the first time and read the file from disk (using the provided fetcher)
final ByteArrayResource fileBytes = new ByteArrayResource();
fileBytes.copyFrom(resourceCache.get(LOCAL_TEST_FILE_URI).get());

// this time we hit the cache, and read the bytes from memory instead of from disk
final ByteArrayResource fileBytesAgain = new ByteArrayResource();
fileBytesAgain.copyFrom(resourceCache.get(LOCAL_TEST_FILE_URI).get());



// Manually setting fetchers and CachingStrategies can be a pain.
// You can abstract all this away by creating case-specific subclasses.
// This subclass cache uses ByteArrayCachingStrategy by default and
// abstracts the get URI parameter by just taking a path string as a parameter instead.
final LocalFileInMemoryCache fileCache = new LocalFileInMemoryCache();

final ByteArrayResource fileBytes3 = new ByteArrayResource();
fileBytes3.copyFrom(fileCache.get("/path/to/another/file.txt").get());

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 of ResourceCache with an interface designed specifically for caching files with structured URIs from remote blob stores. See LocalFileInMemoryCache for an example of how to modify the interface of ResourceCache.

Copy link
Contributor

@jklamer jklamer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't finished reading yet but

@lucaspcram
Copy link
Contributor Author

Haha @jklamer does this look like something you might find useful?

@lucaspcram lucaspcram changed the title Added generic Resource caching capabilities DNM: Added generic Resource caching capabilities Aug 1, 2018
Copy link
Collaborator

@matthieun matthieun left a 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
Copy link
Collaborator

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,
Copy link
Collaborator

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.

Copy link
Contributor

@snb3300 snb3300 left a 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
Copy link
Contributor

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>

Copy link
Contributor

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@MikeGost MikeGost left a 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
Copy link
Contributor

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;
Copy link
Contributor

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)
Copy link
Contributor

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
Copy link
Contributor

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.

@MikeGost
Copy link
Contributor

MikeGost commented Aug 1, 2018

Talking offline - Guava Cache as a backend (handles thread safety) might be a option to consider.

@lucaspcram lucaspcram changed the title DNM: Added generic Resource caching capabilities Added generic Resource caching capabilities Aug 6, 2018
@lucaspcram
Copy link
Contributor Author

This PR is ready for merge.

Copy link
Contributor

@MikeGost MikeGost left a 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 {}",
Copy link
Contributor

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 {}",
Copy link
Contributor

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
Copy link
Contributor

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,
Copy link
Contributor

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.

Copy link
Contributor Author

@lucaspcram lucaspcram Aug 10, 2018

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.

@MikeGost
Copy link
Contributor

Great work here, @lucaspcram! Merging.

@MikeGost MikeGost merged commit a2764a0 into osmlab:dev Aug 13, 2018
@lucaspcram lucaspcram deleted the atlascache branch August 14, 2018 17:38
@lucaspcram lucaspcram added this to the 5.1.8 milestone Aug 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants