You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@chemistry.apache.org by fm...@apache.org on 2015/05/23 22:48:21 UTC

svn commit: r1681380 [2/2] - in /chemistry/opencmis/trunk: chemistry-opencmis-client/chemistry-opencmis-client-api/src/main/java/org/apache/chemistry/opencmis/client/api/ chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache...

Added: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/AbstractExecutorServiceAsyncSession.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/AbstractExecutorServiceAsyncSession.java?rev=1681380&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/AbstractExecutorServiceAsyncSession.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/AbstractExecutorServiceAsyncSession.java Sat May 23 20:48:20 2015
@@ -0,0 +1,784 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.chemistry.opencmis.client.runtime.async;
+
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.chemistry.opencmis.client.api.AsyncSession;
+import org.apache.chemistry.opencmis.client.api.CmisObject;
+import org.apache.chemistry.opencmis.client.api.Document;
+import org.apache.chemistry.opencmis.client.api.ObjectId;
+import org.apache.chemistry.opencmis.client.api.ObjectType;
+import org.apache.chemistry.opencmis.client.api.OperationContext;
+import org.apache.chemistry.opencmis.client.api.Policy;
+import org.apache.chemistry.opencmis.client.api.Session;
+import org.apache.chemistry.opencmis.commons.data.Ace;
+import org.apache.chemistry.opencmis.commons.data.Acl;
+import org.apache.chemistry.opencmis.commons.data.ContentStream;
+import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
+import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
+import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
+import org.apache.chemistry.opencmis.commons.enums.VersioningState;
+import org.apache.chemistry.opencmis.commons.impl.IOUtils;
+
+/**
+ * An abstract implementation of the {@link AsyncSession} interface that uses an
+ * {@link ExecutorService} object for running asynchronous tasks.
+ */
+public abstract class AbstractExecutorServiceAsyncSession<E extends ExecutorService> extends AbstractAsyncSession {
+
+    public AbstractExecutorServiceAsyncSession(Session session) {
+        super(session);
+    }
+
+    /**
+     * Returns the {@link ExecutorService} object.
+     */
+    public abstract E getExecutorService();
+
+    /**
+     * A {@link Callable} that has a {@link Session} object.
+     */
+    public static abstract class SessionCallable<V> implements Callable<V> {
+
+        protected Session session;
+
+        public SessionCallable(Session session) {
+            if (session == null) {
+                throw new IllegalArgumentException("Session must be set!");
+            }
+
+            this.session = session;
+        }
+    }
+
+    /**
+     * Submits a task for execution.
+     * 
+     * @see ExecutorService#submit(Callable)
+     */
+    public <T> Future<T> submit(SessionCallable<T> task) {
+        return getExecutorService().submit(task);
+    }
+
+    // --- types ---
+
+    protected static class GetTypeDefinitonCallable extends SessionCallable<ObjectType> {
+        private String typeId;
+
+        public GetTypeDefinitonCallable(Session session, String typeId) {
+            super(session);
+            this.typeId = typeId;
+        }
+
+        @Override
+        public ObjectType call() throws Exception {
+            return session.getTypeDefinition(typeId);
+        }
+    }
+
+    @Override
+    public Future<ObjectType> getTypeDefinition(String typeId) {
+        return submit(new GetTypeDefinitonCallable(session, typeId));
+    }
+
+    protected static class CreateTypeCallable extends SessionCallable<ObjectType> {
+        private TypeDefinition type;
+
+        public CreateTypeCallable(Session session, TypeDefinition type) {
+            super(session);
+            this.type = type;
+        }
+
+        @Override
+        public ObjectType call() throws Exception {
+            return session.createType(type);
+        }
+    }
+
+    @Override
+    public Future<ObjectType> createType(TypeDefinition type) {
+        return submit(new CreateTypeCallable(session, type));
+    }
+
+    protected static class UpdateTypeCallable extends SessionCallable<ObjectType> {
+        private TypeDefinition type;
+
+        public UpdateTypeCallable(Session session, TypeDefinition type) {
+            super(session);
+            this.type = type;
+        }
+
+        @Override
+        public ObjectType call() throws Exception {
+            return session.updateType(type);
+        }
+    }
+
+    @Override
+    public Future<ObjectType> updateType(TypeDefinition type) {
+        return submit(new UpdateTypeCallable(session, type));
+    }
+
+    protected static class DeleteTypeCallable extends SessionCallable<Object> {
+        private String typeId;
+
+        public DeleteTypeCallable(Session session, String typeId) {
+            super(session);
+            this.typeId = typeId;
+        }
+
+        @Override
+        public Object call() throws Exception {
+            session.deleteType(typeId);
+            return null;
+        }
+    }
+
+    @Override
+    public Future<?> deleteType(String typeId) {
+        return submit(new DeleteTypeCallable(session, typeId));
+    }
+
+    // --- objects ---
+
+    protected static class GetObjectCallable extends SessionCallable<CmisObject> {
+        private ObjectId objectId;
+        private String objectIdStr;
+        private OperationContext context;
+
+        public GetObjectCallable(Session session, ObjectId objectId) {
+            this(session, objectId, null);
+        }
+
+        public GetObjectCallable(Session session, ObjectId objectId, OperationContext context) {
+            super(session);
+            this.objectId = objectId;
+            this.context = context;
+        }
+
+        public GetObjectCallable(Session session, String objectId) {
+            this(session, objectId, null);
+        }
+
+        public GetObjectCallable(Session session, String objectId, OperationContext context) {
+            super(session);
+            this.objectIdStr = objectId;
+            this.context = context;
+        }
+
+        @Override
+        public CmisObject call() throws Exception {
+            if (objectId != null) {
+                if (context != null) {
+                    return session.getObject(objectId, context);
+                } else {
+                    return session.getObject(objectId);
+                }
+            } else {
+                if (context != null) {
+                    return session.getObject(objectIdStr, context);
+                } else {
+                    return session.getObject(objectIdStr);
+                }
+            }
+        }
+    }
+
+    @Override
+    public Future<CmisObject> getObject(ObjectId objectId, OperationContext context) {
+        return submit(new GetObjectCallable(session, objectId, context));
+    }
+
+    @Override
+    public Future<CmisObject> getObject(String objectId, OperationContext context) {
+        return submit(new GetObjectCallable(session, objectId, context));
+    }
+
+    protected static class GetObjectByPathCallable extends SessionCallable<CmisObject> {
+        private String path;
+        private String parentPath;
+        private String name;
+        private OperationContext context;
+
+        public GetObjectByPathCallable(Session session, String path) {
+            this(session, path, (OperationContext) null);
+        }
+
+        public GetObjectByPathCallable(Session session, String path, OperationContext context) {
+            super(session);
+            this.path = path;
+            this.context = context;
+        }
+
+        public GetObjectByPathCallable(Session session, String parentPath, String name) {
+            this(session, parentPath, name, null);
+        }
+
+        public GetObjectByPathCallable(Session session, String parentPath, String name, OperationContext context) {
+            super(session);
+            this.parentPath = parentPath;
+            this.name = name;
+            this.context = context;
+        }
+
+        @Override
+        public CmisObject call() throws Exception {
+            if (parentPath != null) {
+                if (context != null) {
+                    return session.getObjectByPath(parentPath, name, context);
+                } else {
+                    return session.getObjectByPath(parentPath, name);
+                }
+            } else {
+                if (context != null) {
+                    return session.getObjectByPath(path, context);
+                } else {
+                    return session.getObjectByPath(path);
+                }
+            }
+        }
+    }
+
+    @Override
+    public Future<CmisObject> getObjectByPath(String path, OperationContext context) {
+        return submit(new GetObjectByPathCallable(session, path, context));
+    }
+
+    @Override
+    public Future<CmisObject> getObjectByPath(String parentPath, String name, OperationContext context) {
+        return submit(new GetObjectByPathCallable(session, parentPath, name, context));
+    }
+
+    protected static class GetLatestDocumentVersionCallable extends SessionCallable<Document> {
+        private ObjectId objectId;
+        private String objectIdStr;
+        private boolean major;
+        private OperationContext context;
+
+        public GetLatestDocumentVersionCallable(Session session, ObjectId objectId) {
+            this(session, objectId, false, null);
+        }
+
+        public GetLatestDocumentVersionCallable(Session session, ObjectId objectId, boolean major,
+                OperationContext context) {
+            super(session);
+            this.objectId = objectId;
+            this.major = major;
+            this.context = context;
+        }
+
+        public GetLatestDocumentVersionCallable(Session session, String objectId) {
+            this(session, objectId, false, null);
+        }
+
+        public GetLatestDocumentVersionCallable(Session session, String objectId, boolean major,
+                OperationContext context) {
+            super(session);
+            this.objectIdStr = objectId;
+            this.major = major;
+            this.context = context;
+        }
+
+        @Override
+        public Document call() throws Exception {
+            if (objectId != null) {
+                if (context != null) {
+                    return session.getLatestDocumentVersion(objectId, major, context);
+                } else {
+                    return session.getLatestDocumentVersion(objectId);
+                }
+            } else {
+                if (context != null) {
+                    return session.getLatestDocumentVersion(objectIdStr, major, context);
+                } else {
+                    return session.getLatestDocumentVersion(objectIdStr);
+                }
+            }
+        }
+    }
+
+    @Override
+    public Future<Document> getLatestDocumentVersion(ObjectId objectId, boolean major, OperationContext context) {
+        return submit(new GetLatestDocumentVersionCallable(session, objectId, major, context));
+    }
+
+    @Override
+    public Future<Document> getLatestDocumentVersion(String objectId, boolean major, OperationContext context) {
+        return submit(new GetLatestDocumentVersionCallable(session, objectId, major, context));
+    }
+
+    // --- create ---
+
+    protected static class CreateDocumentCallable extends SessionCallable<ObjectId> {
+        private Map<String, ?> properties;
+        private ObjectId folderId;
+        private ContentStream contentStream;
+        private VersioningState versioningState;
+        private List<Policy> policies;
+        private List<Ace> addAces;
+        private List<Ace> removeAces;
+
+        public CreateDocumentCallable(Session session, Map<String, ?> properties, ObjectId folderId,
+                ContentStream contentStream, VersioningState versioningState, List<Policy> policies, List<Ace> addAces,
+                List<Ace> removeAces) {
+            super(session);
+            this.properties = properties;
+            this.folderId = folderId;
+            this.contentStream = contentStream;
+            this.versioningState = versioningState;
+            this.policies = policies;
+            this.addAces = addAces;
+            this.removeAces = removeAces;
+        }
+
+        @Override
+        public ObjectId call() throws Exception {
+            return session.createDocument(properties, folderId, contentStream, versioningState, policies, addAces,
+                    removeAces);
+        }
+    }
+
+    @Override
+    public Future<ObjectId> createDocument(Map<String, ?> properties, ObjectId folderId, ContentStream contentStream,
+            VersioningState versioningState, List<Policy> policies, List<Ace> addAces, List<Ace> removeAces) {
+        return submit(new CreateDocumentCallable(session, properties, folderId, contentStream, versioningState,
+                policies, addAces, removeAces));
+    }
+
+    protected static class CreateDocumentFromSourceCallable extends SessionCallable<ObjectId> {
+        private ObjectId source;
+        private Map<String, ?> properties;
+        private ObjectId folderId;
+        private VersioningState versioningState;
+        private List<Policy> policies;
+        private List<Ace> addAces;
+        private List<Ace> removeAces;
+
+        public CreateDocumentFromSourceCallable(Session session, ObjectId source, Map<String, ?> properties,
+                ObjectId folderId, VersioningState versioningState, List<Policy> policies, List<Ace> addAces,
+                List<Ace> removeAces) {
+            super(session);
+            this.source = source;
+            this.properties = properties;
+            this.folderId = folderId;
+            this.versioningState = versioningState;
+            this.policies = policies;
+            this.addAces = addAces;
+            this.removeAces = removeAces;
+        }
+
+        @Override
+        public ObjectId call() throws Exception {
+            return session.createDocumentFromSource(source, properties, folderId, versioningState, policies, addAces,
+                    removeAces);
+        }
+    }
+
+    @Override
+    public Future<ObjectId> createDocumentFromSource(ObjectId source, Map<String, ?> properties, ObjectId folderId,
+            VersioningState versioningState, List<Policy> policies, List<Ace> addAces, List<Ace> removeAces) {
+        return submit(new CreateDocumentFromSourceCallable(session, source, properties, folderId, versioningState,
+                policies, addAces, removeAces));
+    }
+
+    protected static class CreateFolderCallable extends SessionCallable<ObjectId> {
+
+        private Map<String, ?> properties;
+        private ObjectId folderId;
+        private List<Policy> policies;
+        private List<Ace> addAces;
+        private List<Ace> removeAces;
+
+        public CreateFolderCallable(Session session, Map<String, ?> properties, ObjectId folderId,
+                List<Policy> policies, List<Ace> addAces, List<Ace> removeAces) {
+            super(session);
+            this.properties = properties;
+            this.folderId = folderId;
+            this.policies = policies;
+            this.addAces = addAces;
+            this.removeAces = removeAces;
+        }
+
+        @Override
+        public ObjectId call() throws Exception {
+            return session.createFolder(properties, folderId, policies, addAces, removeAces);
+        }
+    }
+
+    @Override
+    public Future<ObjectId> createFolder(Map<String, ?> properties, ObjectId folderId, List<Policy> policies,
+            List<Ace> addAces, List<Ace> removeAces) {
+        return submit(new CreateFolderCallable(session, properties, folderId, policies, addAces, removeAces));
+    }
+
+    protected static class CreatePolicyCallable extends SessionCallable<ObjectId> {
+        private Map<String, ?> properties;
+        private ObjectId folderId;
+        private List<Policy> policies;
+        private List<Ace> addAces;
+        private List<Ace> removeAces;
+
+        public CreatePolicyCallable(Session session, Map<String, ?> properties, ObjectId folderId,
+                List<Policy> policies, List<Ace> addAces, List<Ace> removeAces) {
+            super(session);
+            this.properties = properties;
+            this.folderId = folderId;
+            this.policies = policies;
+            this.addAces = addAces;
+            this.removeAces = removeAces;
+        }
+
+        @Override
+        public ObjectId call() throws Exception {
+            return session.createPolicy(properties, folderId, policies, addAces, removeAces);
+        }
+    }
+
+    @Override
+    public Future<ObjectId> createPolicy(Map<String, ?> properties, ObjectId folderId, List<Policy> policies,
+            List<Ace> addAces, List<Ace> removeAces) {
+        return submit(new CreatePolicyCallable(session, properties, folderId, policies, addAces, removeAces));
+    }
+
+    protected static class CreateItemCallable extends SessionCallable<ObjectId> {
+        private Map<String, ?> properties;
+        private ObjectId folderId;
+        private List<Policy> policies;
+        private List<Ace> addAces;
+        private List<Ace> removeAces;
+
+        public CreateItemCallable(Session session, Map<String, ?> properties, ObjectId folderId, List<Policy> policies,
+                List<Ace> addAces, List<Ace> removeAces) {
+            super(session);
+            this.properties = properties;
+            this.folderId = folderId;
+            this.policies = policies;
+            this.addAces = addAces;
+            this.removeAces = removeAces;
+        }
+
+        @Override
+        public ObjectId call() throws Exception {
+            return session.createItem(properties, folderId, policies, addAces, removeAces);
+        }
+    }
+
+    @Override
+    public Future<ObjectId> createItem(Map<String, ?> properties, ObjectId folderId, List<Policy> policies,
+            List<Ace> addAces, List<Ace> removeAces) {
+        return submit(new CreateItemCallable(session, properties, folderId, policies, addAces, removeAces));
+    }
+
+    protected static class CreateRelationshipCallable extends SessionCallable<ObjectId> {
+        private Map<String, ?> properties;
+        private List<Policy> policies;
+        private List<Ace> addAces;
+        private List<Ace> removeAces;
+
+        public CreateRelationshipCallable(Session session, Map<String, ?> properties, List<Policy> policies,
+                List<Ace> addAces, List<Ace> removeAces) {
+            super(session);
+            this.properties = properties;
+            this.policies = policies;
+            this.addAces = addAces;
+            this.removeAces = removeAces;
+        }
+
+        @Override
+        public ObjectId call() throws Exception {
+            return session.createRelationship(properties, policies, addAces, removeAces);
+        }
+    }
+
+    @Override
+    public Future<ObjectId> createRelationship(Map<String, ?> properties, List<Policy> policies, List<Ace> addAces,
+            List<Ace> removeAces) {
+        return submit(new CreateRelationshipCallable(session, properties, policies, addAces, removeAces));
+    }
+
+    // --- content ---
+
+    protected static class GetContentStreamCallable extends SessionCallable<ContentStream> {
+        private ObjectId docId;
+        private String streamId;
+        private BigInteger offset;
+        private BigInteger length;
+
+        public GetContentStreamCallable(Session session, ObjectId docId, String streamId, BigInteger offset,
+                BigInteger length) {
+            super(session);
+            this.docId = docId;
+            this.streamId = streamId;
+            this.offset = offset;
+            this.length = length;
+        }
+
+        @Override
+        public ContentStream call() throws Exception {
+            return session.getContentStream(docId, streamId, offset, length);
+        }
+    }
+
+    @Override
+    public Future<ContentStream> getContentStream(ObjectId docId, String streamId, BigInteger offset, BigInteger length) {
+        return submit(new GetContentStreamCallable(session, docId, streamId, offset, length));
+    }
+
+    protected static class StoreContentStreamCallable extends GetContentStreamCallable {
+        private OutputStream target;
+
+        public StoreContentStreamCallable(Session session, ObjectId docId, String streamId, BigInteger offset,
+                BigInteger length, OutputStream target) {
+            super(session, docId, streamId, offset, length);
+            this.target = target;
+        }
+
+        @Override
+        public ContentStream call() throws Exception {
+            ContentStream contentStream = super.call();
+            try {
+                if (contentStream != null && contentStream.getStream() != null && target != null) {
+                    IOUtils.copy(contentStream.getStream(), target);
+                }
+            } finally {
+                IOUtils.closeQuietly(contentStream);
+            }
+
+            return contentStream;
+        }
+    }
+
+    @Override
+    public Future<ContentStream> storeContentStream(ObjectId docId, String streamId, BigInteger offset,
+            BigInteger length, OutputStream target) {
+        return submit(new StoreContentStreamCallable(session, docId, streamId, offset, length, target));
+    }
+
+    // --- delete ---
+
+    protected static class DeleteCallable extends SessionCallable<Object> {
+        private ObjectId objectId;
+        private boolean allVersions;
+
+        public DeleteCallable(Session session, ObjectId objectId, boolean allVersions) {
+            super(session);
+            this.objectId = objectId;
+            this.allVersions = allVersions;
+        }
+
+        @Override
+        public Object call() throws Exception {
+            session.delete(objectId, allVersions);
+            return null;
+        }
+    }
+
+    @Override
+    public Future<?> delete(ObjectId objectId, boolean allVersions) {
+        return submit(new DeleteCallable(session, objectId, allVersions));
+    }
+
+    protected static class DeleteTreeCallable extends SessionCallable<List<String>> {
+        private ObjectId folderId;
+        private boolean allVersions;
+        private UnfileObject unfile;
+        private boolean continueOnFailure;
+
+        public DeleteTreeCallable(Session session, ObjectId folderId, boolean allVersions, UnfileObject unfile,
+                boolean continueOnFailure) {
+            super(session);
+            this.folderId = folderId;
+            this.allVersions = allVersions;
+            this.unfile = unfile;
+            this.continueOnFailure = continueOnFailure;
+        }
+
+        @Override
+        public List<String> call() throws Exception {
+            return session.deleteTree(folderId, allVersions, unfile, continueOnFailure);
+        }
+    }
+
+    @Override
+    public Future<List<String>> deleteTree(ObjectId folderId, boolean allVersions, UnfileObject unfile,
+            boolean continueOnFailure) {
+        return submit(new DeleteTreeCallable(session, folderId, allVersions, unfile, continueOnFailure));
+    }
+
+    // --- ACL ---
+
+    protected static class ApplyAclCallable extends SessionCallable<Acl> {
+        private ObjectId objectId;
+        private List<Ace> addAces;
+        private List<Ace> removeAces;
+        private AclPropagation aclPropagation;
+
+        public ApplyAclCallable(Session session, ObjectId objectId, List<Ace> addAces, List<Ace> removeAces,
+                AclPropagation aclPropagation) {
+            super(session);
+            this.objectId = objectId;
+            this.addAces = addAces;
+            this.removeAces = removeAces;
+            this.aclPropagation = aclPropagation;
+        }
+
+        @Override
+        public Acl call() throws Exception {
+            return session.applyAcl(objectId, addAces, removeAces, aclPropagation);
+        }
+    }
+
+    @Override
+    public Future<Acl> applyAcl(ObjectId objectId, List<Ace> addAces, List<Ace> removeAces,
+            AclPropagation aclPropagation) {
+        return getExecutorService()
+                .submit(new ApplyAclCallable(session, objectId, addAces, removeAces, aclPropagation));
+    }
+
+    protected static class SetAclCallable extends SessionCallable<Acl> {
+        private ObjectId objectId;
+        private List<Ace> aces;
+
+        public SetAclCallable(Session session, ObjectId objectId, List<Ace> aces) {
+            super(session);
+            this.objectId = objectId;
+            this.aces = aces;
+        }
+
+        @Override
+        public Acl call() throws Exception {
+            return session.setAcl(objectId, aces);
+        }
+    }
+
+    @Override
+    public Future<Acl> setAcl(ObjectId objectId, List<Ace> aces) {
+        return submit(new SetAclCallable(session, objectId, aces));
+    }
+
+    // --- policy ---
+
+    protected static class ApplyPolicyCallable extends SessionCallable<Object> {
+        private ObjectId objectId;
+        private ObjectId[] policyIds;
+
+        public ApplyPolicyCallable(Session session, ObjectId objectId, ObjectId... policyIds) {
+            super(session);
+            this.objectId = objectId;
+            this.policyIds = policyIds;
+        }
+
+        @Override
+        public Object call() throws Exception {
+            session.applyPolicy(objectId, policyIds);
+            return null;
+        }
+    }
+
+    @Override
+    public Future<?> applyPolicy(ObjectId objectId, ObjectId... policyIds) {
+        return submit(new ApplyPolicyCallable(session, objectId, policyIds));
+    }
+
+    protected static class RemovePolicyCallable extends SessionCallable<Object> {
+        private ObjectId objectId;
+        private ObjectId[] policyIds;
+
+        public RemovePolicyCallable(Session session, ObjectId objectId, ObjectId... policyIds) {
+            super(session);
+            this.objectId = objectId;
+            this.policyIds = policyIds;
+        }
+
+        @Override
+        public Object call() throws Exception {
+            session.removePolicy(objectId, policyIds);
+            return null;
+        }
+    }
+
+    @Override
+    public Future<?> removePolicy(ObjectId objectId, ObjectId... policyIds) {
+        return submit(new RemovePolicyCallable(session, objectId, policyIds));
+    }
+
+    // --- shut down ---
+
+    /**
+     * @see ExecutorService#shutdown()
+     */
+    public void shutdown() {
+        if (getExecutorService() != null) {
+            getExecutorService().shutdown();
+        }
+    }
+
+    /**
+     * @see ExecutorService#shutdownNow()
+     */
+    public List<Runnable> shutdownNow() {
+        if (getExecutorService() != null) {
+            return getExecutorService().shutdownNow();
+        }
+
+        return Collections.emptyList();
+    }
+
+    /**
+     * @see ExecutorService#isShutdown()
+     */
+    public boolean isShutdown() {
+        if (getExecutorService() != null) {
+            return getExecutorService().isShutdown();
+        }
+
+        return true;
+    }
+
+    /**
+     * @see ExecutorService#isTerminated()
+     */
+    public boolean isTerminated() {
+        if (getExecutorService() != null) {
+            return getExecutorService().isTerminated();
+        }
+
+        return true;
+    }
+
+    /**
+     * @see ExecutorService#awaitTermination(long, TimeUnit)
+     */
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+        if (getExecutorService() != null) {
+            return getExecutorService().awaitTermination(timeout, unit);
+        }
+
+        return true;
+    }
+}

