/*******************************************************************************
* Copyright (c) 2009, 2010 Fraunhofer IWU and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Fraunhofer IWU - initial API and implementation
*******************************************************************************/
package net.enilink.komma.em;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import net.enilink.komma.core.IEntity;
import net.enilink.komma.core.IStatement;
import net.enilink.komma.dm.change.DataChangeTracker;
import net.enilink.komma.dm.change.IDataChange;
import net.enilink.komma.dm.change.IDataChangeListener;
import net.enilink.komma.dm.change.IDataChangeTracker;
import net.enilink.komma.dm.change.IStatementChange;
import net.enilink.komma.em.internal.CachedEntity;
import net.enilink.komma.em.util.IClosable;
import net.enilink.vocab.owl.OWL;
import net.enilink.vocab.rdf.RDF;
public class CacheModule extends AbstractModule {
static class CacheClosable implements IClosable {
@Inject
Cache<Object, CachedEntity> cache;
@Override
public void close() {
if (cache != null) {
cache.invalidateAll();
cache = null;
}
}
}
private String cacheName;
public static CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder()
.expireAfterAccess(2, TimeUnit.MINUTES).maximumSize(30000);
public CacheModule(String cacheName) {
this.cacheName = cacheName;
}
@Override
protected void configure() {
Multibinder<IClosable> closableBinder = Multibinder.<IClosable> newSetBinder(binder(),
new TypeLiteral<IClosable>() {
});
closableBinder.addBinding().to(CacheClosable.class);
}
@Provides
@Singleton
Cache<Object, CachedEntity> provideCache(IDataChangeTracker changeTracker) {
final Cache<Object, CachedEntity> cache = builder.build();
IDataChangeListener refreshListener = new IDataChangeListener() {
boolean refresh(Object entity) {
CachedEntity cached = cache.getIfPresent(entity);
boolean refreshed = false;
if (cached != null) {
// iterate over all contexts and refresh each entity
for (Object contextKey : cached.contexts()) {
Object entityInCtx = cached.getSelf(contextKey);
if (entityInCtx instanceof IEntity) {
((IEntity) entityInCtx).refresh();
refreshed = true;
}
}
}
return refreshed;
}
@Override
public void dataChanged(List<IDataChange> changes) {
for (IDataChange change : changes) {
if (change instanceof IStatementChange) {
IStatement stmt = ((IStatementChange) change).getStatement();
// refresh existing subjects and objects
boolean subjectRefreshed = refresh(stmt.getSubject());
refresh(stmt.getObject());
// clear cache completely if owl:imports has
// changed
// TODO find a better approach instead of clearing the
// whole cache
if (stmt.getContext() != null && OWL.PROPERTY_IMPORTS.equals(stmt.getPredicate())) {
cache.invalidateAll();
continue;
}
// do only remove "properties" node from cache to ensure
// that the above refresh logic keeps working
CachedEntity cachedSubject = cache.getIfPresent(stmt.getSubject());
if (cachedSubject != null) {
cachedSubject.clearProperties();
}
CachedEntity cachedObject = cache.getIfPresent(stmt.getObject());
if (cachedObject != null) {
cachedObject.clearProperties();
}
// remove entity completely from cache if its type has
// been changed
if (subjectRefreshed && RDF.PROPERTY_TYPE.equals(stmt.getPredicate())) {
cache.invalidate(stmt.getSubject());
}
}
}
}
};
// ensure higher priority for this entity manager in the listener
// list
((DataChangeTracker) changeTracker).addInternalChangeListener(refreshListener);
return cache;
}
public static void stop() {
// do nothing
// This is required for more sophisticated caching engines like
// Infinispan.
}
}