/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.capedwarf.datastore;
import java.util.Collection;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import com.google.appengine.api.datastore.BaseDatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceConfig;
import com.google.appengine.api.datastore.Entities;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Transaction;
import com.google.apphosting.api.ApiProxy;
import org.hibernate.search.query.engine.spi.TimeoutExceptionFactory;
import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.context.Flag;
import org.infinispan.metadata.Metadata;
import org.infinispan.query.CacheQuery;
import org.infinispan.query.Search;
import org.infinispan.query.SearchManager;
import org.infinispan.query.spi.SearchManagerImplementor;
import org.jboss.capedwarf.common.app.Application;
import org.jboss.capedwarf.common.infinispan.InfinispanUtils;
import org.jboss.capedwarf.datastore.query.Indexes;
import org.jboss.capedwarf.datastore.query.PreparedQueryImpl;
import org.jboss.capedwarf.datastore.query.QueryConverter;
import org.jboss.capedwarf.datastore.query.QueryHandleService;
import org.jboss.capedwarf.shared.compatibility.Compatibility;
import org.jboss.capedwarf.shared.config.CacheName;
import org.jboss.capedwarf.shared.config.IndexesXml;
import org.jboss.capedwarf.shared.reflection.FieldInvocation;
import org.jboss.capedwarf.shared.reflection.ReflectionUtils;
/**
* Base Datastore service.
*
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
* @author <a href="mailto:marko.luksa@gmail.com">Marko Luksa</a>
*/
public class BaseDatastoreServiceImpl implements BaseDatastoreService, CurrentTransactionProvider, PostLoadHandle, QueryHandleService {
private static final FieldInvocation<Long> VERSION = ReflectionUtils.cacheField("org.infinispan.container.versioning.SimpleClusteredVersion", "version");
private static final Map<ClassLoader, DatastoreServiceConfig> configs = new WeakHashMap<ClassLoader, DatastoreServiceConfig>();
protected final Logger log = Logger.getLogger(getClass().getName());
protected final String appId;
protected final AdvancedCache<Key, Entity> store;
protected final AdvancedCache<Key, Entity> ignoreReturnStore;
protected final SearchManager searchManager;
private final QueryConverter queryConverter;
private DatastoreServiceConfig config;
private volatile DatastoreCallbacks datastoreCallbacks;
private final QueryTypeFactories factories;
private final AdvancedCache<Key, EntityGroupMetadata> entityGroupMetadataStore;
/**
* Cache default config.
* No need to parse potential callbacks on every new default instance.
*
* @param cl the app classloader
* @return config
*/
private static synchronized DatastoreServiceConfig withDefaults(ClassLoader cl) {
DatastoreServiceConfig dsc = configs.get(cl);
if (dsc == null) {
dsc = DatastoreServiceConfig.Builder.withDefaults();
configs.put(cl, dsc);
}
return dsc;
}
public BaseDatastoreServiceImpl() {
this(null);
}
public BaseDatastoreServiceImpl(DatastoreServiceConfig config) {
this.appId = Application.getAppId();
final ClassLoader classLoader = getAppClassLoader();
this.config = (config == null ? withDefaults(classLoader) : config);
store = createStore().getAdvancedCache().with(classLoader);
// we don't expect "put", "remove" to return anything
ignoreReturnStore = store.withFlags(Flag.IGNORE_RETURN_VALUES);
Compatibility c = Compatibility.getInstance();
boolean useMetadata = (c.isEnabled(Compatibility.Feature.DISABLE_METADATA) == false);
if (useMetadata) {
entityGroupMetadataStore = InfinispanUtils.<Key, EntityGroupMetadata>getCache(appId, CacheName.DATASTORE_VERSIONS)
.getAdvancedCache()
.with(classLoader)
.withFlags(Flag.IGNORE_RETURN_VALUES);
} else {
entityGroupMetadataStore = null;
}
this.searchManager = Search.getSearchManager(store);
if (searchManager instanceof SearchManagerImplementor) {
SearchManagerImplementor smi = (SearchManagerImplementor) searchManager;
smi.setTimeoutExceptionFactory(new TimeoutExceptionFactory() {
public RuntimeException createTimeoutException(String message, org.apache.lucene.search.Query query) {
return new ApiProxy.ApiDeadlineExceededException("datastore", "RunQuery");
}
});
}
this.queryConverter = new QueryConverter(searchManager);
this.factories = new QueryTypeFactories(this);
}
protected ClassLoader getAppClassLoader() {
return Application.getAppClassLoader();
}
protected Cache<Key, Entity> createStore() {
return InfinispanUtils.getCache(appId, CacheName.DEFAULT);
}
public DatastoreCallbacks getDatastoreCallbacks() {
if (datastoreCallbacks == null) {
Object callbacks = ReflectionUtils.invokeInstanceMethod(getDatastoreServiceConfig(), "getDatastoreCallbacks");
datastoreCallbacks = new DatastoreCallbacks(callbacks);
}
return datastoreCallbacks;
}
protected final void putEntityGroupKey(Key key) {
if (entityGroupMetadataStore != null) {
entityGroupMetadataStore.put(Entities.createEntityGroupKey(key), EntityGroupMetadata.SINGLETON);
}
}
protected final Entity getEntityGroupMetadataEntity(Key key) {
if (entityGroupMetadataStore != null) {
Entity entity = new Entity(key);
entity.setProperty(Entity.VERSION_RESERVED_PROPERTY, readEntityGroupVersion(key));
return entity;
} else {
throw new IllegalStateException("Metadata is disabled, enable it via compatibility property: " + Compatibility.Feature.DISABLE_METADATA);
}
}
private Long readEntityGroupVersion(Key key) {
CacheEntry cacheEntry = entityGroupMetadataStore.getCacheEntry(key);
Metadata metadata = cacheEntry.getMetadata();
return VERSION.invoke(metadata.version());
}
public void execute(Entity result) {
getDatastoreCallbacks().executePostLoadCallbacks(this, result);
}
public Cache<Key, Entity> getCache() {
return store;
}
public SearchManager getSearchManager() {
return searchManager;
}
public PreparedQuery createQuery(Transaction tx, Query query) {
if (tx != null && query.getAncestor() == null) {
throw new IllegalArgumentException("Only ancestor queries are allowed inside transactions.");
}
javax.transaction.Transaction transaction = beforeTx(tx);
try {
IndexesXml.Index index = Indexes.getIndex(query);
CacheQuery cacheQuery = queryConverter.convert(query, index);
Double deadlineSeconds = getDatastoreServiceConfig().getDeadline();
if (deadlineSeconds != null) {
long deadlineMicroseconds = (long) (deadlineSeconds * 1000000);
cacheQuery.timeout(deadlineMicroseconds, TimeUnit.MICROSECONDS);
}
return new PreparedQueryImpl(this, query, index, cacheQuery, tx != null);
} finally {
afterTx(transaction);
}
}
public PreparedQuery prepare(Query query) {
return prepare(null, query);
}
public PreparedQuery prepare(Transaction tx, Query query) {
return factories.prepare(tx, query);
}
public Transaction getCurrentTransaction() {
Transaction tx = CapedwarfTransaction.currentTransaction();
if (tx == null)
throw new NoSuchElementException("No current transaction.");
return tx;
}
public Transaction getCurrentTransaction(Transaction transaction) {
Transaction tx = CapedwarfTransaction.currentTransaction();
return (tx != null) ? tx : transaction;
}
public Collection<Transaction> getActiveTransactions() {
return CapedwarfTransaction.getTransactions();
}
static javax.transaction.Transaction beforeTx(Transaction tx) {
if (tx == null) {
// if tx is null, explicitly suspend current tx
return CapedwarfTransaction.suspendTx();
} else {
if (!tx.isActive()) {
throw new IllegalStateException("Transaction is not active: " + tx);
}
return null;
}
}
static void afterTx(javax.transaction.Transaction transaction) {
if (transaction != null) {
CapedwarfTransaction.resumeTx(transaction);
}
}
public DatastoreServiceConfig getDatastoreServiceConfig() {
return config;
}
}