Added: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/AsyncSessionFactoryImpl.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/AsyncSessionFactoryImpl.java?rev=1681380&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/AsyncSessionFactoryImpl.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/AsyncSessionFactoryImpl.java Sat May 23 20:48:20 2015
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.chemistry.opencmis.client.runtime.async;
+
+import org.apache.chemistry.opencmis.client.api.AsyncSession;
+import org.apache.chemistry.opencmis.client.api.AsyncSessionFactory;
+import org.apache.chemistry.opencmis.client.api.Session;
+
+/**
+ * Factory for {@link AsyncSession} objects.
+ */
+public class AsyncSessionFactoryImpl implements AsyncSessionFactory {
+
+    protected AsyncSessionFactoryImpl() {
+    }
+
+    public static AsyncSessionFactoryImpl newInstance() {
+        return new AsyncSessionFactoryImpl();
+    }
+
+    @Override
+    public AsyncSession createAsyncSession(Session session) {
+        return createAsyncSession(session, 5);
+    }
+
+    @Override
+    public AsyncSession createAsyncSession(Session session, int maxParallelRequests) {
+        if (session == null) {
+            throw new IllegalArgumentException("Session must be set!");
+        }
+
+        if (maxParallelRequests < 1) {
+            throw new IllegalArgumentException("maxParallelRequests must be >0!");
+        }
+
+        return new ThreadPoolExecutorAsyncSession(session, maxParallelRequests);
+    }
+}

