Java tutorial
/* * Copyright 2011 Tsutomu YANO. * * 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 com.shelfmap.simplequery; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.simpledb.AmazonSimpleDB; import com.amazonaws.services.simpledb.AmazonSimpleDBClient; import com.amazonaws.services.simpledb.model.*; import com.shelfmap.simplequery.attribute.SelectAttribute; import com.shelfmap.simplequery.domain.*; import com.shelfmap.simplequery.domain.impl.DefaultDomainFactory; import com.shelfmap.simplequery.expression.ItemConverter; import com.shelfmap.simplequery.expression.SelectQuery; import com.shelfmap.simplequery.expression.impl.Select; import com.shelfmap.simplequery.factory.DomainAttributeFactory; import com.shelfmap.simplequery.factory.DomainDescriptorFactory; import com.shelfmap.simplequery.factory.ItemConverterFactory; import com.shelfmap.simplequery.factory.impl.DefaultDomainAttributeFactory; import com.shelfmap.simplequery.factory.impl.DefaultDomainDescriptorFactory; import com.shelfmap.simplequery.factory.impl.DefaultItemConverterFactory; import java.io.Serializable; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * * Order of lock-aquisition:<br> * all method which need have more than 1 lock must follow the following locking order. * <ol> * <li>cachedObjects' Lock * <li>putObjects' Lock * <li>deleteObjects' Lock * </ol> * * THIS CLASS IS THREAD SAFE * * @author Tsutomu YANO */ public class DefaultContext implements Context, Serializable { private static final long serialVersionUID = 1L; //TODO hey! AWSCredentials must be serializable! or must be transient! private final AWSCredentials credentials; private AmazonSimpleDB simpleDB; private final Lock simpleDBLock = new ReentrantLock(); private AmazonS3 s3; private final Lock s3Lock = new ReentrantLock(); private final Deque<CachedObject> cachedObjects = new ArrayDeque<CachedObject>(); private final ReentrantReadWriteLock cachedObjectRwl = new ReentrantReadWriteLock(); private final Lock cachedObjectReadLock = cachedObjectRwl.readLock(); private final Lock cachedObjectWriteLock = cachedObjectRwl.writeLock(); private RemoteDomainBuilder remoteDomainBuilder; private final Lock remoteDomainBuilderLock = new ReentrantLock(); AtomicBoolean autoCreateRemoteDomain = new AtomicBoolean(false); public DefaultContext(AWSCredentials credentials) { this.credentials = credentials; } @Override public ItemConverterFactory getItemConverterFactory() { return new DefaultItemConverterFactory(this); } @Override public <T> DomainInstanceFactory<T> getDomainInstanceFactory(Domain<T> domain) { return new DefaultInstanceFactory<T>(this, domain); } @Override public DomainDescriptorFactory getDomainDescriptorFactory() { return new DefaultDomainDescriptorFactory(this); } @Override public DomainAttributeFactory getDomainAttributeFactory() { return new DefaultDomainAttributeFactory(this); } @Override public DomainFactory getDomainFactory() { return new DefaultDomainFactory(); } @Override public AWSCredentials getCredentials() { return this.credentials; } @Override public AmazonSimpleDB getSimpleDB() { simpleDBLock.lock(); try { if (this.simpleDB == null) { this.simpleDB = createSimpleDb(getCredentials()); } return this.simpleDB; } finally { simpleDBLock.unlock(); } } @Override public RemoteDomainBuilder getRemoteDomainBuilder() { remoteDomainBuilderLock.lock(); try { if (remoteDomainBuilder == null) { remoteDomainBuilder = new SimpleRemoteDomainBuilder(this); } return remoteDomainBuilder; } finally { remoteDomainBuilderLock.unlock(); } } @Override public boolean isAutoCreateRemoteDomain() { return autoCreateRemoteDomain.get(); } @Override public void setAutoCreateRemoteDomain(boolean auto) { this.autoCreateRemoteDomain.set(auto); } @Override public SelectQuery select(SelectAttribute... attributes) { return newSelectQuery(attributes); } protected SelectQuery newSelectQuery(SelectAttribute... attributes) { return new Select(this, attributes); } @Override public AmazonS3 getS3() { s3Lock.lock(); try { if (this.s3 == null) { this.s3 = createS3(getCredentials()); } ; return this.s3; } finally { s3Lock.unlock(); } } protected AmazonSimpleDB createSimpleDb(AWSCredentials securityCredential) { ClientConfiguration clientConfig = configureSimpleDb(); return clientConfig == null ? new AmazonSimpleDBClient(securityCredential) : new AmazonSimpleDBClient(securityCredential, clientConfig); } protected ClientConfiguration configureSimpleDb() { return null; } protected AmazonS3 createS3(AWSCredentials securityCredential) { ClientConfiguration clientConfig = configureS3(); return clientConfig == null ? new AmazonS3Client(securityCredential) : new AmazonS3Client(securityCredential, clientConfig); } protected ClientConfiguration configureS3() { return null; } private Collection<CachedObject> asCachedUpdateObjects(Object... domainObjects) { List<CachedObject> cachedList = new ArrayList<CachedObject>(); for (Object object : domainObjects) { cachedList.add(new UpdateObject(object)); } return cachedList; } private Collection<CachedObject> asCachedDeleteObjects(Object... domainObjects) { List<CachedObject> cachedList = new ArrayList<CachedObject>(); for (Object object : domainObjects) { cachedList.add(new DeleteObject(object)); } return cachedList; } @Override public void putObjects(Object... domainObjects) { cachedObjectWriteLock.lock(); try { cachedObjects.addAll(asCachedUpdateObjects(domainObjects)); } finally { cachedObjectWriteLock.unlock(); } } @Override public LinkedHashSet<Object> getPutObjects() { cachedObjectReadLock.lock(); try { LinkedHashSet<Object> set = new LinkedHashSet<Object>(); for (CachedObject cached : cachedObjects) { if (cached.getObjectType() == ObjectType.PUT) { set.add(cached.getObject()); } } return set; } finally { cachedObjectReadLock.unlock(); } } @Override public void deleteObjects(Object... domainObjects) { cachedObjectWriteLock.lock(); try { cachedObjects.addAll(asCachedDeleteObjects(domainObjects)); } finally { cachedObjectWriteLock.unlock(); } } @Override public LinkedHashSet<Object> getDeleteObjects() { cachedObjectReadLock.lock(); try { LinkedHashSet<Object> set = new LinkedHashSet<Object>(); for (CachedObject cached : cachedObjects) { if (cached.getObjectType() == ObjectType.DELETE) { set.add(cached.getObject()); } } return set; } finally { cachedObjectReadLock.unlock(); } } @Override public void deleteItem(Domain<?> domain, String itemName) throws AmazonServiceException, AmazonClientException { DeleteAttributesRequest request = new DeleteAttributesRequest(domain.getDomainName(), itemName); getSimpleDB().deleteAttributes(request); } @Override public void save() throws AmazonServiceException, AmazonClientException { //we must reading and writing cachedObjects as atomic processing //from reading the objects until clear the objects, //because if other thread writing data into the collections and then we //got writeLock and clear the collection, the wrote data written by other thread lost. //So we must get WRITE locks at first. //TODO I believe we can make this implementation more efficient. ex) putting and deleting data wtih some threads, and wait until all threads end. cachedObjectWriteLock.lock(); try { if (cachedObjects.isEmpty()) return; if (isAutoCreateRemoteDomain()) { RemoteDomainBuilder domainBuilder = getRemoteDomainBuilder(); //create remote domains if domains is not created yet. for (CachedObject cachedObject : cachedObjects) { Object o = cachedObject.getObject(); Domain<?> domain = getDomainFactory().findDomain(o.getClass()); if (domain == null) { throw new IllegalStateException("the domain object '" + o + "' is not a domain object. Could not find @SimpleDbDOmain annotation."); } if (domainBuilder.isBuilt(domain)) { domainBuilder.add(domain); } } domainBuilder.build(); } Map<Domain<?>, List<DeletableItem>> deleteItems = new HashMap<Domain<?>, List<DeletableItem>>(); Map<Domain<?>, List<ReplaceableItem>> putItems = new HashMap<Domain<?>, List<ReplaceableItem>>(); int handlingCount = 0; ObjectType prevType = null; for (CachedObject cached : new ArrayList<CachedObject>(cachedObjects)) { ObjectType currentType = cached.getObjectType(); Object object = cached.getObject(); if (prevType != null && prevType != currentType) { switch (prevType) { case PUT: doPutObjects(putItems); break; case DELETE: doDeleteObjects(deleteItems); break; default: throw new IllegalStateException("No such objecType: " + currentType); } for (int i = 0; i < handlingCount; i++) { cachedObjects.removeFirst(); } handlingCount = 0; } switch (currentType) { case PUT: handlePutObject(object, putItems, deleteItems); break; case DELETE: handleDeleteObject(object, deleteItems); break; default: throw new IllegalStateException("No such objecType: " + currentType); } handlingCount++; prevType = currentType; } //handle all remaining objects doPutObjects(putItems); doDeleteObjects(deleteItems); //all objects are processed successfully, then clear all objects from caches. cachedObjects.clear(); } finally { cachedObjectWriteLock.unlock(); } } private void doPutObjects(Map<Domain<?>, List<ReplaceableItem>> putItems) throws AmazonClientException { AmazonSimpleDB sdb = getSimpleDB(); for (Map.Entry<Domain<?>, List<ReplaceableItem>> entry : putItems.entrySet()) { Domain<?> domain = entry.getKey(); List<ReplaceableItem> items = entry.getValue(); BatchPutAttributesRequest request = new BatchPutAttributesRequest(domain.getDomainName(), items); sdb.batchPutAttributes(request); } putItems.clear(); } private void doDeleteObjects(Map<Domain<?>, List<DeletableItem>> deleteItems) throws AmazonClientException { AmazonSimpleDB sdb = getSimpleDB(); for (Map.Entry<Domain<?>, List<DeletableItem>> entry : deleteItems.entrySet()) { Domain<?> domain = entry.getKey(); List<DeletableItem> items = entry.getValue(); BatchDeleteAttributesRequest request = new BatchDeleteAttributesRequest(domain.getDomainName(), items); sdb.batchDeleteAttributes(request); } deleteItems.clear(); } private void handleDeleteObject(Object object, Map<Domain<?>, List<DeletableItem>> deleteItems) { Domain<?> domain = getDomainFactory().findDomain(object.getClass()); DomainDescriptor descriptor = getDomainDescriptorFactory().create(domain); String itemName = descriptor.getItemNameFrom(object); DeletableItem item = new DeletableItem().withName(itemName); List<DeletableItem> list = deleteItems.get(domain); if (list == null) { list = new ArrayList<DeletableItem>(); deleteItems.put(domain, list); } list.add(item); } private void handlePutObject(Object object, Map<Domain<?>, List<ReplaceableItem>> putItems, Map<Domain<?>, List<DeletableItem>> deleteItems) { Domain<?> domain = getDomainFactory().findDomain(object.getClass()); DomainDescriptor descriptor = getDomainDescriptorFactory().create(domain); String itemName = descriptor.getItemNameFrom(object); ItemConverter<?> itemConverter = getItemConverterFactory().create(domain); ItemState itemState = itemConverter.makeCurrentStateOf(object); Collection<ReplaceableAttribute> changed = itemState.getChangedItems(); Collection<Attribute> deleted = itemState.getDeletedItems(); if (!changed.isEmpty()) { List<ReplaceableItem> list = putItems.get(domain); if (list == null) { list = new ArrayList<ReplaceableItem>(); putItems.put(domain, list); } list.add(new ReplaceableItem().withName(itemName).withAttributes(changed)); } if (!deleted.isEmpty()) { List<DeletableItem> list = deleteItems.get(domain); if (list == null) { list = new ArrayList<DeletableItem>(); deleteItems.put(domain, list); } list.add(new DeletableItem().withName(itemName).withAttributes(deleted)); } } @Override public Iterator<CachedObject> iterator() { return new ArrayList<CachedObject>(cachedObjects).iterator(); } }