/* * Copyright 2004-2010 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.slim3.datastore; import java.util.ArrayList; import java.util.List; import java.util.Map; import com.google.appengine.api.datastore.AsyncDatastoreService; import com.google.appengine.api.datastore.Blob; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.EntityTranslator; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.Transaction; import com.google.appengine.api.datastore.Query.FilterOperator; import com.google.apphosting.api.DatastorePb.PutRequest; import com.google.storage.onestore.v3.OnestoreEntity.EntityProto; /** * A class for journal. * * @author higa * @since 1.0.0 * */ public class Journal { /** * The kind of journal entity. */ public static final String KIND = "slim3.Journal"; /** * The globalTransactionKey property name. */ public static final String GLOBAL_TRANSACTION_KEY_PROPERTY = "globalTransactionKey"; /** * The content property name. */ public static final String CONTENT_PROPERTY = "content"; /** * The putList property prefix. */ public static final String PUT_LIST_PROPERTY = "putList"; /** * The deleteList property prefix. */ public static final String DELETE_LIST_PROPERTY = "deleteList"; /** * Applies the journals. * * @param ds * the asynchronous datastore service * @param globalTransactionKey * the global transaction key * @throws NullPointerException * if the globalTransactionKey parameter is null * */ public static void apply(AsyncDatastoreService ds, Key globalTransactionKey) throws NullPointerException { if (globalTransactionKey == null) { throw new NullPointerException( "The globalTransactionKey parameter must not be null."); } List<Entity> entities = new EntityQuery(ds, KIND).filter( GLOBAL_TRANSACTION_KEY_PROPERTY, FilterOperator.EQUAL, globalTransactionKey).asList(); apply(ds, entities); } /** * Applies the journals. * * @param ds * the asynchronous datastore service * @param entities * the entities * @throws NullPointerException * if the ds parameter is null or if the entities parameter is * null * */ @SuppressWarnings("unchecked") public static void apply(AsyncDatastoreService ds, List<Entity> entities) throws NullPointerException { if (ds == null) { throw new NullPointerException("The ds parameter must not be null."); } if (entities == null) { throw new NullPointerException( "The entities parameter must not be null."); } for (Entity entity : entities) { PutRequest putReq = new PutRequest(); List<Blob> putList = (List<Blob>) entity.getProperty(PUT_LIST_PROPERTY); List<Key> deleteList = (List<Key>) entity.getProperty(DELETE_LIST_PROPERTY); List<Entity> putEntities = new ArrayList<Entity>(); if (putList != null) { for (Blob blob : putList) { EntityProto proto = putReq.addEntity(); proto.mergeFrom(blob.getBytes()); putEntities.add(EntityTranslator.createFromPb(proto)); } } if (putEntities.size() > 0) { DatastoreUtil.put(ds, null, putEntities); } if (deleteList != null) { DatastoreUtil.delete(ds, null, deleteList); } DatastoreUtil.delete(ds, null, entity.getKey()); } } /** * Applies the journals. * * @param ds * the asynchronous datastore service * @param tx * the transaction * @param journals * the journals * @throws NullPointerException * if the ds parameter is null or if the tx parameter is null or * if the journals parameter is null * */ public static void apply(AsyncDatastoreService ds, Transaction tx, Map<Key, Entity> journals) throws NullPointerException { if (ds == null) { throw new NullPointerException("The ds parameter must not be null."); } if (tx == null) { throw new NullPointerException("The tx parameter must not be null."); } if (journals == null) { throw new NullPointerException( "The journals parameter must not be null."); } List<Entity> putList = new ArrayList<Entity>(); List<Key> deleteList = new ArrayList<Key>(); for (Key key : journals.keySet()) { Entity entity = journals.get(key); if (entity != null) { putList.add(entity); } else { deleteList.add(key); } } DatastoreUtil.put(ds, tx, putList); DatastoreUtil.delete(ds, tx, deleteList); } /** * Puts the journals to the datastore. * * @param ds * the asynchronous datastore service * @param globalTransactionKey * the global transaction key * @param journalMap * the map of journals * @return journal entities * @throws NullPointerException * if the ds parameter is null or if the globalTransactionKey * parameter is null or if the journalMap parameter is null * */ public static List<Entity> put(AsyncDatastoreService ds, Key globalTransactionKey, Map<Key, Entity> journalMap) throws NullPointerException { if (ds == null) { throw new NullPointerException("The ds parameter must not be null."); } if (journalMap == null) { throw new NullPointerException( "The journalMap parameter must not be null."); } List<Entity> entities = new ArrayList<Entity>(); if (journalMap.size() == 0) { return entities; } int totalSize = 0; Entity entity = createEntity(ds, globalTransactionKey); List<Blob> putList = new ArrayList<Blob>(); List<Key> deleteList = new ArrayList<Key>(); for (Key key : journalMap.keySet()) { Entity targetEntity = journalMap.get(key); boolean put = targetEntity != null; EntityProto targetProto = put ? EntityTranslator.convertToPb(targetEntity) : null; int size = put ? targetProto.encodingSize() : 0; if (totalSize != 0 && totalSize + size + DatastoreUtil.EXTRA_SIZE > DatastoreUtil.MAX_ENTITY_SIZE) { entity.setUnindexedProperty(PUT_LIST_PROPERTY, putList); entity.setUnindexedProperty(DELETE_LIST_PROPERTY, deleteList); DatastoreUtil.put(ds, null, entity); entities.add(entity); entity = createEntity(ds, globalTransactionKey); putList = new ArrayList<Blob>(); deleteList = new ArrayList<Key>(); totalSize = 0; } if (put) { byte[] content = new byte[targetProto.encodingSize()]; targetProto.outputTo(content, 0); putList.add(new Blob(content)); } else { deleteList.add(key); } totalSize += size + DatastoreUtil.EXTRA_SIZE; } entity.setUnindexedProperty(PUT_LIST_PROPERTY, putList); entity.setUnindexedProperty(DELETE_LIST_PROPERTY, deleteList); DatastoreUtil.put(ds, null, entity); entities.add(entity); return entities; } /** * Deletes entities specified by the global transaction key in transaction. * * @param ds * the asynchronous datastore service * @param globalTransactionKey * the global transaction key * @throws NullPointerException * if the ds parameter is null or if the globalTransactionKey * parameter is null */ public static void deleteInTx(AsyncDatastoreService ds, Key globalTransactionKey) throws NullPointerException { if (ds == null) { throw new NullPointerException("The ds parameter must not be null."); } if (globalTransactionKey == null) { throw new NullPointerException( "The globalTransactionKey parameter must not be null."); } List<Key> keys = getKeys(ds, globalTransactionKey); for (Key key : keys) { deleteInTx(ds, globalTransactionKey, key); } } /** * Returns the keys specified by the global transaction key. * * @param ds * the asynchronous datastore service * @param globalTransactionKey * the global transaction key * @return a list of keys * @throws NullPointerException * if the ds parameter is null or if the globalTransactionKey * parameter is null * */ protected static List<Key> getKeys(AsyncDatastoreService ds, Key globalTransactionKey) throws NullPointerException { if (ds == null) { throw new NullPointerException("The ds parameter must not be null."); } if (globalTransactionKey == null) { throw new NullPointerException( "The globalTransactionKey parameter must not be null."); } return new EntityQuery(ds, KIND).filter( GLOBAL_TRANSACTION_KEY_PROPERTY, FilterOperator.EQUAL, globalTransactionKey).asKeyList(); } /** * Creates an entity. * * @param ds * the asynchronous datastore service * @param globalTransactionKey * the global transaction key * @return an entity * @throws NullPointerException * if the ds parameter is null or if the globalTransactionKey * parameter is null */ protected static Entity createEntity(AsyncDatastoreService ds, Key globalTransactionKey) throws NullPointerException { if (ds == null) { throw new NullPointerException("The ds parameter must not be null."); } if (globalTransactionKey == null) { throw new NullPointerException( "The globalTransactionKey parameter must not be null."); } Entity entity = new Entity(DatastoreUtil.allocateId(ds, KIND)); entity.setProperty( GLOBAL_TRANSACTION_KEY_PROPERTY, globalTransactionKey); return entity; } /** * Deletes an entity specified by the key in transaction. * * @param ds * the asynchronous datastore service * @param globalTransactionKey * the global transaction key * @param key * the key * * @throws NullPointerException * if the ds parameter is null or if the globalTransactionKey * parameter is null or if the key parameter is null */ protected static void deleteInTx(AsyncDatastoreService ds, Key globalTransactionKey, Key key) throws NullPointerException { if (ds == null) { throw new NullPointerException("The ds parameter must not be null."); } if (globalTransactionKey == null) { throw new NullPointerException( "The globalTransactionKey parameter must not be null."); } if (key == null) { throw new NullPointerException( "The key parameter must not be null."); } Transaction tx = DatastoreUtil.beginTransaction(ds); try { Entity entity = DatastoreUtil.getOrNull(ds, tx, key); if (entity != null && globalTransactionKey.equals(entity .getProperty(GLOBAL_TRANSACTION_KEY_PROPERTY))) { DatastoreUtil.delete(ds, tx, key); tx.commit(); } } finally { if (tx.isActive()) { tx.rollback(); } } } /** * Constructor. * */ private Journal() { } }