Added: chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/ThreadPoolExecutorAsyncSession.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/ThreadPoolExecutorAsyncSession.java?rev=1681380&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/ThreadPoolExecutorAsyncSession.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-impl/src/main/java/org/apache/chemistry/opencmis/client/runtime/async/ThreadPoolExecutorAsyncSession.java Sat May 23 20:48:20 2015
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.chemistry.opencmis.client.runtime.async;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.chemistry.opencmis.client.api.AsyncSession;
+import org.apache.chemistry.opencmis.client.api.Session;
+
+/**
+ * An implementation of the {@link AsyncSession} interface that uses an
+ * {@link ThreadPoolExecutor} object for running asynchronous tasks.
+ */
+public class ThreadPoolExecutorAsyncSession extends AbstractExecutorServiceAsyncSession<ThreadPoolExecutor> {
+
+    private ThreadPoolExecutor executor;
+
+    public ThreadPoolExecutorAsyncSession(Session session) {
+        this(session, 5);
+    }
+
+    public ThreadPoolExecutorAsyncSession(Session session, int maxThreads) {
+        super(session);
+        executor = new ThreadPoolExecutor(maxThreads, maxThreads, 0L, TimeUnit.MILLISECONDS,
+                new LinkedBlockingQueue<Runnable>());
+    }
+
+    @Override
+    public ThreadPoolExecutor getExecutorService() {
+        return executor;
+    }
+}

