/* * JBoss, Home of Professional Open Source. * Copyright 2012, 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.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.transaction.Status; import javax.transaction.Synchronization; import com.google.appengine.api.datastore.DatastoreAttributes; import com.google.appengine.api.datastore.DatastoreService; 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.ImplicitTransactionManagementPolicy; import com.google.appengine.api.datastore.Index; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyRange; import com.google.appengine.api.datastore.Query; import com.google.appengine.api.datastore.Transaction; import com.google.appengine.api.datastore.TransactionOptions; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import org.jboss.capedwarf.shared.compatibility.Compatibility; import org.jboss.capedwarf.shared.components.ComponentRegistry; import org.jboss.capedwarf.shared.components.MapKey; import org.jboss.capedwarf.shared.components.Slot; import org.jboss.capedwarf.shared.config.ApplicationConfiguration; import org.jboss.capedwarf.shared.config.IndexesXml; import org.jboss.capedwarf.shared.reflection.MethodInvocation; import org.jboss.capedwarf.shared.reflection.ReflectionUtils; import org.jboss.capedwarf.shared.reflection.TargetInvocation; /** * JBoss DatastoreService impl. * * @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a> * @author <a href="mailto:marko.luksa@gmail.com">Marko Luksa</a> */ class DatastoreServiceImpl extends BaseDatastoreServiceImpl implements DatastoreServiceInternal { private static final MethodInvocation<Void> setId = ReflectionUtils.cacheMethod(Key.class, "setId", Long.TYPE); private static final MethodInvocation<Void> setChecked = ReflectionUtils.cacheMethod(Key.class, "setChecked", Boolean.TYPE); private final static TargetInvocation<Boolean> isChecked = ReflectionUtils.cacheInvocation(Key.class, "isChecked"); private DatastoreAttributes datastoreAttributes; private volatile Map<String, Integer> allocationsMap; private Map<Index, Index.IndexState> indexes; private final boolean async; // do we use async frontend private final EntityModifier entityModifier; public DatastoreServiceImpl() { this(null); } public DatastoreServiceImpl(DatastoreServiceConfig config) { this(config, false); } private DatastoreServiceImpl(DatastoreServiceConfig config, boolean async) { super(config); Compatibility instance = Compatibility.getInstance(); boolean enabled = instance.isEnabled(Compatibility.Feature.IGNORE_ENTITY_PROPERTY_CONVERSION); entityModifier = enabled ? CloningEntityModifier.INSTANCE : EntityModifierImpl.INSTANCE; this.async = async; } public static DatastoreServiceInternal async(DatastoreServiceConfig config) { if (config.getImplicitTransactionManagementPolicy() == ImplicitTransactionManagementPolicy.AUTO) { throw new IllegalArgumentException("Async Datastore service does not support ImplicitTransactionManagementPolicy.AUTO!"); } return new DatastoreServiceImpl(config, true); } static void applyKeyChecked(Key original, Key clone) { setChecked.invokeWithTarget(original, isChecked.invokeUnchecked(clone)); } static void applyKeyChecked(Entity original, Entity clone) { applyKeyChecked(original.getKey(), clone.getKey()); } protected Map<String, Integer> getAllocationsMap() { if (allocationsMap == null) { synchronized (this) { if (allocationsMap == null) { MapKey<String, Integer> key = new MapKey<String, Integer>(Slot.ALLOCATIONS_MAP); allocationsMap = ComponentRegistry.getInstance().getComponent(key); } } } return allocationsMap; } protected AllocationTuple getRangeStart(Key parent, String kind, long num) { final SequenceTuple st = SequenceTuple.getSequenceTuple(getAllocationsMap(), kind); long asNum = st.getAllocationSize() * num; long start = KeyGenerator.generateRange(appId, parent, st.getSequenceName(), asNum); return new AllocationTuple(start, asNum); } protected Entity getEntity(Key key) { if (Entities.ENTITY_GROUP_METADATA_KIND.equals(key.getKind())) { return getEntityGroupMetadataEntity(key); } EntityGroupTracker.trackKey(key); Entity entity = store.get(key); return EntityUtils.cloneEntity(entity); } public Entity get(Transaction tx, Key key) { try { final javax.transaction.Transaction transaction = beforeTx(tx); try { return getEntity(key); } finally { afterTx(transaction); } } finally { if (async) { TxTasks.end(); } } } public void get(Transaction tx, List<Key> keys, Map<Key, Entity> map) { try { final javax.transaction.Transaction transaction = beforeTx(tx); try { for (Key key : keys) { Entity entity = getEntity(key); if (entity != null) { map.put(key, entity); } } } finally { afterTx(transaction); } } finally { if (async) { TxTasks.end(); } } } public Key put(Transaction tx, Entity entity, Runnable post) { return put(tx, Collections.singletonList(entity), post).get(0); } public List<Key> put(Transaction tx, Iterable<Entity> entities, Runnable post) { try { javax.transaction.Transaction transaction = beforeTx(tx); try { List<Tuple> keyToEntityMap = new ArrayList<Tuple>(); for (Entity entity : entities) { checkEntity(entity); assignIdIfNeeded(entity); Key key = entity.getKey(); EntityGroupTracker.trackKey(key); keyToEntityMap.add(new Tuple(key, entityModifier.modify(entity))); } putInTx(keyToEntityMap, post); return Lists.transform(keyToEntityMap, FN); } finally { afterTx(transaction); } } finally { if (async) { TxTasks.end(); } } } private void checkEntity(Entity entity) { final String kind = entity.getKind(); if (KindUtils.inProgress(KindUtils.Type.METADATA) == false && KindUtils.match(kind, KindUtils.Type.METADATA)) { throw new IllegalArgumentException("Cannot store metadata kind: " + kind); } } private void assignIdIfNeeded(Entity entity) { Key key = entity.getKey(); if (key.isComplete() == false) { Long id = getRangeStart(key.getParent(), key.getKind(), 1).getStart(); setId.invokeWithTarget(key, id); } else if (isChecked.invokeUnchecked(key) == false) { SequenceTuple st = SequenceTuple.getSequenceTuple(getAllocationsMap(), key.getKind()); String sequenceName = st.getSequenceName(); long allocationSize = st.getAllocationSize(); KeyGenerator.updateRange(appId, key.getId(), sequenceName, allocationSize); } setChecked.invokeWithTarget(key, true); } @Override public void delete(Transaction tx, Iterable<Key> keys, Runnable post) { try { final javax.transaction.Transaction transaction = beforeTx(tx); try { for (Key key : keys) { EntityGroupTracker.trackKey(key); } removeInTx(keys, post); } finally { afterTx(transaction); } } finally { if (async) { TxTasks.end(); } } } public Transaction beginTransaction(TransactionOptions options) { return CapedwarfTransaction.newTransaction(options); } public synchronized Map<Index, Index.IndexState> getIndexes() { if (indexes == null) { indexes = new HashMap<Index, Index.IndexState>(); long id = 0; IndexesXml indexesXml = ApplicationConfiguration.getInstance().getIndexesXml(); for (IndexesXml.Index i : indexesXml.getIndexes().values()) { List<Index.Property> properties = new ArrayList<Index.Property>(); for (IndexesXml.Property p : i.getProperties()) { Index.Property property = ReflectionUtils.newInstance( Index.Property.class, new Class[]{String.class, Query.SortDirection.class}, new Object[]{p.getName(), toDirection(p.getDirection())} ); properties.add(property); } Index index = ReflectionUtils.newInstance( Index.class, new Class[]{Long.TYPE, String.class, Boolean.TYPE, List.class}, new Object[]{++id, i.getKind(), i.isAncestor(), properties} ); indexes.put(index, Index.IndexState.SERVING); } } return indexes; } private static Query.SortDirection toDirection(String direction) { if ("ASC".equalsIgnoreCase(direction)) { return Query.SortDirection.ASCENDING; } else if ("DESC".equalsIgnoreCase(direction)) { return Query.SortDirection.DESCENDING; } else { throw new IllegalArgumentException("No such direction: " + direction); } } public KeyRange allocateIds(Key parent, String kind, long num) { final AllocationTuple at = getRangeStart(parent, kind, num); final long start = at.getStart(); return new KeyRange(parent, kind, start, start + at.getNum() - 1); } public DatastoreService.KeyRangeState allocateIdRange(KeyRange keyRange) { final String kind = keyRange.getStart().getKind(); final SequenceTuple st = SequenceTuple.getSequenceTuple(getAllocationsMap(), kind); return KeyGenerator.checkRange(appId, keyRange, st.getSequenceName()); } public synchronized DatastoreAttributes getDatastoreAttributes() { if (datastoreAttributes == null) datastoreAttributes = ReflectionUtils.newInstance(DatastoreAttributes.class); return datastoreAttributes; } /** * Delay put if the tx is active. * * @param post the post fn */ protected void putInTx(final List<Tuple> keyToEntityMap, final Runnable post) { final javax.transaction.Transaction tx = CapedwarfTransaction.getTx(); if (tx == null) { doPut(keyToEntityMap, post); } else { try { tx.registerSynchronization(new Synchronization() { public void beforeCompletion() { doPut(keyToEntityMap, null); } public void afterCompletion(int status) { if (post != null && status == Status.STATUS_COMMITTED) { post.run(); } } }); } catch (Exception e) { throw new RuntimeException(e); } } } /** * Delay remove if the tx is active. * * @param keys the keys to remove * @param post the post fn */ protected void removeInTx(final Iterable<Key> keys, final Runnable post) { final javax.transaction.Transaction tx = CapedwarfTransaction.getTx(); if (tx == null) { doRemove(keys, post); } else { try { tx.registerSynchronization(new Synchronization() { public void beforeCompletion() { doRemove(keys, null); } public void afterCompletion(int status) { if (post != null && status == Status.STATUS_COMMITTED) { post.run(); } } }); } catch (Exception e) { throw new RuntimeException(e); } } } private void doPut(List<Tuple> keyToEntityMap, Runnable post) { for (Tuple tuple : keyToEntityMap) { putEntityGroupKey(tuple.key); ignoreReturnStore.put(tuple.key, tuple.entity); } if (post != null) { post.run(); } } private void doRemove(Iterable<Key> keys, Runnable post) { for (Key key : keys) { ignoreReturnStore.remove(key); } if (post != null) { post.run(); } } public Iterator<Entity> getAllEntitiesIterator() { return Iterators.filter(store.values().iterator(), SkipMetadataAndStatsEntities.INSTANCE); } /** * Testing only! */ public void clearCache() { store.clear(); } private static class Tuple { Key key; Entity entity; private Tuple(Key key, Entity entity) { this.key = key; this.entity = entity; } } private static final Tuple2Key FN = new Tuple2Key(); private static class Tuple2Key implements Function<Tuple, Key> { public Key apply(Tuple input) { return input.key; } } private static class SkipMetadataAndStatsEntities implements Predicate<Entity> { private static final Predicate<Entity> INSTANCE = new SkipMetadataAndStatsEntities(); public boolean apply(Entity entity) { return (KindUtils.isSpecial(entity.getKind()) == false); } } }