Modified: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/impl/AbstractCmisTest.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/impl/AbstractCmisTest.java?rev=1681380&r1=1681379&r2=1681380&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/impl/AbstractCmisTest.java (original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/impl/AbstractCmisTest.java Sat May 23 20:48:20 2015
@@ -18,6 +18,7 @@
  */
 package org.apache.chemistry.opencmis.tck.impl;
 
+import java.lang.reflect.Array;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -323,6 +324,50 @@ public abstract class AbstractCmisTest i
             }
         }
 
+        return success;
+    }
+
+    protected CmisTestResult assertEqualArray(Object expected, Object actual, CmisTestResult success,
+            CmisTestResult failure) {
+        if (expected == null && actual == null) {
+            return success;
+        }
+
+        if (expected == null) {
+            return addResultChild(failure, createResult(CmisTestResultStatus.INFO, "Expected array is null!"));
+        }
+
+        if (!expected.getClass().isArray()) {
+            return addResultChild(failure, createResult(CmisTestResultStatus.INFO, "Expected array is not an array!"));
+        }
+
+        if (actual == null) {
+            return addResultChild(failure, createResult(CmisTestResultStatus.INFO, "Actual array is null!"));
+        }
+
+        if (!actual.getClass().isArray()) {
+            return addResultChild(failure, createResult(CmisTestResultStatus.INFO, "Actual array is not an array!"));
+        }
+
+        if (Array.getLength(expected) != Array.getLength(actual)) {
+            return addResultChild(
+                    failure,
+                    createResult(
+                            CmisTestResultStatus.INFO,
+                            "Array sizes don't match! expected: " + Array.getLength(expected) + " / actual: "
+                                    + Array.getLength(actual)));
+        }
+
+        for (int i = 0; i < Array.getLength(expected); i++) {
+            if (!isEqual(Array.get(expected, i), Array.get(actual, i))) {
+                return addResultChild(
+                        failure,
+                        createResult(CmisTestResultStatus.INFO,
+                                "expected array item[" + i + "]: " + formatValue(Array.get(expected, i))
+                                        + " / actual array item[" + i + "]: " + formatValue(Array.get(actual, i))));
+            }
+        }
+
         return success;
     }
 

Added: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/AsyncCreateAndDeleteDocumentTest.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/AsyncCreateAndDeleteDocumentTest.java?rev=1681380&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/AsyncCreateAndDeleteDocumentTest.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/AsyncCreateAndDeleteDocumentTest.java Sat May 23 20:48:20 2015
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.chemistry.opencmis.tck.tests.crud;
+
+import static org.apache.chemistry.opencmis.tck.CmisTestResultStatus.FAILURE;
+import static org.apache.chemistry.opencmis.tck.CmisTestResultStatus.UNEXPECTED_EXCEPTION;
+import static org.apache.chemistry.opencmis.tck.CmisTestResultStatus.WARNING;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.Future;
+
+import org.apache.chemistry.opencmis.client.api.AsyncSession;
+import org.apache.chemistry.opencmis.client.api.CmisObject;
+import org.apache.chemistry.opencmis.client.api.Document;
+import org.apache.chemistry.opencmis.client.api.Folder;
+import org.apache.chemistry.opencmis.client.api.ItemIterable;
+import org.apache.chemistry.opencmis.client.api.ObjectId;
+import org.apache.chemistry.opencmis.client.api.Session;
+import org.apache.chemistry.opencmis.client.runtime.async.AbstractExecutorServiceAsyncSession;
+import org.apache.chemistry.opencmis.client.runtime.async.AsyncSessionFactoryImpl;
+import org.apache.chemistry.opencmis.commons.PropertyIds;
+import org.apache.chemistry.opencmis.commons.data.ContentStream;
+import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
+import org.apache.chemistry.opencmis.tck.CmisTestResult;
+import org.apache.chemistry.opencmis.tck.impl.AbstractSessionTest;
+
+/**
+ * Simple document test.
+ */
+public class AsyncCreateAndDeleteDocumentTest extends AbstractSessionTest {
+
+    @Override
+    public void init(Map<String, String> parameters) {
+        super.init(parameters);
+        setName("Asynchronous Create and Delete Document Test");
+        setDescription("Creates documents in parallel, checks the newly created documents and finally deletes the created documents in parallel.");
+    }
+
+    @Override
+    public void run(Session session) {
+        CmisTestResult f;
+
+        int numOfDocuments = 100;
+        String mimeType = "text/plain";
+
+        byte[] contentBytes = new byte[64 * 1024];
+        for (int i = 0; i < contentBytes.length; i++) {
+            contentBytes[i] = (byte) ('0' + i % 10);
+        }
+
+        // create an async session
+        AsyncSession asyncSession = AsyncSessionFactoryImpl.newInstance().createAsyncSession(session, 10);
+
+        // create a test folder
+        Folder testFolder = createTestFolder(session);
+
+        try {
+            // create documents
+            List<Future<ObjectId>> docFutures = new ArrayList<Future<ObjectId>>();
+            for (int i = 0; i < numOfDocuments; i++) {
+                String name = "asyncdoc" + i + ".txt";
+
+                Map<String, Object> properties = new HashMap<String, Object>();
+                properties.put(PropertyIds.NAME, name);
+                properties.put(PropertyIds.OBJECT_TYPE_ID, getDocumentTestTypeId());
+
+                ContentStream contentStream = new ContentStreamImpl(name, BigInteger.valueOf(contentBytes.length),
+                        mimeType, new ByteArrayInputStream(contentBytes));
+
+                Future<ObjectId> newDocument = asyncSession.createDocument(properties, testFolder, contentStream, null);
+
+                docFutures.add(newDocument);
+            }
+
+            // wait for all document being created
+            List<ObjectId> docIds = new ArrayList<ObjectId>();
+            try {
+                for (Future<ObjectId> docFuture : docFutures) {
+                    ObjectId id = docFuture.get();
+                    docIds.add(id);
+                }
+            } catch (Exception e) {
+                addResult(createResult(UNEXPECTED_EXCEPTION,
+                        "Documents could not been created! Exception: " + e.getMessage(), e, true));
+            }
+
+            // check children of the test folder
+            int count = countChildren(testFolder);
+            f = createResult(FAILURE, "Test folder should have " + numOfDocuments + " children but has " + count + "!");
+            addResult(assertEquals(count, numOfDocuments, null, f));
+
+            // simple children test
+            addResult(checkChildren(session, testFolder, "Test folder children check"));
+
+            // get documents
+            Map<String, Future<CmisObject>> getObjectFutures = new HashMap<String, Future<CmisObject>>();
+            Map<String, Future<ContentStream>> contentStreamFutures = new HashMap<String, Future<ContentStream>>();
+            Map<String, ByteArrayOutputStream> content = new HashMap<String, ByteArrayOutputStream>();
+
+            for (ObjectId docId : docIds) {
+                Future<CmisObject> getObjectFuture = asyncSession.getObject(docId, SELECT_ALL_NO_CACHE_OC);
+                getObjectFutures.put(docId.getId(), getObjectFuture);
+
+                ByteArrayOutputStream out = new ByteArrayOutputStream(contentBytes.length);
+                content.put(docId.getId(), out);
+
+                Future<ContentStream> contentStreamFuture = asyncSession.storeContentStream(docId, out);
+                contentStreamFutures.put(docId.getId(), contentStreamFuture);
+            }
+
+            // wait for all document being fetched
+            try {
+                for (Map.Entry<String, Future<CmisObject>> getObjectFuture : getObjectFutures.entrySet()) {
+                    CmisObject object = getObjectFuture.getValue().get();
+
+                    f = createResult(FAILURE, "Fetching document failed!");
+                    addResult(assertIsTrue(object instanceof Document, null, f));
+
+                    if (object != null) {
+                        f = createResult(FAILURE, "Fetched wrong document!");
+                        addResult(assertEquals(getObjectFuture.getKey(), object.getId(), null, f));
+                    }
+                }
+            } catch (Exception e) {
+                addResult(createResult(UNEXPECTED_EXCEPTION,
+                        "Documents could not been fetched! Exception: " + e.getMessage(), e, true));
+            }
+
+            // wait for all document content being fetched
+            try {
+                for (Map.Entry<String, Future<ContentStream>> contentStreamFuture : contentStreamFutures.entrySet()) {
+                    ContentStream contentStream = contentStreamFuture.getValue().get();
+
+                    f = createResult(FAILURE, "Fetching document content failed!");
+                    addResult(assertNotNull(contentStream, null, f));
+
+                    if (contentStream != null) {
+                        if (contentStream.getMimeType() == null) {
+                            addResult(createResult(FAILURE, "Content MIME type is null!"));
+                        } else {
+                            f = createResult(WARNING, "Content MIME types don't match!");
+                            addResult(assertIsTrue(contentStream.getMimeType().trim().toLowerCase(Locale.ENGLISH)
+                                    .startsWith(mimeType.toLowerCase(Locale.ENGLISH)), null, f));
+                        }
+                    }
+
+                    ByteArrayOutputStream out = content.get(contentStreamFuture.getKey());
+                    byte[] readBytes = out.toByteArray();
+
+                    f = createResult(FAILURE, "Read content length doesn't match document content length!");
+                    addResult(assertEquals(contentBytes.length, readBytes.length, null, f));
+
+                    f = createResult(FAILURE, "Read content doesn't match document content!");
+                    addResult(assertEqualArray(contentBytes, readBytes, null, f));
+                }
+            } catch (Exception e) {
+                addResult(createResult(UNEXPECTED_EXCEPTION,
+                        "Document content could not been fetched! Exception: " + e.getMessage(), e, true));
+            }
+
+            // delete documents
+            List<Future<?>> delFutures = new ArrayList<Future<?>>();
+            for (ObjectId docId : docIds) {
+                Future<?> delFuture = asyncSession.delete(docId);
+                delFutures.add(delFuture);
+            }
+
+            // wait for all document being deleted
+            try {
+                for (Future<?> delFuture : delFutures) {
+                    delFuture.get();
+                }
+            } catch (Exception e) {
+                addResult(createResult(UNEXPECTED_EXCEPTION,
+                        "Documents could not been deleted! Exception: " + e.getMessage(), e, true));
+            }
+
+            // check children of the test folder
+            count = countChildren(testFolder);
+            f = createResult(FAILURE, "Test folder should be empty but has " + count + " children!");
+            addResult(assertEquals(count, 0, null, f));
+        } finally {
+            // delete the test folder
+            deleteTestFolder();
+
+            if (asyncSession instanceof AbstractExecutorServiceAsyncSession<?>) {
+                ((AbstractExecutorServiceAsyncSession<?>) asyncSession).shutdown();
+            }
+        }
+
+        addResult(createInfoResult("Tested the parallel creation and deletion of " + numOfDocuments + " documents."));
+    }
+
+    private int countChildren(Folder folder) {
+        int count = 0;
+        ItemIterable<CmisObject> children = folder.getChildren(SELECT_ALL_NO_CACHE_OC);
+        for (CmisObject child : children) {
+            if (child instanceof Document) {
+                count++;
+            }
+        }
+
+        return count;
+    }
+}

Added: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/AsyncCreateAndDeleteFolderTest.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/AsyncCreateAndDeleteFolderTest.java?rev=1681380&view=auto
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/AsyncCreateAndDeleteFolderTest.java (added)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/AsyncCreateAndDeleteFolderTest.java Sat May 23 20:48:20 2015
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.chemistry.opencmis.tck.tests.crud;
+
+import static org.apache.chemistry.opencmis.tck.CmisTestResultStatus.FAILURE;
+import static org.apache.chemistry.opencmis.tck.CmisTestResultStatus.UNEXPECTED_EXCEPTION;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Future;
+
+import org.apache.chemistry.opencmis.client.api.AsyncSession;
+import org.apache.chemistry.opencmis.client.api.CmisObject;
+import org.apache.chemistry.opencmis.client.api.Folder;
+import org.apache.chemistry.opencmis.client.api.ItemIterable;
+import org.apache.chemistry.opencmis.client.api.ObjectId;
+import org.apache.chemistry.opencmis.client.api.Session;
+import org.apache.chemistry.opencmis.client.runtime.async.AbstractExecutorServiceAsyncSession;
+import org.apache.chemistry.opencmis.client.runtime.async.AsyncSessionFactoryImpl;
+import org.apache.chemistry.opencmis.commons.PropertyIds;
+import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
+import org.apache.chemistry.opencmis.tck.CmisTestResult;
+import org.apache.chemistry.opencmis.tck.impl.AbstractSessionTest;
+
+/**
+ * Simple document test.
+ */
+public class AsyncCreateAndDeleteFolderTest extends AbstractSessionTest {
+
+    @Override
+    public void init(Map<String, String> parameters) {
+        super.init(parameters);
+        setName("Asynchronous Create and Delete Folder Test");
+        setDescription("Creates folders in parallel and deletes the created folders in parallel.");
+    }
+
+    @Override
+    public void run(Session session) {
+        CmisTestResult f;
+
+        int numOfFolders = 100;
+
+        // create an async session
+        AsyncSession asyncSession = AsyncSessionFactoryImpl.newInstance().createAsyncSession(session, 10);
+
+        // create a test folder
+        Folder testFolder = createTestFolder(session);
+
+        try {
+            // create folders
+            List<Future<ObjectId>> folderFutures = new ArrayList<Future<ObjectId>>();
+            for (int i = 0; i < numOfFolders; i++) {
+                String name = "asyncfolder" + i;
+
+                Map<String, Object> properties = new HashMap<String, Object>();
+                properties.put(PropertyIds.NAME, name);
+                properties.put(PropertyIds.OBJECT_TYPE_ID, getFolderTestTypeId());
+
+                Future<ObjectId> newFolder = asyncSession.createFolder(properties, testFolder);
+
+                folderFutures.add(newFolder);
+            }
+
+            // wait for all folders being created
+            List<ObjectId> folderIds = new ArrayList<ObjectId>();
+            try {
+                for (Future<ObjectId> folderFuture : folderFutures) {
+                    ObjectId id = folderFuture.get();
+                    folderIds.add(id);
+                }
+            } catch (Exception e) {
+                addResult(createResult(UNEXPECTED_EXCEPTION,
+                        "Folder could not been created! Exception: " + e.getMessage(), e, true));
+            }
+
+            // check children of test folder
+            int count = countChildren(testFolder);
+            f = createResult(FAILURE, "Test folder should have " + numOfFolders + " children but has " + count + "!");
+            addResult(assertEquals(count, numOfFolders, null, f));
+
+            // get folders
+            Map<String, Future<CmisObject>> getObjectFutures = new HashMap<String, Future<CmisObject>>();
+
+            for (ObjectId folderId : folderIds) {
+                Future<CmisObject> getObjectFuture = asyncSession.getObject(folderId, SELECT_ALL_NO_CACHE_OC);
+                getObjectFutures.put(folderId.getId(), getObjectFuture);
+            }
+
+            // wait for all folders being fetched
+            List<String> paths = new ArrayList<String>();
+            try {
+                for (Map.Entry<String, Future<CmisObject>> getObjectFuture : getObjectFutures.entrySet()) {
+                    CmisObject object = getObjectFuture.getValue().get();
+
+                    f = createResult(FAILURE, "Fetching folder failed!");
+                    addResult(assertIsTrue(object instanceof Folder, null, f));
+
+                    if (object != null) {
+                        f = createResult(FAILURE, "Fetched wrong folder!");
+                        addResult(assertEquals(getObjectFuture.getKey(), object.getId(), null, f));
+
+                        paths.add(((Folder) object).getPath());
+                    }
+                }
+            } catch (Exception e) {
+                addResult(createResult(UNEXPECTED_EXCEPTION,
+                        "Folders could not been fetched! Exception: " + e.getMessage(), e, true));
+            }
+
+            // get folders by path
+            Map<String, Future<CmisObject>> getObjectByPathFutures = new HashMap<String, Future<CmisObject>>();
+
+            for (String path : paths) {
+                Future<CmisObject> getObjectByPathFuture = asyncSession.getObjectByPath(path, SELECT_ALL_NO_CACHE_OC);
+                getObjectByPathFutures.put(path, getObjectByPathFuture);
+            }
+
+            // wait for all folders being fetched
+            try {
+                for (Map.Entry<String, Future<CmisObject>> getObjectByPathFuture : getObjectByPathFutures.entrySet()) {
+                    CmisObject object = getObjectByPathFuture.getValue().get();
+
+                    f = createResult(FAILURE, "Fetching folder failed!");
+                    addResult(assertIsTrue(object instanceof Folder, null, f));
+
+                    if (object != null) {
+                        f = createResult(FAILURE, "Fetched wrong folder!");
+                        addResult(assertEquals(getObjectByPathFuture.getKey(), ((Folder) object).getPath(), null, f));
+                    }
+                }
+            } catch (Exception e) {
+                addResult(createResult(UNEXPECTED_EXCEPTION,
+                        "Folders could not been fetched! Exception: " + e.getMessage(), e, true));
+            }
+
+            // delete folders
+            List<Future<?>> delFutures = new ArrayList<Future<?>>();
+            for (ObjectId folderId : folderIds) {
+                Future<?> delFuture = asyncSession.deleteTree(folderId, true, UnfileObject.DELETE, true);
+                delFutures.add(delFuture);
+            }
+
+            // wait for all folders being deleted
+            try {
+                for (Future<?> delFuture : delFutures) {
+                    delFuture.get();
+                }
+            } catch (Exception e) {
+                addResult(createResult(UNEXPECTED_EXCEPTION,
+                        "Folder could not been deleted! Exception: " + e.getMessage(), e, true));
+            }
+
+            // check children of test folder
+            count = countChildren(testFolder);
+            f = createResult(FAILURE, "Test folder should be empty but has " + count + " children!");
+            addResult(assertEquals(count, 0, null, f));
+        } finally {
+            // delete the test folder
+            deleteTestFolder();
+
+            if (asyncSession instanceof AbstractExecutorServiceAsyncSession<?>) {
+                ((AbstractExecutorServiceAsyncSession<?>) asyncSession).shutdown();
+            }
+        }
+
+        addResult(createInfoResult("Tested the parallel creation and deletion of " + numOfFolders + " folders."));
+    }
+
+    private int countChildren(Folder folder) {
+        int count = 0;
+        ItemIterable<CmisObject> children = folder.getChildren(SELECT_ALL_NO_CACHE_OC);
+        for (CmisObject child : children) {
+            if (child instanceof Folder) {
+                count++;
+            }
+        }
+
+        return count;
+    }
+}

Modified: chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/CRUDTestGroup.java
URL: http://svn.apache.org/viewvc/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/CRUDTestGroup.java?rev=1681380&r1=1681379&r2=1681380&view=diff
==============================================================================
--- chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/CRUDTestGroup.java (original)
+++ chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-tck/src/main/java/org/apache/chemistry/opencmis/tck/tests/crud/CRUDTestGroup.java Sat May 23 20:48:20 2015
@@ -52,5 +52,7 @@ public class CRUDTestGroup extends Abstr
         addTest(new MoveTest());
         addTest(new DeleteTreeTest());
         addTest(new OperationContextTest());
+        addTest(new AsyncCreateAndDeleteDocumentTest());
+        addTest(new AsyncCreateAndDeleteFolderTest());
     }
 }