diff --git a/core/pom.xml b/core/pom.xml index 7df1af98..2f47f0b7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ jp.aegif.nemaki core war - 2.3.12 + 2.3.13 core NemakiWare server https://github.com/NemakiWare/NemakiWare @@ -34,11 +34,8 @@ junit - junit - 4.10 - jar - test - false + junit + 4.12 @@ -246,7 +243,7 @@ net.sf.ehcache ehcache - 2.7.2 + 2.10.2 @@ -274,6 +271,18 @@ cloning 1.9.1 + + + com.google.guava + guava + 19.0 + + + + joda-time + joda-time + 2.9.3 + jp.aegif.nemakiware @@ -301,9 +310,9 @@ 2.19.1 true - - **/*TestGroup.java - + + **/*TestGroup.java + diff --git a/core/src/main/java/jp/aegif/nemaki/businesslogic/ContentService.java b/core/src/main/java/jp/aegif/nemaki/businesslogic/ContentService.java index 50b0a950..3343c412 100644 --- a/core/src/main/java/jp/aegif/nemaki/businesslogic/ContentService.java +++ b/core/src/main/java/jp/aegif/nemaki/businesslogic/ContentService.java @@ -390,7 +390,7 @@ Item createItem(CallContext callContext, String repositoryId, * @return */ Content update(String repositoryId, Content content); - + /** * Update properties of a content * diff --git a/core/src/main/java/jp/aegif/nemaki/businesslogic/impl/ContentServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/businesslogic/impl/ContentServiceImpl.java index 5bc73350..96ce96b8 100644 --- a/core/src/main/java/jp/aegif/nemaki/businesslogic/impl/ContentServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/businesslogic/impl/ContentServiceImpl.java @@ -27,15 +27,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.businesslogic.rendition.RenditionManager; import jp.aegif.nemaki.cmis.aspect.query.solr.SolrUtil; @@ -43,7 +39,6 @@ import jp.aegif.nemaki.cmis.factory.info.RepositoryInfo; import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap; import jp.aegif.nemaki.dao.ContentDaoService; -import jp.aegif.nemaki.dao.impl.cached.ContentDaoServiceImpl; import jp.aegif.nemaki.model.Ace; import jp.aegif.nemaki.model.Acl; import jp.aegif.nemaki.model.Archive; @@ -212,10 +207,11 @@ public Folder getParent(String repositoryId, String objectId) { public List getChildren(String repositoryId, String folderId) { List children = new ArrayList(); - List indices = contentDaoService.getLatestChildrenIndex(repositoryId, folderId); + List indices = contentDaoService.getChildren(repositoryId, folderId); if (CollectionUtils.isEmpty(indices)) return null; - + + //TODO getを重複して行う必要なし for (Content c : indices) { if (c.isDocument()) { Document d = contentDaoService.getDocument(repositoryId, c.getId()); @@ -613,14 +609,14 @@ public Document checkOut(CallContext callContext, String repositoryId, String ob public void cancelCheckOut(CallContext callContext, String repositoryId, String objectId, ExtensionsData extension) { Document pwc = getDocument(repositoryId, objectId); - VersionSeries vs = getVersionSeries(repositoryId, pwc); - + writeChangeEvent(callContext, repositoryId, pwc, ChangeType.DELETED); // Delete attachment & document itself(without archiving) contentDaoService.delete(repositoryId, pwc.getAttachmentNodeId()); contentDaoService.delete(repositoryId, pwc.getId()); + VersionSeries vs = getVersionSeries(repositoryId, pwc); // Reverse the effect of checkout setModifiedSignature(callContext, vs); vs.setVersionSeriesCheckedOut(false); @@ -628,6 +624,14 @@ public void cancelCheckOut(CallContext callContext, String repositoryId, String vs.setVersionSeriesCheckedOutId(""); contentDaoService.update(repositoryId, vs); + List versions = getAllVersions(callContext, repositoryId, vs.getId()); + if(CollectionUtils.isNotEmpty(versions)){ + //Collections.sort(versions, new VersionComparator()); + for(Document version : versions){ + contentDaoService.refreshCmisObjectData(repositoryId, version.getId()); + } + } + // Call Solr indexing(optional) solrUtil.callSolrIndexing(repositoryId); } @@ -637,10 +641,11 @@ public Document checkIn(CallContext callContext, String repositoryId, Holder policies, org.apache.chemistry.opencmis.commons.data.Acl addAces, org.apache.chemistry.opencmis.commons.data.Acl removeAces, ExtensionsData extension) { + String id = objectId.getValue(); + Document pwc = getDocument(repositoryId, id); Document checkedIn = buildCopyDocumentWithBasicProperties(callContext, pwc); - Document latest = getDocumentOfLatestVersion(repositoryId, pwc.getVersionSeriesId()); // When PWCUpdatable is true @@ -657,6 +662,9 @@ public Document checkIn(CallContext callContext, String repositoryId, Holder injectPropertyValue(Collection> pro } @Override - public synchronized Content updateProperties(CallContext callContext, String repositoryId, Properties properties, + public Content updateProperties(CallContext callContext, String repositoryId, Properties properties, Content content) { Content modified = modifyProperties(callContext, repositoryId, properties, content); @@ -1130,9 +1135,9 @@ public synchronized Content updateProperties(CallContext callContext, String rep return result; } - + @Override - public synchronized Content update(String repositoryId, Content content) { + public Content update(String repositoryId, Content content) { Content result = null; if (content instanceof Document) { @@ -1174,18 +1179,31 @@ private void setUpdatePropertyValue(String repositoryId, Content content, Proper } @Override - public synchronized void move(String repositoryId, Content content, Folder target) { + public void move(String repositoryId, Content content, Folder target) { + String sourceId = content.getParentId(); + content.setParentId(target.getId()); String uniqueName = buildUniqueName(repositoryId, content.getName(), target.getId(), null); content.setName(uniqueName); - update(repositoryId, content); + + move(repositoryId, content, sourceId); // Call Solr indexing(optional) solrUtil.callSolrIndexing(repositoryId); } + + private Content move(String repositoryId, Content content, String sourceId){ + Content result = null; + if(content instanceof Document){ + result = contentDaoService.move(repositoryId, (Document)content, sourceId); + }else if(content instanceof Folder){ + result = contentDaoService.move(repositoryId, (Folder)content, sourceId); + } + return result; + } @Override - public synchronized void applyPolicy(CallContext callContext, String repositoryId, String policyId, String objectId, + public void applyPolicy(CallContext callContext, String repositoryId, String policyId, String objectId, ExtensionsData extension) { Policy policy = getPolicy(repositoryId, policyId); List ids = policy.getAppliedIds(); @@ -1199,7 +1217,7 @@ public synchronized void applyPolicy(CallContext callContext, String repositoryI } @Override - public synchronized void removePolicy(CallContext callContext, String repositoryId, String policyId, + public void removePolicy(CallContext callContext, String repositoryId, String policyId, String objectId, ExtensionsData extension) { Policy policy = getPolicy(repositoryId, policyId); List ids = policy.getAppliedIds(); @@ -1218,6 +1236,12 @@ public synchronized void removePolicy(CallContext callContext, String repository @Override public void delete(CallContext callContext, String repositoryId, String objectId, Boolean deletedWithParent) { Content content = getContent(repositoryId, objectId); + + //TODO workaround + if(content == null){ + //If content is already deleted, do nothing; + return; + } // Record the change event(Before the content is deleted!) writeChangeEvent(callContext, repositoryId, content, ChangeType.DELETED); @@ -1390,7 +1414,7 @@ private boolean isPreviewEnabled() { } @Override - public synchronized void appendAttachment(CallContext callContext, String repositoryId, Holder objectId, + public void appendAttachment(CallContext callContext, String repositoryId, Holder objectId, Holder changeToken, ContentStream contentStream, boolean isLastChunk, ExtensionsData extension) { Document document = contentDaoService.getDocument(repositoryId, objectId.getValue()); AttachmentNode attachment = getAttachment(repositoryId, document.getAttachmentNodeId()); diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/aspect/CompileService.java b/core/src/main/java/jp/aegif/nemaki/cmis/aspect/CompileService.java index 56bb9cb4..28f75801 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/aspect/CompileService.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/aspect/CompileService.java @@ -42,9 +42,9 @@ public ObjectData compileObjectData(CallContext context, String repositoryId, Content content, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includeAcl); - public ObjectList compileObjectDataList(CallContext callContext, + public ObjectList compileObjectDataList(CallContext callContext, String repositoryId, List contents, String filter, - Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includeAcl, BigInteger maxItems, BigInteger skipCount, boolean folderOnly); + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includeAcl, BigInteger maxItems, BigInteger skipCount, boolean folderOnly, String orderBy); public ObjectList compileChangeDataList(CallContext context, String repositoryId, List changes, Holder changeLogToken, Boolean includeProperties, diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/aspect/impl/CompileServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/aspect/impl/CompileServiceImpl.java index f2188b43..07f157fb 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/aspect/impl/CompileServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/aspect/impl/CompileServiceImpl.java @@ -84,6 +84,7 @@ import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.cmis.aspect.CompileService; import jp.aegif.nemaki.cmis.aspect.PermissionService; +import jp.aegif.nemaki.cmis.aspect.SortUtil; import jp.aegif.nemaki.cmis.aspect.type.TypeManager; import jp.aegif.nemaki.cmis.factory.info.AclCapabilities; import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap; @@ -103,7 +104,6 @@ import jp.aegif.nemaki.model.Rendition; import jp.aegif.nemaki.model.VersionSeries; import jp.aegif.nemaki.util.DataUtil; -import jp.aegif.nemaki.util.cache.NemakiCache; import jp.aegif.nemaki.util.cache.NemakiCachePool; import jp.aegif.nemaki.util.constant.CmisExtensionToken; import net.sf.ehcache.Element; @@ -120,6 +120,7 @@ public class CompileServiceImpl implements CompileService { private TypeManager typeManager; private AclCapabilities aclCapabilities; private NemakiCachePool nemakiCachePool; + private SortUtil sortUtil; private boolean includeRelationshipsEnabled = true; @@ -276,90 +277,65 @@ private List filterRelationships(String objectId, List b } @Override - public ObjectList compileObjectDataList(CallContext callContext, + public ObjectList compileObjectDataList(CallContext callContext, String repositoryId, List contents, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includeAcl, BigInteger maxItems, - BigInteger skipCount, boolean folderOnly) { - ObjectListImpl list = new ObjectListImpl(); - list.setObjects(new ArrayList()); - - //Return empty result when no children exist + BigInteger skipCount, boolean folderOnly, String orderBy) { if (CollectionUtils.isEmpty(contents)) { + //Empty list + ObjectListImpl list = new ObjectListImpl(); + list.setObjects(new ArrayList()); list.setNumItems(BigInteger.ZERO); list.setHasMoreItems(false); return list; - } - - // Convert skip and max to integer - int skip = (skipCount == null ? 0 : skipCount.intValue()); - if (skip < 0) { - skip = 0; - } - int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); - if (max < 0) { - max = Integer.MAX_VALUE; - } - - int count = 0; - Listods = new ArrayList(); - for(T t : contents){ - //Skip items - if (count < skip) { - count++; - continue; - } - if (ods.size() >= max) { - break; - } + }else{ + Listods = new ArrayList(); + for(T c : contents){ + //Filter by folderOnly + if(folderOnly && !c.isFolder()){ + continue; + } - Content _c = (Content) t; - //Filter by folderOnly - if(folderOnly && !_c.isFolder()){ - continue; - } + //Get each ObjectData + ObjectData _od; + Element v = nemakiCachePool.get(repositoryId).getObjectDataCache().get(c.getId()); + if(v == null){ + _od = compileObjectDataWithFullAttributes(callContext, repositoryId, c); + }else{ + _od = (ObjectDataImpl)v.getObjectValue(); + } - //Convert content class - Content c = null; - if(_c.isFolder()){ - c = (Folder)t; - }else if(_c.isDocument()){ - c = (Document)t; - }else if(_c.isPolicy()){ - c = (Policy)t; - }else if(_c.isRelationship()){ - c = (Relationship)t; - }else if(_c.isItem()){ - c = (Item)t; + ObjectData od = filterObjectDataInList(callContext, repositoryId, _od, filter, includeAllowableActions, includeRelationships, renditionFilter, includeAcl); + if(od != null){ + ods.add(od); + } } - - //Get each ObjectData - ObjectData _od; - Element v = nemakiCachePool.get(repositoryId).getObjectDataCache().get(c.getId()); - if(v == null){ - _od = compileObjectDataWithFullAttributes(callContext, repositoryId, c); + + //Sort + sortUtil.sort(repositoryId, ods, orderBy); + + //Set metadata + ObjectListImpl list = new ObjectListImpl(); + Integer _skipCount = skipCount.intValue(); + Integer _maxItems = maxItems.intValue(); + + if(_skipCount >= ods.size()){ + list.setHasMoreItems(false); + list.setObjects(new ArrayList()); }else{ - _od = (ObjectDataImpl)v.getObjectValue(); + //hasMoreItems + Boolean hasMoreItems = _skipCount + _maxItems < ods.size(); + list.setHasMoreItems(hasMoreItems); + //paged list + Integer toIndex = Math.min(_skipCount + _maxItems, ods.size()); + list.setObjects(new ArrayList<>(ods.subList(_skipCount, toIndex))); } + //totalNumItem + list.setNumItems(BigInteger.valueOf(ods.size())); - ObjectData od = filterObjectDataInList(callContext, repositoryId, _od, filter, includeAllowableActions, includeRelationships, renditionFilter, includeAcl); - if(od != null){ - ods.add(od); - } - } - list.setObjects(ods); - - //Set list meta data - if(CollectionUtils.isEmpty(list.getObjects())){ return list; - }else{ - list.setNumItems(BigInteger.valueOf(contents.size())); - if(max + skip < contents.size()){ - list.setHasMoreItems(true); - } } - - return list; } @Override @@ -1462,4 +1438,8 @@ public void setNemakiCachePool(NemakiCachePool nemakiCachePool) { public void setIncludeRelationshipsEnabled(boolean includeRelationshipsEnabled) { this.includeRelationshipsEnabled = includeRelationshipsEnabled; } + + public void setSortUtil(SortUtil sortUtil) { + this.sortUtil = sortUtil; + } } \ No newline at end of file diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/aspect/impl/ExceptionServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/aspect/impl/ExceptionServiceImpl.java index d35a89c4..5adc34e6 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/aspect/impl/ExceptionServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/aspect/impl/ExceptionServiceImpl.java @@ -23,10 +23,8 @@ import java.math.BigDecimal; import java.math.BigInteger; -import java.security.acl.Permission; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -34,7 +32,6 @@ import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.ContentStream; -import org.apache.chemistry.opencmis.commons.data.PermissionMapping; import org.apache.chemistry.opencmis.commons.data.Properties; import org.apache.chemistry.opencmis.commons.data.PropertyData; import org.apache.chemistry.opencmis.commons.data.PropertyDecimal; @@ -91,7 +88,6 @@ import jp.aegif.nemaki.model.VersionSeries; import jp.aegif.nemaki.util.DataUtil; import jp.aegif.nemaki.util.constant.DomainType; -import jp.aegif.nemaki.util.constant.PropertyKey; public class ExceptionServiceImpl implements ExceptionService, ApplicationContextAware { @@ -342,6 +338,11 @@ public void objectNotFoundParentFolder(String repositoryId, String id, Content c @Override public void permissionDenied(CallContext context, String repositoryId, String key, Content content) { + if(content == null){ + System.out.println(); + } + + String baseTypeId = content.getType(); Acl acl = contentService.calculateAcl(repositoryId, content); permissionDeniedInternal(context, repositoryId, key, acl, baseTypeId, content); diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/aspect/query/solr/SolrQueryProcessor.java b/core/src/main/java/jp/aegif/nemaki/cmis/aspect/query/solr/SolrQueryProcessor.java index 51245d43..4de0027b 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/aspect/query/solr/SolrQueryProcessor.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/aspect/query/solr/SolrQueryProcessor.java @@ -27,15 +27,16 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.locks.Lock; import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.cmis.aspect.CompileService; import jp.aegif.nemaki.cmis.aspect.ExceptionService; import jp.aegif.nemaki.cmis.aspect.PermissionService; -import jp.aegif.nemaki.cmis.aspect.SortUtil; import jp.aegif.nemaki.cmis.aspect.query.QueryProcessor; import jp.aegif.nemaki.cmis.aspect.type.TypeManager; import jp.aegif.nemaki.model.Content; +import jp.aegif.nemaki.util.lock.ThreadLockService; import org.antlr.runtime.tree.Tree; import org.apache.chemistry.opencmis.commons.PropertyIds; @@ -69,8 +70,8 @@ public class SolrQueryProcessor implements QueryProcessor { private PermissionService permissionService; private CompileService compileService; private ExceptionService exceptionService; + private ThreadLockService threadLockService; private SolrUtil solrUtil; - private SortUtil sortUtil; private static final Log logger = LogFactory .getLog(SolrQueryProcessor.class); @@ -228,42 +229,37 @@ public ObjectList query(CallContext callContext, String repositoryId, } } - - // Filter out by permissions - List permitted = permissionService.getFiltered( - callContext, repositoryId, contents); - - // Filter return value with SELECT clause - Map requestedWithAliasKey = queryObject - .getRequestedPropertiesByAlias(); - String filter = null; - if (!requestedWithAliasKey.keySet().contains("*")) { - // Create filter(queryNames) from query aliases - filter = StringUtils.join(requestedWithAliasKey.values(), ","); - } - - // Build ObjectList - ObjectList result = compileService.compileObjectDataList( - callContext, repositoryId, permitted, filter, - includeAllowableActions, includeRelationships, renditionFilter, false, - maxItems, skipCount, false); - - // Sort - List sortSpecs = queryObject.getOrderBys(); - List _orderBy = new ArrayList(); - for (SortSpec sortSpec : sortSpecs) { - List _sortSpec = new ArrayList(); - _sortSpec.add(sortSpec.getSelector().getName()); - if (!sortSpec.isAscending()) { - _sortSpec.add("DESC"); + + + List locks = threadLockService.readLocks(repositoryId, contents); + try{ + threadLockService.bulkLock(locks); + + // Filter out by permissions + List permitted = permissionService.getFiltered( + callContext, repositoryId, contents); + + // Filter return value with SELECT clause + Map requestedWithAliasKey = queryObject + .getRequestedPropertiesByAlias(); + String filter = null; + if (!requestedWithAliasKey.keySet().contains("*")) { + // Create filter(queryNames) from query aliases + filter = StringUtils.join(requestedWithAliasKey.values(), ","); } - _orderBy.add(StringUtils.join(_sortSpec, " ")); - } - String orderBy = StringUtils.join(_orderBy, ","); - sortUtil.sort(repositoryId, result.getObjects(), orderBy); + // Build ObjectList + String orderBy = orderBy(queryObject); + ObjectList result = compileService.compileObjectDataList( + callContext, repositoryId, permitted, filter, + includeAllowableActions, includeRelationships, renditionFilter, false, + maxItems, skipCount, false, orderBy); - return result; + return result; + + }finally{ + threadLockService.bulkUnlock(locks); + } } else { ObjectListImpl nullList = new ObjectListImpl(); nullList.setHasMoreItems(false); @@ -271,6 +267,22 @@ public ObjectList query(CallContext callContext, String repositoryId, return nullList; } } + + private String orderBy(QueryObject queryObject){ + List sortSpecs = queryObject.getOrderBys(); + List _orderBy = new ArrayList(); + for (SortSpec sortSpec : sortSpecs) { + List _sortSpec = new ArrayList(); + _sortSpec.add(sortSpec.getSelector().getName()); + if (!sortSpec.isAscending()) { + _sortSpec.add("DESC"); + } + + _orderBy.add(StringUtils.join(_sortSpec, " ")); + } + String orderBy = StringUtils.join(_orderBy, ","); + return orderBy; + } private Tree extractWhereTree(Tree tree){ for (int i = 0; i < tree.getChildCount(); i++) { @@ -313,7 +325,7 @@ public void setSolrUtil(SolrUtil solrUtil) { this.solrUtil = solrUtil; } - public void setSortUtil(SortUtil sortUtil) { - this.sortUtil = sortUtil; + public void setThreadLockService(ThreadLockService threadLockService) { + this.threadLockService = threadLockService; } } \ No newline at end of file diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/AclServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/AclServiceImpl.java index 7081b16c..23aa86c7 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/AclServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/AclServiceImpl.java @@ -21,6 +21,7 @@ package jp.aegif.nemaki.cmis.service.impl; import java.util.List; +import java.util.concurrent.locks.Lock; import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.cmis.aspect.CompileService; @@ -30,12 +31,10 @@ import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap; import jp.aegif.nemaki.cmis.service.AclService; import jp.aegif.nemaki.model.Content; -import jp.aegif.nemaki.util.PropertyManager; -import jp.aegif.nemaki.util.cache.NemakiCache; import jp.aegif.nemaki.util.cache.NemakiCachePool; import jp.aegif.nemaki.util.constant.DomainType; import jp.aegif.nemaki.util.constant.PrincipalId; -import jp.aegif.nemaki.util.constant.PropertyKey; +import jp.aegif.nemaki.util.lock.ThreadLockService; import org.apache.chemistry.opencmis.commons.data.Ace; import org.apache.chemistry.opencmis.commons.data.Acl; @@ -56,79 +55,102 @@ public class AclServiceImpl implements AclService { private CompileService compileService; private ExceptionService exceptionService; private TypeManager typeManager; - private PropertyManager propertyManager; + private ThreadLockService threadLockService; private NemakiCachePool nemakiCachePool; private RepositoryInfoMap repositoryInfoMap; @Override public Acl getAcl(CallContext callContext, String repositoryId, String objectId, Boolean onlyBasicPermissions) { - // ////////////////// - // General Exception - // ////////////////// + exceptionService.invalidArgumentRequired("objectId", objectId); - Content content = contentService.getContent(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext,repositoryId, PermissionMapping.CAN_GET_ACL_OBJECT, content); - - // ////////////////// - // Body of the method - // ////////////////// - jp.aegif.nemaki.model.Acl acl = contentService.calculateAcl(repositoryId, content); - return compileService.compileAcl(acl, content.isAclInherited(), onlyBasicPermissions); + + Lock lock = threadLockService.getReadLock(repositoryId, objectId); + + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + + Content content = contentService.getContent(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + exceptionService.permissionDenied(callContext,repositoryId, PermissionMapping.CAN_GET_ACL_OBJECT, content); + + // ////////////////// + // Body of the method + // ////////////////// + jp.aegif.nemaki.model.Acl acl = contentService.calculateAcl(repositoryId, content); + return compileService.compileAcl(acl, content.isAclInherited(), onlyBasicPermissions); + }finally{ + lock.unlock(); + } } @Override public Acl applyAcl(CallContext callContext, String repositoryId, String objectId, Acl acl, AclPropagation aclPropagation) { - // ////////////////// - // General Exception - // ////////////////// exceptionService.invalidArgumentRequired("objectId", objectId); - Content content = contentService.getContent(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext,repositoryId, PermissionMapping.CAN_APPLY_ACL_OBJECT, content); - - // ////////////////// - // Specific Exception - // ////////////////// - TypeDefinition td = typeManager.getTypeDefinition(repositoryId, content); - if(!td.isControllableAcl()) exceptionService.constraint(objectId, "applyAcl cannot be performed on the object whose controllableAcl = false"); - exceptionService.constraintAclPropagationDoesNotMatch(aclPropagation); - exceptionService.constraintPermissionDefined(repositoryId, acl, objectId); - - // ////////////////// - // Body of the method - // ////////////////// - //Check ACL inheritance - boolean inherited = true; //Inheritance defaults to true if nothing input - List exts = acl.getExtensions(); - if(!CollectionUtils.isEmpty(exts)){ - for(CmisExtensionElement ext : exts){ - if(ext.getName().equals("inherited")){ - inherited = Boolean.valueOf(ext.getValue()); + + Lock lock = threadLockService.getReadLock(repositoryId, objectId); + + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + + Content content = contentService.getContent(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + exceptionService.permissionDenied(callContext,repositoryId, PermissionMapping.CAN_APPLY_ACL_OBJECT, content); + + // ////////////////// + // Specific Exception + // ////////////////// + TypeDefinition td = typeManager.getTypeDefinition(repositoryId, content); + if(!td.isControllableAcl()) exceptionService.constraint(objectId, "applyAcl cannot be performed on the object whose controllableAcl = false"); + exceptionService.constraintAclPropagationDoesNotMatch(aclPropagation); + exceptionService.constraintPermissionDefined(repositoryId, acl, objectId); + + // ////////////////// + // Body of the method + // ////////////////// + //Check ACL inheritance + boolean inherited = true; //Inheritance defaults to true if nothing input + List exts = acl.getExtensions(); + if(!CollectionUtils.isEmpty(exts)){ + for(CmisExtensionElement ext : exts){ + if(ext.getName().equals("inherited")){ + inherited = Boolean.valueOf(ext.getValue()); + } } + if(!content.isAclInherited().equals(inherited)) content.setAclInherited(inherited); } - if(!content.isAclInherited().equals(inherited)) content.setAclInherited(inherited); - } - jp.aegif.nemaki.model.Acl nemakiAcl = new jp.aegif.nemaki.model.Acl(); - //REPOSUTORYDETERMINED or PROPAGATE is considered as PROPAGATE - boolean objectOnly = (aclPropagation == AclPropagation.OBJECTONLY)? true : false; - for(Ace ace : acl.getAces()){ - if(ace.isDirect()){ - jp.aegif.nemaki.model.Ace nemakiAce = new jp.aegif.nemaki.model.Ace(ace.getPrincipalId(), ace.getPermissions(), objectOnly); - nemakiAcl.getLocalAces().add(nemakiAce); + jp.aegif.nemaki.model.Acl nemakiAcl = new jp.aegif.nemaki.model.Acl(); + //REPOSUTORYDETERMINED or PROPAGATE is considered as PROPAGATE + boolean objectOnly = (aclPropagation == AclPropagation.OBJECTONLY)? true : false; + for(Ace ace : acl.getAces()){ + if(ace.isDirect()){ + jp.aegif.nemaki.model.Ace nemakiAce = new jp.aegif.nemaki.model.Ace(ace.getPrincipalId(), ace.getPermissions(), objectOnly); + nemakiAcl.getLocalAces().add(nemakiAce); + } } - } - convertSystemPrinciaplId(repositoryId, nemakiAcl); - content.setAcl(nemakiAcl); - contentService.update(repositoryId, content); - - nemakiCachePool.get(repositoryId).removeCmisCache(objectId); + convertSystemPrinciaplId(repositoryId, nemakiAcl); + content.setAcl(nemakiAcl); + contentService.update(repositoryId, content); + + nemakiCachePool.get(repositoryId).removeCmisCache(objectId); + + return getAcl(callContext, repositoryId, objectId, false); + + }finally{ + lock.unlock(); + } - return getAcl(callContext, repositoryId, objectId, false); } private void convertSystemPrinciaplId(String repositoryId, jp.aegif.nemaki.model.Acl acl){ @@ -163,8 +185,8 @@ public void setTypeManager(TypeManager typeManager) { this.typeManager = typeManager; } - public void setPropertyManager(PropertyManager propertyManager) { - this.propertyManager = propertyManager; + public void setThreadLockService(ThreadLockService threadLockService) { + this.threadLockService = threadLockService; } public void setNemakiCachePool(NemakiCachePool nemakiCachePool) { diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/NavigationServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/NavigationServiceImpl.java index 4aba946e..77e434fa 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/NavigationServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/NavigationServiceImpl.java @@ -25,18 +25,19 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.locks.Lock; import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.cmis.aspect.CompileService; import jp.aegif.nemaki.cmis.aspect.ExceptionService; import jp.aegif.nemaki.cmis.aspect.PermissionService; -import jp.aegif.nemaki.cmis.aspect.SortUtil; import jp.aegif.nemaki.cmis.service.NavigationService; import jp.aegif.nemaki.model.Content; import jp.aegif.nemaki.model.Document; import jp.aegif.nemaki.model.Folder; import jp.aegif.nemaki.util.DataUtil; import jp.aegif.nemaki.util.constant.DomainType; +import jp.aegif.nemaki.util.lock.ThreadLockService; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.ExtensionsData; @@ -64,7 +65,7 @@ public class NavigationServiceImpl implements NavigationService { private ExceptionService exceptionService; private CompileService compileService; private PermissionService permissionService; - private SortUtil sortUtil; + private ThreadLockService threadLockService; @Override public ObjectInFolderList getChildren(CallContext callContext, @@ -74,31 +75,37 @@ public ObjectInFolderList getChildren(CallContext callContext, String renditionFilter, Boolean includePathSegments, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension, Holder parentObjectData) { - // ////////////////// - // General Exception - // ////////////////// - exceptionService.invalidArgumentRequiredString("folderId", folderId); - Folder folder = contentService.getFolder(repositoryId, folderId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_GET_CHILDREN_FOLDER, folder); - // ////////////////// - // Specific Exception - // ////////////////// - exceptionService.invalidArgumentFolderId(folder, folderId); - - // ////////////////// - // Body of the method - // ////////////////// - // Set ObjectData of parent folder for ObjectInfo - ObjectData _parent = compileService.compileObjectData( - callContext, repositoryId, folder, filter, - includeAllowableActions, includeRelationships, renditionFilter, false); - parentObjectData.setValue(_parent); - - return getChildrenInternal(callContext, repositoryId, folderId, filter, - orderBy, includeAllowableActions, includeRelationships, - renditionFilter, includePathSegments, maxItems, skipCount, false); + exceptionService.invalidArgumentRequiredString("folderId", folderId); + + Lock parentLock = threadLockService.getReadLock(repositoryId, folderId); + + try{ + parentLock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Folder folder = contentService.getFolder(repositoryId, folderId); + exceptionService.invalidArgumentFolderId(folder, folderId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_GET_CHILDREN_FOLDER, folder); + + // ////////////////// + // Body of the method + // ////////////////// + // Set ObjectData of parent folder for ObjectInfo + ObjectData _parent = compileService.compileObjectData( + callContext, repositoryId, folder, filter, + includeAllowableActions, includeRelationships, renditionFilter, false); + parentObjectData.setValue(_parent); + + return getChildrenInternal(callContext, repositoryId, folderId, filter, + orderBy, includeAllowableActions, includeRelationships, + renditionFilter, includePathSegments, maxItems, skipCount, false); + }finally{ + parentLock.unlock(); + } } private ObjectInFolderList getChildrenInternal(CallContext callContext, @@ -115,34 +122,41 @@ private ObjectInFolderList getChildrenInternal(CallContext callContext, // Build ObjectList List contents = contentService.getChildren(repositoryId, folderId); - - contents = permissionService.getFiltered(callContext, repositoryId, contents); - - ObjectList ol = compileService.compileObjectDataList(callContext, - repositoryId, contents, filter, - includeAllowableActions, includeRelationships, renditionFilter, false, - maxItems, skipCount, folderOnly); - - // Sort - sortUtil.sort(repositoryId, ol.getObjects(), orderBy); - - // Build ObjectInFolderList - for (ObjectData od : ol.getObjects()) { - ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl(); - objectInFolder.setObject(od); - if (includePathSegments) { - String name = DataUtil.getStringProperty(od.getProperties(), - PropertyIds.NAME); - objectInFolder.setPathSegment(name); + + + List locks = threadLockService.readLocks(repositoryId, contents); + + try{ + threadLockService.bulkLock(locks); + + contents = permissionService.getFiltered(callContext, repositoryId, contents); + + ObjectList ol = compileService.compileObjectDataList(callContext, + repositoryId, contents, filter, + includeAllowableActions, includeRelationships, renditionFilter, false, + maxItems, skipCount, folderOnly, orderBy); + + + // Build ObjectInFolderList + for (ObjectData od : ol.getObjects()) { + ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl(); + objectInFolder.setObject(od); + if (includePathSegments) { + String name = DataUtil.getStringProperty(od.getProperties(), + PropertyIds.NAME); + objectInFolder.setPathSegment(name); + } + result.getObjects().add(objectInFolder); } - result.getObjects().add(objectInFolder); - } - result.setNumItems(ol.getNumItems()); - result.setHasMoreItems(ol.hasMoreItems()); + result.setNumItems(ol.getNumItems()); + result.setHasMoreItems(ol.hasMoreItems()); - return result; + return result; + }finally{ + threadLockService.bulkUnlock(locks); + } } - + @Override public List getDescendants( CallContext callContext, String repositoryId, String folderId, @@ -151,41 +165,51 @@ public List getDescendants( String renditionFilter, Boolean includePathSegment, boolean foldersOnly, ExtensionsData extension, Holder anscestorObjectData) { - // ////////////////// - // General Exception - // ////////////////// exceptionService.invalidArgumentRequiredString("folderId", folderId); - Folder folder = contentService.getFolder(repositoryId, folderId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_GET_DESCENDENTS_FOLDER, folder); - - // ////////////////// - // Specific Exception - // ////////////////// - exceptionService.invalidArgumentFolderId(folder, folderId); - exceptionService.invalidArgumentDepth(depth); - - // ////////////////// - // Body of the method - // ////////////////// - // check depth - int d = (depth == null ? 2 : depth.intValue()); - - // set defaults if values not set - boolean iaa = (includeAllowableActions == null ? false - : includeAllowableActions.booleanValue()); - boolean ips = (includePathSegment == null ? false : includePathSegment - .booleanValue()); - - // Set ObjectData of the starting folder for ObjectInfo - ObjectData _folder = compileService.compileObjectData( - callContext, repositoryId, folder, filter, - includeAllowableActions, includeRelationships, renditionFilter, false); - anscestorObjectData.setValue(_folder); - - // get the tree. - return getDescendantsInternal(callContext, repositoryId, _folder, filter, iaa, - false, includeRelationships, null, ips, 0, d, foldersOnly); + + Lock parentLock = threadLockService.getReadLock(repositoryId, folderId); + + try{ + parentLock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Folder folder = contentService.getFolder(repositoryId, folderId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_GET_DESCENDENTS_FOLDER, folder); + + // ////////////////// + // Specific Exception + // ////////////////// + exceptionService.invalidArgumentFolderId(folder, folderId); + exceptionService.invalidArgumentDepth(depth); + + // ////////////////// + // Body of the method + // ////////////////// + // check depth + int d = (depth == null ? 2 : depth.intValue()); + + // set defaults if values not set + boolean iaa = (includeAllowableActions == null ? false + : includeAllowableActions.booleanValue()); + boolean ips = (includePathSegment == null ? false : includePathSegment + .booleanValue()); + + // Set ObjectData of the starting folder for ObjectInfo + ObjectData _folder = compileService.compileObjectData( + callContext, repositoryId, folder, filter, + includeAllowableActions, includeRelationships, renditionFilter, false); + anscestorObjectData.setValue(_folder); + + // get the tree. + return getDescendantsInternal(callContext, repositoryId, _folder, filter, iaa, + false, includeRelationships, null, ips, 0, d, foldersOnly); + + }finally{ + parentLock.unlock(); + } } private List getDescendantsInternal( @@ -234,27 +258,46 @@ private List getDescendantsInternal( @Override public ObjectData getFolderParent(CallContext callContext, String repositoryId, String folderId, String filter) { - // ////////////////// - // General Exception - // ////////////////// + exceptionService.invalidArgumentRequiredString("folderId", folderId); - Folder folder = (Folder) contentService.getContent(repositoryId, folderId); - exceptionService.objectNotFound(DomainType.OBJECT, folder, folderId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT, folder); - - // ////////////////// - // Specific Exception - // ////////////////// - Folder parent = contentService.getParent(repositoryId, folderId); - exceptionService.objectNotFoundParentFolder(repositoryId, folderId, parent); - exceptionService.invalidArgumentRootFolder(repositoryId, folder); - - // ////////////////// - // Body of the method - // ////////////////// - return compileService.compileObjectData(callContext, repositoryId, - parent, filter, true, IncludeRelationships.NONE, null, true); + + Lock childLock = threadLockService.getReadLock(repositoryId, folderId); + + try{ + childLock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Folder folder = (Folder) contentService.getContent(repositoryId, folderId); + exceptionService.objectNotFound(DomainType.OBJECT, folder, folderId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT, folder); + + // ////////////////// + // Specific Exception + // ////////////////// + Folder parent = contentService.getParent(repositoryId, folderId); + + Lock parentLock = threadLockService.getReadLock(repositoryId, parent.getId()); + try{ + parentLock.lock(); + + exceptionService.objectNotFoundParentFolder(repositoryId, folderId, parent); + exceptionService.invalidArgumentRootFolder(repositoryId, folder); + + // ////////////////// + // Body of the method + // ////////////////// + return compileService.compileObjectData(callContext, repositoryId, + parent, filter, true, IncludeRelationships.NONE, null, true); + + }finally{ + parentLock.unlock(); + } + }finally{ + childLock.unlock(); + } } @Override @@ -262,37 +305,59 @@ public List getObjectParents(CallContext callContext, String repositoryId, String objectId, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includeRelativePathSegment, ExtensionsData extension) { - // ////////////////// - // General Exception - // ////////////////// - exceptionService.invalidArgumentRequired("objectId", objectId); - Content content = contentService.getContent(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_GET_PARENTS_FOLDER, content); - // ////////////////// - // Specific Exception - // ////////////////// - Folder parent = contentService.getParent(repositoryId, objectId); - exceptionService.objectNotFoundParentFolder(repositoryId, objectId, parent); - exceptionService.invalidArgumentRootFolder(repositoryId, content); + exceptionService.invalidArgumentRequired("objectId", objectId); + + Lock childLock = threadLockService.getReadLock(repositoryId, objectId); + + try{ + childLock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Content content = contentService.getContent(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_GET_PARENTS_FOLDER, content); + + + //Get parent + Folder parent = contentService.getParent(repositoryId, objectId); + Lock parentLock = threadLockService.getReadLock(repositoryId, parent.getId()); + + try{ + parentLock.lock(); + + // ////////////////// + // Specific Exception + // ////////////////// + exceptionService.objectNotFoundParentFolder(repositoryId, objectId, parent); + exceptionService.invalidArgumentRootFolder(repositoryId, content); + + // ////////////////// + // Body of the method + // ////////////////// + ObjectParentDataImpl result = new ObjectParentDataImpl(); + ObjectData o = compileService.compileObjectData(callContext, + repositoryId, parent, filter, includeAllowableActions, + includeRelationships, null, true); + result.setObject(o); + boolean irps = (includeRelativePathSegment == null ? false + : includeRelativePathSegment.booleanValue()); + if (irps) { + result.setRelativePathSegment(content.getName()); + } - // ////////////////// - // Body of the method - // ////////////////// - ObjectParentDataImpl result = new ObjectParentDataImpl(); - ObjectData o = compileService.compileObjectData(callContext, - repositoryId, parent, filter, includeAllowableActions, - includeRelationships, null, true); - result.setObject(o); - boolean irps = (includeRelativePathSegment == null ? false - : includeRelativePathSegment.booleanValue()); - if (irps) { - result.setRelativePathSegment(content.getName()); + return Collections.singletonList((ObjectParentData) result); + + }finally{ + parentLock.unlock(); + } + + }finally{ + childLock.unlock(); } - - return Collections.singletonList((ObjectParentData) result); } @Override @@ -322,14 +387,22 @@ public ObjectList getCheckedOutDocs(CallContext callContext, //Folder ID can be null, which means all PWCs are returned. List checkedOuts = contentService.getCheckedOutDocs(repositoryId, folderId, orderBy, extension); - - ObjectList list = compileService.compileObjectDataList( - callContext, repositoryId, checkedOuts, filter, - includeAllowableActions, includeRelationships, renditionFilter, false, - maxItems, skipCount, false); - sortUtil.sort(repositoryId, list.getObjects(), orderBy); - - return list; + + List locks = threadLockService.readLocks(repositoryId, checkedOuts); + + try{ + threadLockService.bulkLock(locks); + + ObjectList list = compileService.compileObjectDataList( + callContext, repositoryId, checkedOuts, filter, + includeAllowableActions, includeRelationships, renditionFilter, false, + maxItems, skipCount, false, orderBy); + + return list; + + }finally{ + threadLockService.bulkUnlock(locks); + } } public void setContentService(ContentService contentService) { @@ -348,9 +421,7 @@ public void setPermissionService(PermissionService permissionService) { this.permissionService = permissionService; } - - public void setSortUtil(SortUtil sortUtil) { - this.sortUtil = sortUtil; + public void setThreadLockService(ThreadLockService threadLockService) { + this.threadLockService = threadLockService; } - } diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/ObjectServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/ObjectServiceImpl.java index 2c21255a..d8fe7360 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/ObjectServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/ObjectServiceImpl.java @@ -34,6 +34,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.Acl; @@ -82,9 +84,9 @@ import jp.aegif.nemaki.model.Rendition; import jp.aegif.nemaki.model.VersionSeries; import jp.aegif.nemaki.util.DataUtil; -import jp.aegif.nemaki.util.PropertyManager; import jp.aegif.nemaki.util.cache.NemakiCachePool; import jp.aegif.nemaki.util.constant.DomainType; +import jp.aegif.nemaki.util.lock.ThreadLockService; public class ObjectServiceImpl implements ObjectService { @@ -98,6 +100,7 @@ public class ObjectServiceImpl implements ObjectService { private CompileService compileService; private SolrUtil solrUtil; private NemakiCachePool nemakiCachePool; + private ThreadLockService threadLockService; private int threadMax; @Override @@ -112,17 +115,25 @@ public ObjectData getObjectByPath(CallContext callContext, String repositoryId, exceptionService.invalidArgumentRequired("objectId", path); // FIXME path is not preserved in db. Content content = contentService.getContentByPath(repositoryId, path); - // TODO create objectNotFoundByPath method - exceptionService.objectNotFound(DomainType.OBJECT, content, path); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content); - // ////////////////// - // Body of the method - // ////////////////// - return compileService.compileObjectData(callContext, repositoryId, - content, filter, includeAllowableActions, - includeRelationships, renditionFilter, includeAcl); + Lock lock = threadLockService.getReadLock(repositoryId, content.getId()); + try{ + lock.lock(); + + // TODO create objectNotFoundByPath method + exceptionService.objectNotFound(DomainType.OBJECT, content, path); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content); + + // ////////////////// + // Body of the method + // ////////////////// + return compileService.compileObjectData(callContext, repositoryId, + content, filter, includeAllowableActions, + includeRelationships, renditionFilter, includeAcl); + }finally{ + lock.unlock(); + } } @Override @@ -131,54 +142,72 @@ public ObjectData getObject(CallContext callContext, String repositoryId, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) { - // ////////////////// - // General Exception - // ////////////////// + exceptionService.invalidArgumentRequired("objectId", objectId); - Content content = contentService.getContent(repositoryId, objectId); - // WORK AROUND: getObject(versionSeriesId) is interpreted as - // getDocumentOflatestVersion - if (content == null) { - VersionSeries versionSeries = contentService - .getVersionSeries(repositoryId, objectId); - if (versionSeries != null) { - content = contentService.getDocumentOfLatestVersion(repositoryId, objectId); + + Lock lock = threadLockService.getReadLock(repositoryId, objectId); + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Content content = contentService.getContent(repositoryId, objectId); + // WORK AROUND: getObject(versionSeriesId) is interpreted as + // getDocumentOflatestVersion + if (content == null) { + VersionSeries versionSeries = contentService + .getVersionSeries(repositoryId, objectId); + if (versionSeries != null) { + content = contentService.getDocumentOfLatestVersion(repositoryId, objectId); + } } + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content); + + // ////////////////// + // Body of the method + // ////////////////// + ObjectData object = compileService.compileObjectData(callContext, + repositoryId, content, filter, includeAllowableActions, + includeRelationships, null, includeAcl); + + return object; + }finally{ + lock.unlock(); } - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content); - - // ////////////////// - // Body of the method - // ////////////////// - ObjectData object = compileService.compileObjectData(callContext, - repositoryId, content, filter, includeAllowableActions, - includeRelationships, null, includeAcl); - - return object; } @Override public ContentStream getContentStream(CallContext callContext, String repositoryId, String objectId, String streamId, BigInteger offset, BigInteger length) { - // ////////////////// - // General Exception - // ////////////////// - exceptionService.invalidArgumentRequired("objectId", objectId); - Content content = contentService.getContent(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content); - // ////////////////// - // Body of the method - // ////////////////// - if (streamId == null) { - return getContentStreamInternal(repositoryId, content, offset, length); - } else { - return getRenditionStream(repositoryId, content, streamId); + exceptionService.invalidArgumentRequired("objectId", objectId); + + Lock lock = threadLockService.getReadLock(repositoryId, objectId); + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Content content = contentService.getContent(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content); + + // ////////////////// + // Body of the method + // ////////////////// + if (streamId == null) { + return getContentStreamInternal(repositoryId, content, offset, length); + } else { + return getRenditionStream(repositoryId, content, streamId); + } + }finally{ + lock.unlock(); } } @@ -231,37 +260,56 @@ private ContentStream getRenditionStream(String repositoryId, Content content, S public List getRenditions(CallContext callContext, String repositoryId, String objectId, String renditionFilter, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) { - List renditions = contentService.getRenditions(repositoryId, objectId); - - List results = new ArrayList(); - for (Rendition rnd : renditions) { - RenditionDataImpl data = new RenditionDataImpl(rnd.getId(), - rnd.getMimetype(), BigInteger.valueOf(rnd.getLength()), - rnd.getKind(), rnd.getTitle(), BigInteger.valueOf(rnd - .getWidth()), BigInteger.valueOf(rnd.getHeight()), - rnd.getRenditionDocumentId()); - results.add(data); + + Lock lock = threadLockService.getReadLock(repositoryId, objectId); + try{ + lock.lock(); + + List renditions = contentService.getRenditions(repositoryId, objectId); + + List results = new ArrayList(); + for (Rendition rnd : renditions) { + RenditionDataImpl data = new RenditionDataImpl(rnd.getId(), + rnd.getMimetype(), BigInteger.valueOf(rnd.getLength()), + rnd.getKind(), rnd.getTitle(), BigInteger.valueOf(rnd + .getWidth()), BigInteger.valueOf(rnd.getHeight()), + rnd.getRenditionDocumentId()); + results.add(data); + } + return results; + }finally{ + lock.unlock(); } - return results; } @Override public AllowableActions getAllowableActions(CallContext callContext, String repositoryId, String objectId) { - // ////////////////// - // General Exception - // ////////////////// - exceptionService.invalidArgumentRequired("objectId", objectId); - Content content = contentService.getContent(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - // NOTE: The permission key doesn't exist according to CMIS - // specification. - // ////////////////// - // Body of the method - // ////////////////// - return compileService.compileAllowableActions(callContext, - repositoryId, content); + exceptionService.invalidArgumentRequired("objectId", objectId); + + Lock lock = threadLockService.getReadLock(repositoryId, objectId); + + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Content content = contentService.getContent(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + // NOTE: The permission key doesn't exist according to CMIS + // specification. + + // ////////////////// + // Body of the method + // ////////////////// + return compileService.compileAllowableActions(callContext, + repositoryId, content); + + }finally{ + lock.unlock(); + } } @Override @@ -448,71 +496,88 @@ public String createDocumentFromSource(CallContext callContext, public void setContentStream(CallContext callContext, String repositoryId, Holder objectId, boolean overwriteFlag, ContentStream contentStream, Holder changeToken) { - // ////////////////// - // General Exception - // ////////////////// - String id = objectId.getValue(); - - exceptionService.invalidArgumentRequiredString("objectId", id); - exceptionService - .invalidArgumentRequired("contentStream", contentStream); - Document doc = (Document) contentService.getContent(repositoryId, id); - exceptionService.objectNotFound(DomainType.OBJECT, doc, id); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_SET_CONTENT_DOCUMENT, doc); - DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager - .getTypeDefinition(repositoryId, doc.getObjectType()); - exceptionService.constraintImmutable(repositoryId, doc, td); - - // ////////////////// - // Specific Exception - // ////////////////// - exceptionService.contentAlreadyExists(doc, overwriteFlag); - exceptionService.streamNotSupported(td, contentStream); - exceptionService.updateConflict(doc, changeToken); - exceptionService.versioning(doc); - Folder parent = contentService.getParent(repositoryId, id); - exceptionService.objectNotFoundParentFolder(repositoryId, id, parent); + + exceptionService.invalidArgumentRequiredHolderString("objectId", objectId); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); + try{ + lock.lock(); + // ////////////////// + // General Exception + // ////////////////// + + exceptionService + .invalidArgumentRequired("contentStream", contentStream); + Document doc = (Document) contentService.getContent(repositoryId, objectId.getValue()); + exceptionService.objectNotFound(DomainType.OBJECT, doc, objectId.getValue()); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_SET_CONTENT_DOCUMENT, doc); + DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager + .getTypeDefinition(repositoryId, doc.getObjectType()); + exceptionService.constraintImmutable(repositoryId, doc, td); + + // ////////////////// + // Specific Exception + // ////////////////// + exceptionService.contentAlreadyExists(doc, overwriteFlag); + exceptionService.streamNotSupported(td, contentStream); + exceptionService.updateConflict(doc, changeToken); + exceptionService.versioning(doc); + Folder parent = contentService.getParent(repositoryId, objectId.getValue()); + exceptionService.objectNotFoundParentFolder(repositoryId, objectId.getValue(), parent); + + // ////////////////// + // Body of the method + // ////////////////// + String oldId = objectId.getValue(); + + // TODO Externalize versioningState + if(doc.isPrivateWorkingCopy()){ + Document result = contentService.replacePwc(callContext, repositoryId, + doc, contentStream); + objectId.setValue(result.getId()); + }else{ + Document result = contentService.createDocumentWithNewStream(callContext, repositoryId, + doc, contentStream); + objectId.setValue(result.getId()); + } - // ////////////////// - // Body of the method - // ////////////////// - String oldId = objectId.getValue(); - - // TODO Externalize versioningState - if(doc.isPrivateWorkingCopy()){ - Document result = contentService.replacePwc(callContext, repositoryId, - doc, contentStream); - objectId.setValue(result.getId()); - }else{ - Document result = contentService.createDocumentWithNewStream(callContext, repositoryId, - doc, contentStream); - objectId.setValue(result.getId()); + nemakiCachePool.get(repositoryId).removeCmisCache(oldId); + }finally{ + lock.unlock(); } - - nemakiCachePool.get(repositoryId).removeCmisCache(oldId); } @Override public void deleteContentStream(CallContext callContext, String repositoryId, Holder objectId, Holder changeToken, ExtensionsData extension) { - // ////////////////// - // Exception - // ////////////////// + exceptionService.invalidArgumentRequiredHolderString("objectId", objectId); - Document document = contentService.getDocument(repositoryId, objectId.getValue()); - exceptionService.objectNotFound(DomainType.OBJECT, document, - document.getId()); - exceptionService.constraintContentStreamRequired(repositoryId, document); - - // ////////////////// - // Body of the method - // ////////////////// - contentService.deleteContentStream(callContext, repositoryId, objectId); - - nemakiCachePool.get(repositoryId).removeCmisCache(objectId.getValue()); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); + try{ + lock.lock(); + + // ////////////////// + // Exception + // ////////////////// + Document document = contentService.getDocument(repositoryId, objectId.getValue()); + exceptionService.objectNotFound(DomainType.OBJECT, document, + document.getId()); + exceptionService.constraintContentStreamRequired(repositoryId, document); + + // ////////////////// + // Body of the method + // ////////////////// + contentService.deleteContentStream(callContext, repositoryId, objectId); + + nemakiCachePool.get(repositoryId).removeCmisCache(objectId.getValue()); + + }finally{ + lock.unlock(); + } } @Override @@ -520,36 +585,44 @@ public void appendContentStream(CallContext callContext, String repositoryId, Holder objectId, Holder changeToken, ContentStream contentStream, boolean isLastChunk, ExtensionsData extension) { - // ////////////////// - // General Exception - // ////////////////// - String id = objectId.getValue(); - - exceptionService.invalidArgumentRequiredString("objectId", id); - exceptionService - .invalidArgumentRequired("contentStream", contentStream); - Document doc = (Document) contentService.getContent(repositoryId, id); - exceptionService.objectNotFound(DomainType.OBJECT, doc, id); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_SET_CONTENT_DOCUMENT, doc); - DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager - .getTypeDefinition(repositoryId, doc.getObjectType()); - exceptionService.constraintImmutable(repositoryId, doc, td); - - // ////////////////// - // Specific Exception - // ////////////////// - exceptionService.streamNotSupported(td, contentStream); - exceptionService.updateConflict(doc, changeToken); - exceptionService.versioning(doc); - - // ////////////////// - // Body of the method - // ////////////////// - contentService.appendAttachment(callContext, repositoryId, objectId, - changeToken, contentStream, isLastChunk, extension); - - nemakiCachePool.get(repositoryId).removeCmisCache(objectId.getValue()); + + exceptionService.invalidArgumentRequiredHolderString("objectId", objectId); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + + exceptionService + .invalidArgumentRequired("contentStream", contentStream); + Document doc = (Document) contentService.getContent(repositoryId, objectId.getValue()); + exceptionService.objectNotFound(DomainType.OBJECT, doc, objectId.getValue()); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_SET_CONTENT_DOCUMENT, doc); + DocumentTypeDefinition td = (DocumentTypeDefinition) typeManager + .getTypeDefinition(repositoryId, doc.getObjectType()); + exceptionService.constraintImmutable(repositoryId, doc, td); + + // ////////////////// + // Specific Exception + // ////////////////// + exceptionService.streamNotSupported(td, contentStream); + exceptionService.updateConflict(doc, changeToken); + exceptionService.versioning(doc); + + // ////////////////// + // Body of the method + // ////////////////// + contentService.appendAttachment(callContext, repositoryId, objectId, + changeToken, contentStream, isLastChunk, extension); + + nemakiCachePool.get(repositoryId).removeCmisCache(objectId.getValue()); + }finally{ + lock.unlock(); + } } @Override @@ -683,21 +756,29 @@ public String createItem(CallContext callContext, String repositoryId, public void updateProperties(CallContext callContext, String repositoryId, Holder objectId, Properties properties, Holder changeToken) { - - // ////////////////// - // Exception - // ////////////////// - Content content = checkExceptionBeforeUpdateProperties(callContext, - repositoryId, objectId, properties, changeToken); - - // ////////////////// - // Body of the method - // ////////////////// - String id = objectId.getValue(); - - contentService.updateProperties(callContext, repositoryId, properties, content); - - nemakiCachePool.get(repositoryId).removeCmisCache(id); + + exceptionService.invalidArgumentRequiredHolderString("objectId", + objectId); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); + try{ + lock.lock(); + + // ////////////////// + // Exception + // ////////////////// + Content content = checkExceptionBeforeUpdateProperties(callContext, + repositoryId, objectId, properties, changeToken); + + // ////////////////// + // Body of the method + // ////////////////// + contentService.updateProperties(callContext, repositoryId, properties, content); + + nemakiCachePool.get(repositoryId).removeCmisCache(objectId.getValue()); + }finally{ + lock.unlock(); + } } private Content checkExceptionBeforeUpdateProperties( @@ -706,8 +787,6 @@ private Content checkExceptionBeforeUpdateProperties( // ////////////////// // General Exception // ////////////////// - exceptionService.invalidArgumentRequiredHolderString("objectId", - objectId); exceptionService.invalidArgumentRequiredCollection("properties", properties.getPropertyList()); Content content = contentService.getContent(repositoryId, objectId.getValue()); @@ -737,14 +816,14 @@ private Content checkExceptionBeforeUpdateProperties( public List bulkUpdateProperties( CallContext callContext, String repositoryId, - List objectIdAndChangeToken, Properties properties, + List objectIdAndChangeTokenList, Properties properties, List addSecondaryTypeIds, List removeSecondaryTypeIds, ExtensionsData extension) { // ////////////////// // General Exception // ////////////////// // Each permission is checked at each execution exceptionService.invalidArgumentRequiredCollection( - "objectIdAndChangeToken", objectIdAndChangeToken); + "objectIdAndChangeToken", objectIdAndChangeTokenList); exceptionService.invalidArgumentSecondaryTypeIds(repositoryId, properties); // ////////////////// @@ -752,67 +831,132 @@ public List bulkUpdateProperties( // ////////////////// List results = new ArrayList(); - for (BulkUpdateObjectIdAndChangeToken idAndToken : objectIdAndChangeToken) { + ExecutorService executor = Executors.newCachedThreadPool(); + List tasks = new ArrayList<>(); + for (BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken : objectIdAndChangeTokenList) { + tasks.add(new BulkUpdateTask(callContext, repositoryId, objectIdAndChangeToken, properties, addSecondaryTypeIds, removeSecondaryTypeIds, extension)); + } + + try { + List> _results = executor.invokeAll(tasks); + for(Future _result : _results){ + try{ + BulkUpdateObjectIdAndChangeToken result = _result.get(); + results.add(result); + }catch(Exception e){ + //TODO log + //do nothing + } + } + } catch (InterruptedException e1) { + //TODO log + e1.printStackTrace(); + } + + return results; + } + + private class BulkUpdateTask implements Callable{ + + private CallContext callContext; + private String repositoryId; + private BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken; + private Properties properties; + private List addSecondaryTypeIds; + private List removeSecondaryTypeIds; + private ExtensionsData extension; + + public BulkUpdateTask(CallContext callContext, String repositoryId, BulkUpdateObjectIdAndChangeToken objectIdAndChangeToken, + Properties properties, List addSecondaryTypeIds, List removeSecondaryTypeIds, + ExtensionsData extension) { + super(); + this.callContext = callContext; + this.repositoryId = repositoryId; + this.objectIdAndChangeToken = objectIdAndChangeToken; + this.properties = properties; + this.addSecondaryTypeIds = addSecondaryTypeIds; + this.removeSecondaryTypeIds = removeSecondaryTypeIds; + this.extension = extension; + } + + @Override + public BulkUpdateObjectIdAndChangeToken call() throws Exception { + exceptionService.invalidArgumentRequiredString("objectId", + objectIdAndChangeToken.getId()); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectIdAndChangeToken.getId()); try { + lock.lock(); + Content content = checkExceptionBeforeUpdateProperties( callContext, repositoryId, - new Holder(idAndToken.getId()), - properties, new Holder(idAndToken.getChangeToken())); + new Holder(objectIdAndChangeToken.getId()), + properties, new Holder(objectIdAndChangeToken.getChangeToken())); contentService.updateProperties(callContext, repositoryId, properties, content); nemakiCachePool.get(repositoryId).removeCmisCache(content.getId()); BulkUpdateObjectIdAndChangeToken result = new BulkUpdateObjectIdAndChangeTokenImpl( - idAndToken.getId(), content.getId(), + objectIdAndChangeToken.getId(), content.getId(), String.valueOf(content.getChangeToken())); - results.add(result); + return result; } catch (Exception e) { // Don't throw an error // Don't return any BulkUpdateObjectIdAndChangetoken + }finally{ + lock.unlock(); } + + // TODO Auto-generated method stub + return null; } - - return results; + } - + @Override public void moveObject(CallContext callContext, String repositoryId, Holder objectId, String sourceFolderId, String targetFolderId) { - // ////////////////// - // General Exception - // ////////////////// + exceptionService.invalidArgumentRequiredHolderString("objectId", objectId); - exceptionService.invalidArgumentRequiredString("sourceFolderId", - sourceFolderId); - exceptionService.invalidArgumentRequiredString("targetFolderId", - targetFolderId); - Content content = contentService.getContent(repositoryId, objectId.getValue()); - exceptionService.objectNotFound(DomainType.OBJECT, content, - objectId.getValue()); - Folder source = contentService.getFolder(repositoryId, sourceFolderId); - exceptionService.objectNotFound(DomainType.OBJECT, source, - sourceFolderId); - Folder target = contentService.getFolder(repositoryId, targetFolderId); - exceptionService.objectNotFound(DomainType.OBJECT, target, - targetFolderId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_MOVE_OBJECT, content); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_MOVE_SOURCE, source); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_MOVE_TARGET, target); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); + try{ + lock.lock(); + // ////////////////// + // General Exception + // ////////////////// + exceptionService.invalidArgumentRequiredString("sourceFolderId", + sourceFolderId); + exceptionService.invalidArgumentRequiredString("targetFolderId", + targetFolderId); + Content content = contentService.getContent(repositoryId, objectId.getValue()); + exceptionService.objectNotFound(DomainType.OBJECT, content, + objectId.getValue()); + Folder source = contentService.getFolder(repositoryId, sourceFolderId); + exceptionService.objectNotFound(DomainType.OBJECT, source, + sourceFolderId); + Folder target = contentService.getFolder(repositoryId, targetFolderId); + exceptionService.objectNotFound(DomainType.OBJECT, target, + targetFolderId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_MOVE_OBJECT, content); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_MOVE_SOURCE, source); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_MOVE_TARGET, target); - // ////////////////// - // Body of the method - // ////////////////// - contentService.move(repositoryId, content, target); + // ////////////////// + // Body of the method + // ////////////////// + contentService.move(repositoryId, content, target); - nemakiCachePool.get(repositoryId).removeCmisCache(content.getId()); + nemakiCachePool.get(repositoryId).removeCmisCache(content.getId()); + }finally{ + lock.unlock(); + } } - - @Override public void deleteObject(CallContext callContext, String repositoryId, String objectId, Boolean allVersions) { @@ -1002,6 +1146,10 @@ public void setNemakiCachePool(NemakiCachePool nemakiCachePool) { this.nemakiCachePool = nemakiCachePool; } + public void setThreadLockService(ThreadLockService threadLockService) { + this.threadLockService = threadLockService; + } + public void setThreadMax(int threadMax) { this.threadMax = threadMax; } diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/ObjectServiceInternalImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/ObjectServiceInternalImpl.java index b316b973..155b0c67 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/ObjectServiceInternalImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/ObjectServiceInternalImpl.java @@ -1,6 +1,7 @@ package jp.aegif.nemaki.cmis.service.impl; import java.util.List; +import java.util.concurrent.locks.Lock; import org.apache.chemistry.opencmis.commons.data.PermissionMapping; import org.apache.chemistry.opencmis.commons.server.CallContext; @@ -13,6 +14,7 @@ import jp.aegif.nemaki.model.Content; import jp.aegif.nemaki.util.cache.NemakiCachePool; import jp.aegif.nemaki.util.constant.DomainType; +import jp.aegif.nemaki.util.lock.ThreadLockService; public class ObjectServiceInternalImpl implements jp.aegif.nemaki.cmis.service.ObjectServiceInternal{ private static final Log log = LogFactory @@ -20,6 +22,7 @@ public class ObjectServiceInternalImpl implements jp.aegif.nemaki.cmis.service.O private ContentService contentService; private ExceptionService exceptionService; + private ThreadLockService threadLockService; private NemakiCachePool nemakiCachePool; @Override @@ -33,47 +36,59 @@ public void deleteObjectInternal(CallContext callContext, String repositoryId, @Override public void deleteObjectInternal(CallContext callContext, String repositoryId, Content content, Boolean allVersions, Boolean deleteWithParent) { - // ////////////////// - // General Exception - // ////////////////// - String objectId = content.getId(); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_DELETE_OBJECT, content); - exceptionService.constraintDeleteRootFolder(repositoryId, objectId); + + Lock lock = threadLockService.getWriteLock(repositoryId, content.getId()); + + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + String objectId = content.getId(); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_DELETE_OBJECT, content); + exceptionService.constraintDeleteRootFolder(repositoryId, objectId); - // ////////////////// - // Body of the method - // ////////////////// - if (content.isDocument()) { - contentService.deleteDocument(callContext, repositoryId, - content.getId(), allVersions, deleteWithParent); - } else if (content.isFolder()) { - List children = contentService.getChildren(repositoryId, objectId); - if (!CollectionUtils.isEmpty(children)) { - exceptionService - .constraint(objectId, - "deleteObject method is invoked on a folder containing objects."); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + + // ////////////////// + // Body of the method + // ////////////////// + if (content.isDocument()) { + contentService.deleteDocument(callContext, repositoryId, + content.getId(), allVersions, deleteWithParent); + } else if (content.isFolder()) { + List children = contentService.getChildren(repositoryId, objectId); + if (!CollectionUtils.isEmpty(children)) { + exceptionService + .constraint(objectId, + "deleteObject method is invoked on a folder containing objects."); + } + contentService.delete(callContext, repositoryId, objectId, deleteWithParent); + + } else { + contentService.delete(callContext, repositoryId, objectId, deleteWithParent); } - contentService.delete(callContext, repositoryId, objectId, deleteWithParent); - } else { - contentService.delete(callContext, repositoryId, objectId, deleteWithParent); + nemakiCachePool.get(repositoryId).removeCmisCache(content.getId()); + }finally{ + lock.unlock(); } - - nemakiCachePool.get(repositoryId).removeCmisCache(content.getId()); } - public void setContentService(ContentService contentService) { this.contentService = contentService; } - public void setExceptionService(ExceptionService exceptionService) { this.exceptionService = exceptionService; } + public void setThreadLockService(ThreadLockService threadLockService) { + this.threadLockService = threadLockService; + } + public void setNemakiCachePool(NemakiCachePool nemakiCachePool) { this.nemakiCachePool = nemakiCachePool; } diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/PolicyServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/PolicyServiceImpl.java index 4ac35e8d..7269ce05 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/PolicyServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/PolicyServiceImpl.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.locks.Lock; import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.cmis.aspect.CompileService; @@ -31,9 +32,9 @@ import jp.aegif.nemaki.cmis.service.PolicyService; import jp.aegif.nemaki.model.Content; import jp.aegif.nemaki.model.Policy; -import jp.aegif.nemaki.util.cache.NemakiCache; import jp.aegif.nemaki.util.cache.NemakiCachePool; import jp.aegif.nemaki.util.constant.DomainType; +import jp.aegif.nemaki.util.lock.ThreadLockService; import org.apache.chemistry.opencmis.commons.data.ExtensionsData; import org.apache.chemistry.opencmis.commons.data.ObjectData; @@ -49,65 +50,91 @@ public class PolicyServiceImpl implements PolicyService { private CompileService compileService; private ExceptionService exceptionService; private TypeManager typeManager; + private ThreadLockService threadLockService; private NemakiCachePool nemakiCachePool; @Override public void applyPolicy(CallContext callContext, String repositoryId, String policyId, String objectId, ExtensionsData extension) { - // ////////////////// - // General Exception - // ////////////////// exceptionService.invalidArgumentRequiredString("objectId", objectId); exceptionService.invalidArgumentRequiredString("policyId", policyId); - Content content = contentService.getContent(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_ADD_POLICY_OBJECT, content); - Policy policy = contentService.getPolicy(repositoryId, policyId); - exceptionService.objectNotFound(DomainType.OBJECT, policy, policyId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_ADD_POLICY_POLICY, policy); - - // ////////////////// - // Specific Exception - // ////////////////// - TypeDefinition td = typeManager.getTypeDefinition(repositoryId, content); - if (!td.isControllablePolicy()) - exceptionService - .constraint(objectId, - "appyPolicy cannot be performed on the object whose controllablePolicy = false"); - - // ////////////////// - // Body of the method - // ////////////////// - contentService.applyPolicy(callContext, repositoryId, policyId, objectId, extension); - nemakiCachePool.get(repositoryId).removeCmisCache(objectId); + Lock objectLock = threadLockService.getWriteLock(repositoryId, objectId); + Lock policyLock = threadLockService.getReadLock(repositoryId, policyId); + try{ + objectLock.lock(); + policyLock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Content content = contentService.getContent(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_ADD_POLICY_OBJECT, content); + Policy policy = contentService.getPolicy(repositoryId, policyId); + exceptionService.objectNotFound(DomainType.OBJECT, policy, policyId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_ADD_POLICY_POLICY, policy); + + // ////////////////// + // Specific Exception + // ////////////////// + TypeDefinition td = typeManager.getTypeDefinition(repositoryId, content); + if (!td.isControllablePolicy()) + exceptionService + .constraint(objectId, + "appyPolicy cannot be performed on the object whose controllablePolicy = false"); + + // ////////////////// + // Body of the method + // ////////////////// + contentService.applyPolicy(callContext, repositoryId, policyId, objectId, extension); + + nemakiCachePool.get(repositoryId).removeCmisCache(objectId); + + }finally{ + objectLock.unlock(); + policyLock.unlock(); + } } @Override public void removePolicy(CallContext callContext, String repositoryId, String policyId, String objectId, ExtensionsData extension) { - // ////////////////// - // General Exception - // ////////////////// + exceptionService.invalidArgumentRequiredString("objectId", objectId); exceptionService.invalidArgumentRequiredString("policyId", policyId); - Content content = contentService.getContent(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_REMOVE_POLICY_OBJECT, content); - Policy policy = contentService.getPolicy(repositoryId, policyId); - exceptionService.objectNotFound(DomainType.OBJECT, policy, policyId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_REMOVE_POLICY_POLICY, policy); - // ////////////////// - // Body of the method - // ////////////////// - contentService.removePolicy(callContext, repositoryId, policyId, objectId, extension); - - nemakiCachePool.get(repositoryId).removeCmisCache(objectId); + Lock objectLock = threadLockService.getWriteLock(repositoryId, objectId); + Lock policyLock = threadLockService.getReadLock(repositoryId, policyId); + try{ + objectLock.lock(); + policyLock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Content content = contentService.getContent(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_REMOVE_POLICY_OBJECT, content); + Policy policy = contentService.getPolicy(repositoryId, policyId); + exceptionService.objectNotFound(DomainType.OBJECT, policy, policyId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_REMOVE_POLICY_POLICY, policy); + + // ////////////////// + // Body of the method + // ////////////////// + contentService.removePolicy(callContext, repositoryId, policyId, objectId, extension); + + nemakiCachePool.get(repositoryId).removeCmisCache(objectId); + + }finally{ + objectLock.unlock(); + policyLock.unlock(); + } } @Override @@ -127,15 +154,24 @@ public List getAppliedPolicies(CallContext callContext, // ////////////////// List policies = contentService.getAppliedPolicies(repositoryId, objectId, extension); - List objects = new ArrayList(); - if (!CollectionUtils.isEmpty(policies)) { - for (Policy policy : policies) { - objects.add(compileService.compileObjectData(callContext, - repositoryId, policy, filter, true, IncludeRelationships.NONE, - null, true)); + + List locks = threadLockService.readLocks(repositoryId, policies); + try{ + threadLockService.bulkLock(locks); + + List objects = new ArrayList(); + if (!CollectionUtils.isEmpty(policies)) { + for (Policy policy : policies) { + objects.add(compileService.compileObjectData(callContext, + repositoryId, policy, filter, true, IncludeRelationships.NONE, + null, true)); + } } + return objects; + + }finally{ + threadLockService.bulkUnlock(locks); } - return objects; } public void setContentService(ContentService contentService) { @@ -158,6 +194,10 @@ public void setTypeManager(TypeManager typeManager) { this.typeManager = typeManager; } + public void setThreadLockService(ThreadLockService threadLockService) { + this.threadLockService = threadLockService; + } + public void setNemakiCachePool(NemakiCachePool nemakiCachePool) { this.nemakiCachePool = nemakiCachePool; } diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/RelationshipServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/RelationshipServiceImpl.java index 4e55b398..368a24a8 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/RelationshipServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/RelationshipServiceImpl.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.locks.Lock; import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.cmis.aspect.CompileService; @@ -35,6 +36,7 @@ import jp.aegif.nemaki.model.Content; import jp.aegif.nemaki.model.Relationship; import jp.aegif.nemaki.util.constant.DomainType; +import jp.aegif.nemaki.util.lock.ThreadLockService; import org.apache.chemistry.opencmis.commons.data.ExtensionsData; import org.apache.chemistry.opencmis.commons.data.ObjectList; @@ -49,6 +51,7 @@ public class RelationshipServiceImpl implements RelationshipService { private ContentService contentService; private CompileService compileService; private ExceptionService exceptionService; + private ThreadLockService threadLockService; @Override public ObjectList getObjectRelationships(CallContext callContext, @@ -56,53 +59,63 @@ public ObjectList getObjectRelationships(CallContext callContext, Boolean includeSubRelationshipTypes, RelationshipDirection relationshipDirection, String typeId, String filter, Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) { - // ////////////////// - // General Exception - // ////////////////// + exceptionService.invalidArgumentRequiredString("objectId", objectId); - Content content = contentService.getContent(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, content); + + Lock lock = threadLockService.getReadLock(repositoryId, objectId); + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + + Content content = contentService.getContent(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, content, objectId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT, content); - // ////////////////// - // Body of the method - // ////////////////// - // Set default - relationshipDirection = (relationshipDirection == null) ? RelationshipDirection.SOURCE - : relationshipDirection; + // ////////////////// + // Body of the method + // ////////////////// + // Set default + relationshipDirection = (relationshipDirection == null) ? RelationshipDirection.SOURCE + : relationshipDirection; - List rels = contentService.getRelationsipsOfObject( - repositoryId, objectId, relationshipDirection); + List rels = contentService.getRelationsipsOfObject( + repositoryId, objectId, relationshipDirection); - // Filtering results - List extracted = new ArrayList(); - if (typeId != null) { - Set typeIds = new HashSet(); - typeIds.add(typeId); + // Filtering results + List extracted = new ArrayList(); + if (typeId != null) { + Set typeIds = new HashSet(); + typeIds.add(typeId); - if (includeSubRelationshipTypes) { - List descendants = typeManager - .getTypesDescendants(repositoryId, typeId, - BigInteger.valueOf(-1), false); - for (TypeDefinitionContainer tdc : descendants) { - typeIds.add(tdc.getTypeDefinition().getId()); + if (includeSubRelationshipTypes) { + List descendants = typeManager + .getTypesDescendants(repositoryId, typeId, + BigInteger.valueOf(-1), false); + for (TypeDefinitionContainer tdc : descendants) { + typeIds.add(tdc.getTypeDefinition().getId()); + } } - } - for (Relationship rel : rels) { - if (typeIds.contains(rel.getId())) { - extracted.add(rel); + for (Relationship rel : rels) { + if (typeIds.contains(rel.getId())) { + extracted.add(rel); + } } + } else { + extracted = rels; } - } else { - extracted = rels; - } - // Compile to ObjectData - return compileService.compileObjectDataList(callContext, - repositoryId, extracted, filter, - includeAllowableActions, IncludeRelationships.NONE, null, false, maxItems, skipCount, false); + // Compile to ObjectData + return compileService.compileObjectDataList(callContext, + repositoryId, extracted, filter, + includeAllowableActions, IncludeRelationships.NONE, null, false, maxItems, skipCount, false, null); + }finally{ + lock.unlock(); + } } public void setTypeManager(TypeManager typeManager) { @@ -120,4 +133,8 @@ public void setCompileService(CompileService compileService) { public void setExceptionService(ExceptionService exceptionService) { this.exceptionService = exceptionService; } + + public void setThreadLockService(ThreadLockService threadLockService) { + this.threadLockService = threadLockService; + } } diff --git a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/VersioningServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/VersioningServiceImpl.java index dacfa62c..9f88ebfd 100644 --- a/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/VersioningServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/cmis/service/impl/VersioningServiceImpl.java @@ -26,6 +26,7 @@ import java.util.Comparator; import java.util.GregorianCalendar; import java.util.List; +import java.util.concurrent.locks.Lock; import jp.aegif.nemaki.businesslogic.ContentService; import jp.aegif.nemaki.cmis.aspect.CompileService; @@ -36,6 +37,7 @@ import jp.aegif.nemaki.model.VersionSeries; import jp.aegif.nemaki.util.cache.NemakiCachePool; import jp.aegif.nemaki.util.constant.DomainType; +import jp.aegif.nemaki.util.lock.ThreadLockService; import org.apache.chemistry.opencmis.commons.data.Acl; import org.apache.chemistry.opencmis.commons.data.ContentStream; @@ -53,6 +55,7 @@ public class VersioningServiceImpl implements VersioningService { private CompileService compileService; private ExceptionService exceptionService; private NemakiCachePool nemakiCachePool; + private ThreadLockService threadLockService; @Override /** @@ -60,65 +63,86 @@ public class VersioningServiceImpl implements VersioningService { */ public void checkOut(CallContext callContext, String repositoryId, Holder objectId, ExtensionsData extension, Holder contentCopied) { - // ////////////////// - // General Exception - // ////////////////// - String id = objectId.getValue(); - exceptionService.invalidArgumentRequiredString("objectId", id); - Document document = contentService.getDocument(repositoryId, id); - exceptionService.objectNotFound(DomainType.OBJECT, document, id); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_CHECKOUT_DOCUMENT, document); - // ////////////////// - // Specific Exception - // ////////////////// - // CMIS doesn't define the error type when checkOut is performed - // repeatedly - exceptionService.constraintAlreadyCheckedOut(repositoryId, document); - exceptionService.constraintVersionable(repositoryId, document.getObjectType()); - exceptionService.versioning(document); - - // ////////////////// - // Body of the method - // ////////////////// - Document pwc = contentService.checkOut(callContext, repositoryId, id, extension); - objectId.setValue(pwc.getId()); - Holder copied = new Holder(true); - contentCopied = copied; + exceptionService.invalidArgumentRequiredHolderString("objectId", objectId); + String originalId = objectId.getValue(); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); - nemakiCachePool.get(repositoryId).removeCmisCache(id); + try{ + lock.lock(); + // ////////////////// + // General Exception + // ////////////////// + Document document = contentService.getDocument(repositoryId, objectId.getValue()); + exceptionService.objectNotFound(DomainType.OBJECT, document, objectId.getValue()); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_CHECKOUT_DOCUMENT, document); + + // ////////////////// + // Specific Exception + // ////////////////// + // CMIS doesn't define the error type when checkOut is performed + // repeatedly + exceptionService.constraintAlreadyCheckedOut(repositoryId, document); + exceptionService.constraintVersionable(repositoryId, document.getObjectType()); + exceptionService.versioning(document); + + // ////////////////// + // Body of the method + // ////////////////// + Document pwc = contentService.checkOut(callContext, repositoryId, objectId.getValue(), extension); + objectId.setValue(pwc.getId()); + Holder copied = new Holder(true); + contentCopied = copied; + + nemakiCachePool.get(repositoryId).removeCmisCache(originalId); + }finally{ + lock.unlock(); + } } @Override public void cancelCheckOut(CallContext callContext, String repositoryId, String objectId, ExtensionsData extension) { - // ////////////////// - // General Exception - // ////////////////// + exceptionService.invalidArgumentRequiredString("objectId", objectId); - Document document = contentService.getDocument(repositoryId, objectId); - exceptionService.objectNotFound(DomainType.OBJECT, document, objectId); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_CHECKIN_DOCUMENT, document); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectId); + + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + Document document = contentService.getDocument(repositoryId, objectId); + exceptionService.objectNotFound(DomainType.OBJECT, document, objectId); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_CHECKIN_DOCUMENT, document); - // ////////////////// - // Specific Exception - // ////////////////// - exceptionService.constraintVersionable(repositoryId, document.getObjectType()); + // ////////////////// + // Specific Exception + // ////////////////// + exceptionService.constraintVersionable(repositoryId, document.getObjectType()); - // ////////////////// - // Body of the method - // ////////////////// - contentService.cancelCheckOut(callContext, repositoryId, objectId, extension); + // ////////////////// + // Body of the method + // ////////////////// + contentService.cancelCheckOut(callContext, repositoryId, objectId, extension); - //remove cache - nemakiCachePool.get(repositoryId).removeCmisCache(objectId); - Document latest = contentService.getDocumentOfLatestVersion(repositoryId, document.getVersionSeriesId()); - //Latest document does not exit when pwc is created as the first version - if(latest != null){ - nemakiCachePool.get(repositoryId).removeCmisCache(latest.getId()); + //remove cache + nemakiCachePool.get(repositoryId).removeCmisCache(objectId); + Document latest = contentService.getDocumentOfLatestVersion(repositoryId, document.getVersionSeriesId()); + //Latest document does not exit when pwc is created as the first version + if(latest != null){ + nemakiCachePool.get(repositoryId).removeCmisCache(latest.getId()); + } + }finally{ + lock.unlock(); } + + } @Override @@ -126,33 +150,50 @@ public void checkIn(CallContext callContext, String repositoryId, Holder objectId, Boolean major, Properties properties, ContentStream contentStream, String checkinComment, List policies, Acl addAces, Acl removeAces, ExtensionsData extension) { - // ////////////////// - // General Exception - // ////////////////// - String id = objectId.getValue(); - exceptionService.invalidArgumentRequiredString("objectId", id); - Document document = contentService.getDocument(repositoryId, id); - exceptionService.objectNotFound(DomainType.OBJECT, document, id); - exceptionService.permissionDenied(callContext, - repositoryId, PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT, document); - // ////////////////// - // Specific Exception - // ////////////////// - exceptionService.constraintVersionable(repositoryId, document.getObjectType()); - // TODO implement - // exceptionService.streamNotSupported(documentTypeDefinition, - // contentStream); - - // ////////////////// - // Body of the method - // ////////////////// - Document checkedIn = contentService.checkIn(callContext, repositoryId, - objectId, major, properties, contentStream, checkinComment, - policies, addAces, removeAces, extension); - objectId.setValue(checkedIn.getId()); + exceptionService.invalidArgumentRequiredHolderString("objectId", objectId); + final String pwcId = objectId.getValue(); + + Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); - nemakiCachePool.get(repositoryId).removeCmisCache(id); + try{ + lock.lock(); + + // ////////////////// + // General Exception + // ////////////////// + + Document pwc = contentService.getDocument(repositoryId, objectId.getValue()); + exceptionService.objectNotFound(DomainType.OBJECT, pwc, objectId.getValue()); + exceptionService.permissionDenied(callContext, + repositoryId, PermissionMapping.CAN_CANCEL_CHECKOUT_DOCUMENT, pwc); + + // ////////////////// + // Specific Exception + // ////////////////// + exceptionService.constraintVersionable(repositoryId, pwc.getObjectType()); + // TODO implement + // exceptionService.streamNotSupported(documentTypeDefinition, + // contentStream); + + // ////////////////// + // Body of the method + // ////////////////// + Document latest = contentService + .getDocumentOfLatestVersion(repositoryId, pwc.getVersionSeriesId()); + + Document checkedIn = contentService.checkIn(callContext, repositoryId, + objectId, major, properties, contentStream, checkinComment, + policies, addAces, removeAces, extension); + objectId.setValue(checkedIn.getId()); + + nemakiCachePool.get(repositoryId).removeCmisCache(pwc.getId()); + if(latest != null){ + nemakiCachePool.get(repositoryId).removeCmisCache(latest.getId()); + } + }finally{ + lock.unlock(); + } } @Override @@ -183,18 +224,28 @@ public ObjectData getObjectOfLatestVersion(CallContext context, document = contentService .getDocumentOfLatestVersion(repositoryId, versionSeriesId); } - exceptionService.objectNotFound(DomainType.OBJECT, document, - versionSeriesId); - exceptionService.permissionDenied(context, - repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, document); + + Lock lock = threadLockService.getReadLock(repositoryId, document.getId()); + + try{ + lock.lock(); + + exceptionService.objectNotFound(DomainType.OBJECT, document, + versionSeriesId); + exceptionService.permissionDenied(context, + repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, document); - // ////////////////// - // Body of the method - // ////////////////// - ObjectData objectData = compileService.compileObjectData(context, - repositoryId, document, filter, - includeAllowableActions, includeRelationships, renditionFilter, includeAcl); - return objectData; + // ////////////////// + // Body of the method + // ////////////////// + ObjectData objectData = compileService.compileObjectData(context, + repositoryId, document, filter, + includeAllowableActions, includeRelationships, renditionFilter, includeAcl); + return objectData; + + }finally{ + lock.unlock(); + } } @Override @@ -217,34 +268,38 @@ public List getAllVersions(CallContext context, .getAllVersions(context, repositoryId, versionSeriesId); exceptionService.objectNotFoundVersionSeries(versionSeriesId, allVersions); - // Sort by the descending order - Collections.sort(allVersions, new VersionComparator()); - - //Permissions filter - /*exceptionService.permissionDenied(context, - PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES, - allVersions.get(0)); - */ - Document latest = allVersions.get(0); - if(latest.isPrivateWorkingCopy()){ - VersionSeries vs = contentService.getVersionSeries(repositoryId, latest); - if(!context.getUsername().equals(vs.getVersionSeriesCheckedOutBy())){ - allVersions.remove(latest); - } - } - // ////////////////// - // Body of the method - // ////////////////// - List result = new ArrayList(); - for (Content content : allVersions) { - ObjectData objectData = compileService.compileObjectData( - context, repositoryId, content, filter, - includeAllowableActions, IncludeRelationships.NONE, null, true); - result.add(objectData); - } + List locks = threadLockService.readLocks(repositoryId, allVersions); + try{ + threadLockService.bulkLock(locks); + + // Sort by the descending order + Collections.sort(allVersions, new VersionComparator()); + + Document latest = allVersions.get(0); + if(latest.isPrivateWorkingCopy()){ + VersionSeries vs = contentService.getVersionSeries(repositoryId, latest); + if(!context.getUsername().equals(vs.getVersionSeriesCheckedOutBy())){ + allVersions.remove(latest); + } + } + + // ////////////////// + // Body of the method + // ////////////////// + List result = new ArrayList(); + for (Content content : allVersions) { + ObjectData objectData = compileService.compileObjectData( + context, repositoryId, content, filter, + includeAllowableActions, IncludeRelationships.NONE, null, true); + result.add(objectData); + } - return result; + return result; + + }finally{ + threadLockService.bulkUnlock(locks); + } } /** @@ -284,4 +339,8 @@ public void setExceptionService(ExceptionService exceptionService) { public void setNemakiCachePool(NemakiCachePool nemakiCachePool) { this.nemakiCachePool = nemakiCachePool; } + + public void setThreadLockService(ThreadLockService threadLockService) { + this.threadLockService = threadLockService; + } } diff --git a/core/src/main/java/jp/aegif/nemaki/dao/ContentDaoService.java b/core/src/main/java/jp/aegif/nemaki/dao/ContentDaoService.java index 97ec084c..2907e953 100644 --- a/core/src/main/java/jp/aegif/nemaki/dao/ContentDaoService.java +++ b/core/src/main/java/jp/aegif/nemaki/dao/ContentDaoService.java @@ -271,7 +271,7 @@ public interface ContentDaoService { * * @return */ - List getLatestChildrenIndex(String repositoryId, String parentId); + List getChildren(String repositoryId, String parentId); /** * Get a child content by name @@ -407,6 +407,8 @@ public interface ContentDaoService { * @return the newly updated document */ Document update(String repositoryId, Document document); + + Document move(String repositoryId, Document document, String sourceId); /** * Update a version series @@ -425,6 +427,8 @@ public interface ContentDaoService { * @return the newly updated folder */ Folder update(String repositoryId, Folder folder); + + Folder move(String repositoryId, Folder folder, String sourceId); /** * Update a relationship @@ -640,6 +644,8 @@ public interface ContentDaoService { */ void deleteArchive(String repositoryId, String archiveId); + void refreshCmisObjectData(String repositoryId, String objectId); + /** * Restore a content from its archive * @param repositoryId TODO diff --git a/core/src/main/java/jp/aegif/nemaki/dao/impl/cached/ContentDaoServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/dao/impl/cached/ContentDaoServiceImpl.java index da2d0407..d36c3a62 100644 --- a/core/src/main/java/jp/aegif/nemaki/dao/impl/cached/ContentDaoServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/dao/impl/cached/ContentDaoServiceImpl.java @@ -21,6 +21,10 @@ ******************************************************************************/ package jp.aegif.nemaki.dao.impl.cached; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.GregorianCalendar; import java.util.List; import jp.aegif.nemaki.dao.ContentDaoService; @@ -39,11 +43,11 @@ import jp.aegif.nemaki.model.Relationship; import jp.aegif.nemaki.model.Rendition; import jp.aegif.nemaki.model.VersionSeries; -import jp.aegif.nemaki.util.cache.NemakiCache; import jp.aegif.nemaki.util.cache.NemakiCachePool; +import jp.aegif.nemaki.util.cache.model.NemakiCache; +import jp.aegif.nemaki.util.cache.model.Tree; import net.sf.ehcache.Cache; import net.sf.ehcache.Element; - import org.apache.chemistry.opencmis.commons.data.ContentStream; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -59,11 +63,11 @@ public class ContentDaoServiceImpl implements ContentDaoService { private ContentDaoService nonCachedContentDaoService; private NemakiCachePool nemakiCachePool; - + private final String TOKEN_CACHE_LATEST_CHANGE_TOKEN = "lc"; public ContentDaoServiceImpl() { - + } // /////////////////////////////////////// @@ -78,8 +82,7 @@ public List getTypeDefinitions(String repositoryId) { return (List) v.getObjectValue(); } - List result = nonCachedContentDaoService - .getTypeDefinitions(repositoryId); + List result = nonCachedContentDaoService.getTypeDefinitions(repositoryId); if (CollectionUtils.isEmpty(result)) { return null; @@ -111,20 +114,16 @@ public NemakiTypeDefinition getTypeDefinition(String repositoryId, String typeId } @Override - public NemakiTypeDefinition createTypeDefinition( - String repositoryId, NemakiTypeDefinition typeDefinition) { - NemakiTypeDefinition nt = nonCachedContentDaoService - .createTypeDefinition(repositoryId, typeDefinition); + public NemakiTypeDefinition createTypeDefinition(String repositoryId, NemakiTypeDefinition typeDefinition) { + NemakiTypeDefinition nt = nonCachedContentDaoService.createTypeDefinition(repositoryId, typeDefinition); Cache typeCache = nemakiCachePool.get(repositoryId).getTypeCache(); typeCache.remove("typedefs"); return nt; } @Override - public NemakiTypeDefinition updateTypeDefinition( - String repositoryId, NemakiTypeDefinition typeDefinition) { - NemakiTypeDefinition nt = nonCachedContentDaoService - .updateTypeDefinition(repositoryId, typeDefinition); + public NemakiTypeDefinition updateTypeDefinition(String repositoryId, NemakiTypeDefinition typeDefinition) { + NemakiTypeDefinition nt = nonCachedContentDaoService.updateTypeDefinition(repositoryId, typeDefinition); Cache typeCache = nemakiCachePool.get(repositoryId).getTypeCache(); typeCache.remove("typedefs"); return nt; @@ -149,44 +148,38 @@ public NemakiPropertyDefinitionCore getPropertyDefinitionCore(String repositoryI } @Override - public NemakiPropertyDefinitionCore getPropertyDefinitionCoreByPropertyId( - String repositoryId, String propertyId) { - return nonCachedContentDaoService - .getPropertyDefinitionCoreByPropertyId(repositoryId, propertyId); + public NemakiPropertyDefinitionCore getPropertyDefinitionCoreByPropertyId(String repositoryId, String propertyId) { + return nonCachedContentDaoService.getPropertyDefinitionCoreByPropertyId(repositoryId, propertyId); } @Override - public NemakiPropertyDefinitionDetail getPropertyDefinitionDetail( - String repositoryId, String nodeId) { + public NemakiPropertyDefinitionDetail getPropertyDefinitionDetail(String repositoryId, String nodeId) { return nonCachedContentDaoService.getPropertyDefinitionDetail(repositoryId, nodeId); } @Override - public List getPropertyDefinitionDetailByCoreNodeId( - String repositoryId, String coreNodeId) { - return nonCachedContentDaoService - .getPropertyDefinitionDetailByCoreNodeId(repositoryId, coreNodeId); + public List getPropertyDefinitionDetailByCoreNodeId(String repositoryId, + String coreNodeId) { + return nonCachedContentDaoService.getPropertyDefinitionDetailByCoreNodeId(repositoryId, coreNodeId); } @Override - public NemakiPropertyDefinitionCore createPropertyDefinitionCore( - String repositoryId, NemakiPropertyDefinitionCore propertyDefinitionCore) { - return nonCachedContentDaoService - .createPropertyDefinitionCore(repositoryId, propertyDefinitionCore); + public NemakiPropertyDefinitionCore createPropertyDefinitionCore(String repositoryId, + NemakiPropertyDefinitionCore propertyDefinitionCore) { + return nonCachedContentDaoService.createPropertyDefinitionCore(repositoryId, propertyDefinitionCore); } @Override - public NemakiPropertyDefinitionDetail createPropertyDefinitionDetail( - String repositoryId, NemakiPropertyDefinitionDetail propertyDefinitionDetail) { - return nonCachedContentDaoService - .createPropertyDefinitionDetail(repositoryId, propertyDefinitionDetail); + public NemakiPropertyDefinitionDetail createPropertyDefinitionDetail(String repositoryId, + NemakiPropertyDefinitionDetail propertyDefinitionDetail) { + return nonCachedContentDaoService.createPropertyDefinitionDetail(repositoryId, propertyDefinitionDetail); } @Override - public NemakiPropertyDefinitionDetail updatePropertyDefinitionDetail( - String repositoryId, NemakiPropertyDefinitionDetail propertyDefinitionDetail) { - NemakiPropertyDefinitionDetail np = nonCachedContentDaoService - .updatePropertyDefinitionDetail(repositoryId, propertyDefinitionDetail); + public NemakiPropertyDefinitionDetail updatePropertyDefinitionDetail(String repositoryId, + NemakiPropertyDefinitionDetail propertyDefinitionDetail) { + NemakiPropertyDefinitionDetail np = nonCachedContentDaoService.updatePropertyDefinitionDetail(repositoryId, + propertyDefinitionDetail); Cache typeCache = nemakiCachePool.get(repositoryId).getTypeCache(); typeCache.remove("typedefs"); return np; @@ -232,12 +225,13 @@ public boolean existContent(String repositoryId, String objectTypeId) { @Override public Document getDocument(String repositoryId, String objectId) { Cache contentCache = nemakiCachePool.get(repositoryId).getContentCache(); + Element v = contentCache.get(objectId); if (v != null) { - try{ + try { return (Document) v.getObjectValue(); - }catch(ClassCastException e){ + } catch (ClassCastException e) { throw e; } } @@ -253,8 +247,7 @@ public Document getDocument(String repositoryId, String objectId) { @Override public List getCheckedOutDocuments(String repositoryId, String parentFolderId) { - return nonCachedContentDaoService - .getCheckedOutDocuments(repositoryId, parentFolderId); + return nonCachedContentDaoService.getCheckedOutDocuments(repositoryId, parentFolderId); } @Override @@ -281,14 +274,12 @@ public List getAllVersions(String repositoryId, String versionSeriesId @Override public Document getDocumentOfLatestVersion(String repositoryId, String versionSeriesId) { - return nonCachedContentDaoService - .getDocumentOfLatestVersion(repositoryId, versionSeriesId); + return nonCachedContentDaoService.getDocumentOfLatestVersion(repositoryId, versionSeriesId); } @Override public Document getDocumentOfLatestMajorVersion(String repositoryId, String versionSeriesId) { - return nonCachedContentDaoService - .getDocumentOfLatestMajorVersion(repositoryId, versionSeriesId); + return nonCachedContentDaoService.getDocumentOfLatestMajorVersion(repositoryId, versionSeriesId); } @Override @@ -297,9 +288,9 @@ public Folder getFolder(String repositoryId, String objectId) { Element v = contentCache.get(objectId); if (v != null) { - try{ + try { return (Folder) v.getObjectValue(); - }catch(ClassCastException e){ + } catch (ClassCastException e) { throw e; } } @@ -320,8 +311,18 @@ public Folder getFolderByPath(String repositoryId, String path) { } @Override - public List getLatestChildrenIndex(String repositoryId, String parentId) { - return nonCachedContentDaoService.getLatestChildrenIndex(repositoryId, parentId); + public List getChildren(String repositoryId, String parentId) { + Tree tree = getOrCreateTreeCache(repositoryId, parentId); + + List result = new ArrayList<>(); + for(String childId : tree.getChildren()){ + Content child = getContent(repositoryId, childId); + if(child != null){ + result.add(child); + } + } + + return result; } @Override @@ -338,10 +339,10 @@ public List getChildrenNames(String repositoryId, String parentId) { public Relationship getRelationship(String repositoryId, String objectId) { Cache cache = nemakiCachePool.get(repositoryId).getContentCache(); Element v = cache.get(objectId); - - if(v == null){ - return (Relationship)v.getObjectValue(); - }else{ + + if (v == null) { + return (Relationship) v.getObjectValue(); + } else { return nonCachedContentDaoService.getRelationship(repositoryId, objectId); } } @@ -373,9 +374,93 @@ public Item getItem(String repositoryId, String objectId) { @Override public Document create(String repositoryId, Document document) { - Document d = nonCachedContentDaoService.create(repositoryId, document); - nemakiCachePool.get(repositoryId).getContentCache().put(new Element(d.getId(), d)); - return d; + Document created = nonCachedContentDaoService.create(repositoryId, document); + nemakiCachePool.get(repositoryId).getContentCache().put(new Element(created.getId(), created)); + + //Tree cache + addToTreeCache(repositoryId, created); + + return created; + } + + private Tree getOrCreateTreeCache(String repositoryId, String parentId){ + NemakiCache treeCache = nemakiCachePool.get(repositoryId).getTreeCache(); + Tree tree = treeCache.get(parentId); + if(tree == null){ + List list = nonCachedContentDaoService.getChildren(repositoryId, parentId); + tree = new Tree(parentId); + if(org.apache.commons.collections.CollectionUtils.isNotEmpty(list)){ + for(Content child : list){ + tree.add(child.getId()); + } + } + treeCache.put(tree.getParent(), tree); + } + + return tree; + } + + private void addToTreeCache(String repositoryId, Content content){ + Tree tree = getOrCreateTreeCache(repositoryId, content.getParentId()); + + if(content instanceof Document){ + Document doc = (Document)content; + if(doc.isPrivateWorkingCopy()){ + //do nothing + return; + }else{ + List versions = getAllVersions(repositoryId, doc.getVersionSeriesId()); + if(versions != null){ + Collections.sort(versions, new VersionComparator()); + for(Document version : versions){ + if(version.getId().equals(doc.getId())){ + tree.add(doc.getId()); + }else{ + tree.remove(version.getId()); + } + } + } + } + }else if(content instanceof Folder || content instanceof Item){ + tree.add(content.getId()); + } + } + + private class VersionComparator implements Comparator { + @Override + public int compare(Content content0, Content content1) { + // TODO when created time is not set + GregorianCalendar created0 = content0.getCreated(); + GregorianCalendar created1 = content1.getCreated(); + + if (created0.before(created1)) { + return 1; + } else if (created0.after(created1)) { + return -1; + } else { + return 0; + } + } + } + + /** + * + * @param repositoryId + * @param doc + * @return Previous version. If previous version does not exist, return null. + * @throws Exception + */ + private Document getPreviousVersion(String repositoryId, Document doc) throws Exception{ + String vsId = doc.getVersionSeriesId(); + List docs = getAllVersions(repositoryId, vsId); + if(CollectionUtils.isEmpty(docs)){ + throw new Exception(String.format("Version series of document[%s] is broken!", doc.getId())); + }else if(docs.size() <=1){ + return null; + }else{ + Document previous = docs.get(docs.size() - 2); + return previous; + } } @Override @@ -391,7 +476,8 @@ public Change create(String repositoryId, Change change) { Change created = nonCachedContentDaoService.create(repositoryId, change); Change latest = nonCachedContentDaoService.getLatestChange(repositoryId); nemakiCachePool.get(repositoryId).getLatestChangeTokenCache().removeAll(); - nemakiCachePool.get(repositoryId).getLatestChangeTokenCache().put(new Element(TOKEN_CACHE_LATEST_CHANGE_TOKEN, latest)); + nemakiCachePool.get(repositoryId).getLatestChangeTokenCache() + .put(new Element(TOKEN_CACHE_LATEST_CHANGE_TOKEN, latest)); return created; } @@ -399,6 +485,8 @@ public Change create(String repositoryId, Change change) { public Folder create(String repositoryId, Folder folder) { Folder created = nonCachedContentDaoService.create(repositoryId, folder); nemakiCachePool.get(repositoryId).getContentCache().put(new Element(created.getId(), created)); + addToTreeCache(repositoryId, created); + return created; } @@ -420,6 +508,7 @@ public Policy create(String repositoryId, Policy policy) { public Item create(String repositoryId, Item item) { Item created = nonCachedContentDaoService.create(repositoryId, item); nemakiCachePool.get(repositoryId).getContentCache().put(new Element(created.getId(), created)); + addToTreeCache(repositoryId, created); return created; } @@ -427,13 +516,20 @@ public Item create(String repositoryId, Item item) { public Document update(String repositoryId, Document document) { Document updated = nonCachedContentDaoService.update(repositoryId, document); nemakiCachePool.get(repositoryId).getContentCache().put(new Element(updated.getId(), updated)); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(updated.getId()); return updated; } + + @Override + public Document move(String repositoryId, Document document, String sourceId) { + moveTreeCache(repositoryId, document, sourceId); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(document.getId()); + return update(repositoryId, document); + } @Override public VersionSeries update(String repositoryId, VersionSeries versionSeries) { - VersionSeries updated = nonCachedContentDaoService - .update(repositoryId, versionSeries); + VersionSeries updated = nonCachedContentDaoService.update(repositoryId, versionSeries); Cache versionSeriesCache = nemakiCachePool.get(repositoryId).getVersionSeriesCache(); versionSeriesCache.put(new Element(updated.getId(), updated)); return updated; @@ -443,13 +539,37 @@ public VersionSeries update(String repositoryId, VersionSeries versionSeries) { public Folder update(String repositoryId, Folder folder) { Folder updated = nonCachedContentDaoService.update(repositoryId, folder); nemakiCachePool.get(repositoryId).getContentCache().put(new Element(updated.getId(), updated)); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(updated.getId()); return updated; } + + @Override + public Folder move(String repositoryId, Folder folder, String sourceId) { + moveTreeCache(repositoryId, folder, sourceId); + return update(repositoryId, folder); + } + private void moveTreeCache(String repositoryId, Content updated, String sourceId){ + String targetId = updated.getParentId(); + + NemakiCache cache = nemakiCachePool.get(repositoryId).getTreeCache(); + if(!sourceId.equals(targetId)){ + //Remove from source + Tree souceTree = cache.get(sourceId); + if(souceTree != null){ + souceTree.remove(updated.getId()); + } + + //Add to target + addToTreeCache(repositoryId, updated); + } + } + @Override public Relationship update(String repositoryId, Relationship relationship) { Relationship updated = nonCachedContentDaoService.update(repositoryId, relationship); nemakiCachePool.get(repositoryId).getContentCache().put(new Element(updated.getId(), updated)); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(updated.getId()); return updated; } @@ -457,6 +577,7 @@ public Relationship update(String repositoryId, Relationship relationship) { public Policy update(String repositoryId, Policy policy) { Policy updated = nonCachedContentDaoService.update(repositoryId, policy); nemakiCachePool.get(repositoryId).getContentCache().put(new Element(updated.getId(), updated)); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(updated.getId()); return updated; } @@ -464,6 +585,7 @@ public Policy update(String repositoryId, Policy policy) { public Item update(String repositoryId, Item item) { Item updated = nonCachedContentDaoService.update(repositoryId, item); nemakiCachePool.get(repositoryId).getContentCache().put(new Element(updated.getId(), updated)); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(updated.getId()); return updated; } @@ -471,24 +593,61 @@ public Item update(String repositoryId, Item item) { public void delete(String repositoryId, String objectId) { NodeBase nb = nonCachedContentDaoService.getNodeBase(repositoryId, objectId); + if(nb == null){ + return; + } + + //read document in advance + Document doc = null; + Document previous = null; + if(nb.isDocument()){ + doc = (Document)getDocument(repositoryId, objectId); + try { + previous = getPreviousVersion(repositoryId, doc); + } catch (Exception e) { + e.printStackTrace(); + } + } + + //read tree in advance + Tree tree = null; + if(nb.isDocument() || nb.isFolder()){ + Content _c = getContent(repositoryId, objectId); + tree = getOrCreateTreeCache(repositoryId, _c.getParentId()); + } + // remove from database nonCachedContentDaoService.delete(repositoryId, objectId); - + // remove from cache - String id = objectId; - - if (nb.isDocument()) { - Document d = this.getDocument(repositoryId, objectId); - // we can delete versionSeries or not? - nemakiCachePool.get(repositoryId).getVersionSeriesCache().remove(d.getVersionSeriesId()); - nemakiCachePool.get(repositoryId).getAttachmentCache().remove(d.getAttachmentNodeId()); - }else if (nb.isAttachment()) { - nemakiCachePool.get(repositoryId).getAttachmentCache().remove(id); + if(doc == null){ + if (nb.isAttachment()) { + nemakiCachePool.get(repositoryId).getAttachmentCache().remove(objectId); + }else{ + nemakiCachePool.get(repositoryId).getContentCache().remove(objectId); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(objectId); + if(tree != null){ + tree.remove(objectId); + } + } + }else{ + //DOCUMENT case + if(doc.isPrivateWorkingCopy()){ + //delete just pwc-related cache + nemakiCachePool.get(repositoryId).getAttachmentCache().remove(doc.getAttachmentNodeId()); + nemakiCachePool.get(repositoryId).getContentCache().remove(doc.getId()); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(doc.getId()); + nemakiCachePool.get(repositoryId).getVersionSeriesCache().remove(doc.getVersionSeriesId()); + }else{ + nemakiCachePool.get(repositoryId).getAttachmentCache().remove(doc.getAttachmentNodeId()); + nemakiCachePool.get(repositoryId).getContentCache().remove(doc.getId()); + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(doc.getId()); + tree.remove(doc.getId()); + nemakiCachePool.get(repositoryId).getVersionSeriesCache().remove(doc.getVersionSeriesId()); + } } - - nemakiCachePool.get(repositoryId).getContentCache().remove(id); } - + // /////////////////////////////////////// // Attachment // /////////////////////////////////////// @@ -531,8 +690,7 @@ public Rendition getRendition(String repositoryId, String objectId) { } @Override - public String createRendition(String repositoryId, - Rendition rendition, ContentStream contentStream) { + public String createRendition(String repositoryId, Rendition rendition, ContentStream contentStream) { return nonCachedContentDaoService.createRendition(repositoryId, rendition, contentStream); } @@ -542,15 +700,13 @@ public String createAttachment(String repositoryId, AttachmentNode attachment, C } @Override - public void updateAttachment(String repositoryId, - AttachmentNode attachment, ContentStream contentStream) { + public void updateAttachment(String repositoryId, AttachmentNode attachment, ContentStream contentStream) { Cache attachmentCache = nemakiCachePool.get(repositoryId).getAttachmentCache(); Element v = attachmentCache.get(attachment.getId()); if (v != null) { attachmentCache.remove(attachment.getId()); } nonCachedContentDaoService.updateAttachment(repositoryId, attachment, contentStream); - } // ////////////////////////////////////////////////////////////////////////////// @@ -563,19 +719,20 @@ public Change getChangeEvent(String repositoryId, String changeTokenId) { @Override public Change getLatestChange(String repositoryId) { - Change change =null; - + Change change = null; + Element v = nemakiCachePool.get(repositoryId).getLatestChangeTokenCache().get(TOKEN_CACHE_LATEST_CHANGE_TOKEN); if (v != null) { - change = (Change)v.getObjectValue(); + change = (Change) v.getObjectValue(); } - + if (change != null) { return change; } else { change = nonCachedContentDaoService.getLatestChange(repositoryId); if (change != null) { - nemakiCachePool.get(repositoryId).getLatestChangeTokenCache().put(new Element(TOKEN_CACHE_LATEST_CHANGE_TOKEN, change)); + nemakiCachePool.get(repositoryId).getLatestChangeTokenCache() + .put(new Element(TOKEN_CACHE_LATEST_CHANGE_TOKEN, change)); } return change; } @@ -583,8 +740,7 @@ public Change getLatestChange(String repositoryId) { @Override public List getLatestChanges(String repositoryId, String startToken, int maxItems) { - return nonCachedContentDaoService - .getLatestChanges(repositoryId, startToken, maxItems); + return nonCachedContentDaoService.getLatestChanges(repositoryId, startToken, maxItems); } // ////////////////////////////////////////////////////////////////////////////// @@ -612,8 +768,7 @@ public List getChildArchives(String repositoryId, Archive archive) { @Override public List getArchivesOfVersionSeries(String repositoryId, String versionSeriesId) { - return nonCachedContentDaoService - .getArchivesOfVersionSeries(repositoryId, versionSeriesId); + return nonCachedContentDaoService.getArchivesOfVersionSeries(repositoryId, versionSeriesId); } @Override @@ -623,8 +778,7 @@ public List getAllArchives(String repositoryId) { @Override public Archive createArchive(String repositoryId, Archive archive, Boolean deletedWithParent) { - return nonCachedContentDaoService.createArchive(repositoryId, - archive, deletedWithParent); + return nonCachedContentDaoService.createArchive(repositoryId, archive, deletedWithParent); } @Override @@ -647,11 +801,18 @@ public void restoreAttachment(String repositoryId, Archive archive) { nonCachedContentDaoService.restoreAttachment(repositoryId, archive); } + // ////////////////////////////////////////////////////////////////////////////// + // Cache management + // ////////////////////////////////////////////////////////////////////////////// + public void refreshCmisObjectData(String repositoryId, String objectId){ + nemakiCachePool.get(repositoryId).getObjectDataCache().remove(objectId); + } + + // ////////////////////////////////////////////////////////////////////////////// // Spring // ////////////////////////////////////////////////////////////////////////////// - public void setNonCachedContentDaoService( - ContentDaoService nonCachedContentDaoService) { + public void setNonCachedContentDaoService(ContentDaoService nonCachedContentDaoService) { this.nonCachedContentDaoService = nonCachedContentDaoService; } diff --git a/core/src/main/java/jp/aegif/nemaki/dao/impl/couch/ContentDaoServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/dao/impl/couch/ContentDaoServiceImpl.java index 582ad188..9f81bfd8 100644 --- a/core/src/main/java/jp/aegif/nemaki/dao/impl/couch/ContentDaoServiceImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/dao/impl/couch/ContentDaoServiceImpl.java @@ -432,7 +432,7 @@ public Folder getFolderByPath(String repositoryId, String path) { } @Override - public List getLatestChildrenIndex(String repositoryId, String parentId) { + public List getChildren(String repositoryId, String parentId) { ViewQuery query = new ViewQuery().designDocId(DESIGN_DOCUMENT).viewName("children").key(parentId); List list = connectorPool.get(repositoryId).queryView(query, CouchContent.class); @@ -618,6 +618,11 @@ public Document update(String repositoryId, Document document) { connectorPool.get(repositoryId).update(update); return update.convert(); } + + @Override + public Document move(String repositoryId, Document document, String sourceId){ + return update(repositoryId, document); + } @Override public VersionSeries update(String repositoryId, VersionSeries versionSeries) { @@ -642,6 +647,11 @@ public Folder update(String repositoryId, Folder folder) { return update.convert(); } + + @Override + public Folder move(String repositoryId, Folder folder, String sourceId){ + return update(repositoryId, folder); + } @Override public Relationship update(String repositoryId, Relationship relationship) { @@ -1026,4 +1036,8 @@ public void setRepositoryInfoMap(RepositoryInfoMap repositoryInfoMap) { this.repositoryInfoMap = repositoryInfoMap; } + @Override + public void refreshCmisObjectData(String repositoryId, String objectId) { + // this method is for cached service + } } diff --git a/core/src/main/java/jp/aegif/nemaki/util/YamlManager.java b/core/src/main/java/jp/aegif/nemaki/util/YamlManager.java index 21f4f948..41bc5547 100644 --- a/core/src/main/java/jp/aegif/nemaki/util/YamlManager.java +++ b/core/src/main/java/jp/aegif/nemaki/util/YamlManager.java @@ -39,7 +39,7 @@ public YamlManager(String baseModelFile){ this.baseModelFile = baseModelFile; } - public synchronized Object loadYml(){ + public Object loadYml(){ InputStream is = getClass().getClassLoader().getResourceAsStream(baseModelFile); if (is == null) { log.error("yaml file not found"); diff --git a/core/src/main/java/jp/aegif/nemaki/util/cache/impl/NemakiCacheImpl.java b/core/src/main/java/jp/aegif/nemaki/util/cache/CacheService.java similarity index 53% rename from core/src/main/java/jp/aegif/nemaki/util/cache/impl/NemakiCacheImpl.java rename to core/src/main/java/jp/aegif/nemaki/util/cache/CacheService.java index bc12898a..2f7ffb51 100644 --- a/core/src/main/java/jp/aegif/nemaki/util/cache/impl/NemakiCacheImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/util/cache/CacheService.java @@ -1,21 +1,27 @@ -package jp.aegif.nemaki.util.cache.impl; - +package jp.aegif.nemaki.util.cache; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; + +import java.util.Map; +import java.util.Map.Entry; + import jp.aegif.nemaki.util.PropertyManager; -import jp.aegif.nemaki.util.cache.CustomCache; -import jp.aegif.nemaki.util.cache.NemakiCache; +import jp.aegif.nemaki.util.YamlManager; +import jp.aegif.nemaki.util.cache.model.NemakiCache; +import jp.aegif.nemaki.util.cache.model.Tree; +import jp.aegif.nemaki.util.cache.CacheService; import jp.aegif.nemaki.util.constant.PropertyKey; -public class NemakiCacheImpl implements NemakiCache{ +public class CacheService { private boolean cacheEnabled; - + private final CacheManager cacheManager; private final String OBJECT_DATA_CACHE = "objectDataCache"; private final String PROPERTIES_CACHE = "propertisCache"; private final String TYPE_CACHE = "typeCache"; private final String CONTENT_CACHE = "contentCache"; + private final String TREE_CACHE = "treeCache"; private final String VERSION_SERIES_CACHE = "versionSeriesCache"; private final String ATTACHMENTS_CACHE = "attachmentCache"; private final String CHANGE_EVENT_CACHE = "changeEventCache"; @@ -24,93 +30,118 @@ public class NemakiCacheImpl implements NemakiCache{ private final String USERS_CACHE = "usersCache"; private final String GROUP_CACHE = "groupCache"; private final String GROUPS_CACHE = "groupsCache"; - + private final String repositoryId; - - public NemakiCacheImpl(String repositoryId, PropertyManager propertyManager) { + + public CacheService(String repositoryId, PropertyManager propertyManager) { this.repositoryId = repositoryId; - + cacheEnabled = propertyManager.readBoolean(PropertyKey.CACHE_CMIS_ENABLED); - + cacheManager = CacheManager.newInstance(); - cacheManager.addCache(new Cache(repositoryId + "_" + OBJECT_DATA_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + PROPERTIES_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + TYPE_CACHE, 1, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + CONTENT_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + VERSION_SERIES_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + ATTACHMENTS_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + CHANGE_EVENT_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + LATEST_CHANGE_TOKEN_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + USER_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + USERS_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + GROUP_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - cacheManager.addCache(new Cache(repositoryId + "_" + GROUPS_CACHE, 10000, false, false, 60 * 60, 60 * 60)); - } - - @Override + loadConfig(propertyManager); + } + + private void loadConfig(PropertyManager propertyManager) { + String configFile = propertyManager.readValue(PropertyKey.CACHE_CONFIG); + YamlManager manager = new YamlManager(configFile); + Map> yml = (Map>) manager.loadYml(); + + // default + Map defaultConfigMap = yml.get("default"); + yml.remove("default"); + + for (Entry> configMap : yml.entrySet()) { + NemakiCacheConfig config = new NemakiCacheConfig(); + config.override(defaultConfigMap); + if(configMap.getValue() != null){ + config.override(configMap.getValue()); + } + + Cache cache = new Cache(repositoryId + "_" + configMap.getKey(), config.maxElementsInMemory.intValue(), + config.overflowToDisc, config.eternal, config.timeToLiveSeconds, config.timeToIdleSeconds); + cacheManager.addCache(cache); + } + + } + + private class NemakiCacheConfig { + private Long maxElementsInMemory; + private Boolean overflowToDisc; + private Boolean eternal; + private Long timeToLiveSeconds; + private Long timeToIdleSeconds; + + private void override(Map map) { + if (map.get("maxElementsInMemory") != null) + maxElementsInMemory = (Long) map.get("maxElementsInMemory"); + if (map.get("overflowToDisc") != null) + overflowToDisc = (Boolean) map.get("overflowToDisc"); + if (map.get("eternal") != null) + eternal = (Boolean) map.get("eternal"); + if (map.get("timeToLiveSeconds") != null) + timeToLiveSeconds = (Long) map.get("timeToLiveSeconds"); + if (map.get("timeToIdleSeconds") != null) + timeToIdleSeconds = (Long) map.get("timeToIdleSeconds"); + } + } + public CustomCache getObjectDataCache() { CustomCache cc = new CustomCache(cacheEnabled); cc.setCache(cacheManager.getCache(repositoryId + "_" + OBJECT_DATA_CACHE)); - return cc; + return cc; } - @Override public Cache getPropertiesCache() { return cacheManager.getCache(repositoryId + "_" + PROPERTIES_CACHE); } - @Override public Cache getTypeCache() { return cacheManager.getCache(repositoryId + "_" + TYPE_CACHE); } - @Override public Cache getContentCache() { return cacheManager.getCache(repositoryId + "_" + CONTENT_CACHE); } - @Override + public NemakiCache getTreeCache() { + NemakiCache cache = new NemakiCache(true, cacheManager.getCache(repositoryId + "_" + TREE_CACHE)); + return cache; + } + public Cache getVersionSeriesCache() { return cacheManager.getCache(repositoryId + "_" + VERSION_SERIES_CACHE); } - @Override public Cache getAttachmentCache() { return cacheManager.getCache(repositoryId + "_" + ATTACHMENTS_CACHE); } - @Override public Cache getChangeEventCache() { return cacheManager.getCache(repositoryId + "_" + CHANGE_EVENT_CACHE); } - @Override public Cache getLatestChangeTokenCache() { return cacheManager.getCache(repositoryId + "_" + LATEST_CHANGE_TOKEN_CACHE); } - @Override public Cache getUserCache() { return cacheManager.getCache(repositoryId + "_" + USER_CACHE); } - @Override public Cache getUsersCache() { return cacheManager.getCache(repositoryId + "_" + USERS_CACHE); } - @Override public Cache getGroupCache() { return cacheManager.getCache(repositoryId + "_" + GROUP_CACHE); } - @Override public Cache getGroupsCache() { return cacheManager.getCache(repositoryId + "_" + GROUPS_CACHE); } - @Override public void removeCmisCache(String objectId) { getPropertiesCache().remove(objectId); getObjectDataCache().remove(objectId); diff --git a/core/src/main/java/jp/aegif/nemaki/util/cache/NemakiCache.java b/core/src/main/java/jp/aegif/nemaki/util/cache/NemakiCache.java deleted file mode 100644 index 2193e7b8..00000000 --- a/core/src/main/java/jp/aegif/nemaki/util/cache/NemakiCache.java +++ /dev/null @@ -1,20 +0,0 @@ -package jp.aegif.nemaki.util.cache; - -import net.sf.ehcache.Cache; - -public interface NemakiCache { - public CustomCache getObjectDataCache(); - public Cache getPropertiesCache(); - public Cache getTypeCache(); - public Cache getContentCache(); - public Cache getVersionSeriesCache(); - public Cache getAttachmentCache(); - public Cache getChangeEventCache(); - public Cache getLatestChangeTokenCache(); - public Cache getUserCache(); - public Cache getUsersCache(); - public Cache getGroupCache(); - public Cache getGroupsCache(); - - public void removeCmisCache(String objectId); -} diff --git a/core/src/main/java/jp/aegif/nemaki/util/cache/NemakiCachePool.java b/core/src/main/java/jp/aegif/nemaki/util/cache/NemakiCachePool.java index ad28b634..87036ab6 100644 --- a/core/src/main/java/jp/aegif/nemaki/util/cache/NemakiCachePool.java +++ b/core/src/main/java/jp/aegif/nemaki/util/cache/NemakiCachePool.java @@ -1,7 +1,7 @@ package jp.aegif.nemaki.util.cache; public interface NemakiCachePool { - NemakiCache get(String repositoryId); + CacheService get(String repositoryId); void add(String repositoryId); void remove(String repositoryId); void removeAll(); diff --git a/core/src/main/java/jp/aegif/nemaki/util/cache/impl/NemakiCachePoolImpl.java b/core/src/main/java/jp/aegif/nemaki/util/cache/impl/NemakiCachePoolImpl.java index 903700e6..5eb3aeb5 100644 --- a/core/src/main/java/jp/aegif/nemaki/util/cache/impl/NemakiCachePoolImpl.java +++ b/core/src/main/java/jp/aegif/nemaki/util/cache/impl/NemakiCachePoolImpl.java @@ -5,13 +5,13 @@ import jp.aegif.nemaki.cmis.factory.info.RepositoryInfoMap; import jp.aegif.nemaki.util.PropertyManager; -import jp.aegif.nemaki.util.cache.NemakiCache; +import jp.aegif.nemaki.util.cache.CacheService; import jp.aegif.nemaki.util.cache.NemakiCachePool; public class NemakiCachePoolImpl implements NemakiCachePool{ - private Map pool = new HashMap(); - private NemakiCache nullCache; + private Map pool = new HashMap(); + private CacheService nullCache; private RepositoryInfoMap repositoryInfoMap; private PropertyManager propertyManager; @@ -25,12 +25,12 @@ public void init(){ add(key); } - nullCache = new NemakiCacheImpl(null, propertyManager); + nullCache = new CacheService(null, propertyManager); } @Override - public NemakiCache get(String repositoryId) { - NemakiCache cache = pool.get(repositoryId); + public CacheService get(String repositoryId) { + CacheService cache = pool.get(repositoryId); if (cache == null){ return nullCache; @@ -41,7 +41,7 @@ public NemakiCache get(String repositoryId) { @Override public void add(String repositoryId) { - pool.put(repositoryId, new NemakiCacheImpl(repositoryId, propertyManager)); + pool.put(repositoryId, new CacheService(repositoryId, propertyManager)); } @Override @@ -56,13 +56,13 @@ public void removeAll() { @Override public void clear(String repositoryId) { - pool.put(repositoryId, new NemakiCacheImpl(repositoryId, propertyManager)); + pool.put(repositoryId, new CacheService(repositoryId, propertyManager)); } @Override public void clearAll() { for(String key : pool.keySet()){ - pool.put(key, new NemakiCacheImpl(key, propertyManager)); + pool.put(key, new CacheService(key, propertyManager)); } } diff --git a/core/src/main/java/jp/aegif/nemaki/util/cache/model/NemakiCache.java b/core/src/main/java/jp/aegif/nemaki/util/cache/model/NemakiCache.java new file mode 100644 index 00000000..b28ce7a1 --- /dev/null +++ b/core/src/main/java/jp/aegif/nemaki/util/cache/model/NemakiCache.java @@ -0,0 +1,64 @@ +package jp.aegif.nemaki.util.cache.model; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import net.sf.ehcache.Cache; +import net.sf.ehcache.Element; + +public class NemakiCache { + private Cache cache; + private final boolean cacheEnabled; + private static final Log log = LogFactory.getLog(NemakiCache.class); + + public NemakiCache(boolean cacheEnabled, Cache cache){ + this.cacheEnabled = cacheEnabled; + this.cache = cache; + } + + public T get(String key){ + if(cacheEnabled){ + Element element = cache.get(key); + if(element == null || element.getObjectValue() == null){ + return null; + }else{ + return (T)element.getObjectValue(); + } + }else{ + return null; + } + } + + public void put(String key, T data){ + if(cacheEnabled){ + Element element = new Element(key, data); + cache.put(element); + } + } + + public void put(Element element){ + if(cacheEnabled){ + cache.put(element); + } + } + + public void remove(String key){ + if(cacheEnabled){ + cache.remove(key); + } + } + + public void removeAll(){ + if(cacheEnabled){ + cache.removeAll(); + } + } + + public Cache getCache(){ + return this.cache; + } + + public void setCache(Cache cache){ + this.cache = cache; + } +} diff --git a/core/src/main/java/jp/aegif/nemaki/util/cache/model/Tree.java b/core/src/main/java/jp/aegif/nemaki/util/cache/model/Tree.java new file mode 100644 index 00000000..eec87164 --- /dev/null +++ b/core/src/main/java/jp/aegif/nemaki/util/cache/model/Tree.java @@ -0,0 +1,40 @@ +package jp.aegif.nemaki.util.cache.model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Tree { + private String parent; + private Set children; + + public Tree(String parent){ + this.parent = parent; + this.children = new HashSet<>(); + } + + public void add(String objectId){ + children.add(objectId); + } + + public void remove(String objectId){ + children.remove(objectId); + } + + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } + + public Set getChildren() { + return children; + } + + public void setChildren(Set children) { + this.children = children; + } +} diff --git a/core/src/main/java/jp/aegif/nemaki/util/constant/PropertyKey.java b/core/src/main/java/jp/aegif/nemaki/util/constant/PropertyKey.java index 00e0e042..a7531160 100644 --- a/core/src/main/java/jp/aegif/nemaki/util/constant/PropertyKey.java +++ b/core/src/main/java/jp/aegif/nemaki/util/constant/PropertyKey.java @@ -254,6 +254,7 @@ public interface PropertyKey { final String LOG_CALLCONTEXT = "log.callcontext"; //Cache + final String CACHE_CONFIG = "cache.config"; final String CACHE_CMIS_ENABLED = "cache.cmis.enabled"; //Auth token diff --git a/core/src/main/java/jp/aegif/nemaki/util/lock/ThreadLockService.java b/core/src/main/java/jp/aegif/nemaki/util/lock/ThreadLockService.java new file mode 100644 index 00000000..bed783cb --- /dev/null +++ b/core/src/main/java/jp/aegif/nemaki/util/lock/ThreadLockService.java @@ -0,0 +1,16 @@ +package jp.aegif.nemaki.util.lock; + +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; + +import jp.aegif.nemaki.model.Content; + +public interface ThreadLockService { + public ReadWriteLock get(String repositoryId, String objectId); + public Lock getWriteLock(String repositoryId, String objectId); + public Lock getReadLock(String repositoryId, String objectId); + public List readLocks(String repositoryId, List contents); + public void bulkLock(List locks); + public void bulkUnlock(List locks); +} diff --git a/core/src/main/java/jp/aegif/nemaki/util/lock/UniqueObjectId.java b/core/src/main/java/jp/aegif/nemaki/util/lock/UniqueObjectId.java new file mode 100644 index 00000000..bc3c9de2 --- /dev/null +++ b/core/src/main/java/jp/aegif/nemaki/util/lock/UniqueObjectId.java @@ -0,0 +1,58 @@ +package jp.aegif.nemaki.util.lock; + +public class UniqueObjectId { + private String repositoryId; + private String objectId; + + public UniqueObjectId(String repositoryId, String objectId) { + super(); + this.repositoryId = repositoryId; + this.objectId = objectId; + } + + public String getRepositoryId() { + return repositoryId; + } + public void setRepositoryId(String repositoryId) { + this.repositoryId = repositoryId; + } + public String getObjectId() { + return objectId; + } + public void setObjectId(String objectId) { + this.objectId = objectId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((objectId == null) ? 0 : objectId.hashCode()); + result = prime * result + ((repositoryId == null) ? 0 : repositoryId.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + UniqueObjectId other = (UniqueObjectId) obj; + if (objectId == null) { + if (other.objectId != null) + return false; + } else if (!objectId.equals(other.objectId)) + return false; + if (repositoryId == null) { + if (other.repositoryId != null) + return false; + } else if (!repositoryId.equals(other.repositoryId)) + return false; + return true; + } + + +} diff --git a/core/src/main/java/jp/aegif/nemaki/util/lock/impl/ThreadLockServiceImpl.java b/core/src/main/java/jp/aegif/nemaki/util/lock/impl/ThreadLockServiceImpl.java new file mode 100644 index 00000000..7d06333d --- /dev/null +++ b/core/src/main/java/jp/aegif/nemaki/util/lock/impl/ThreadLockServiceImpl.java @@ -0,0 +1,62 @@ +package jp.aegif.nemaki.util.lock.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; + +import org.apache.commons.collections.CollectionUtils; + +import com.google.common.util.concurrent.Striped; + +import jp.aegif.nemaki.model.Content; +import jp.aegif.nemaki.util.lock.ThreadLockService; +import jp.aegif.nemaki.util.lock.UniqueObjectId; + +public class ThreadLockServiceImpl implements ThreadLockService{ + + private final Striped locks = Striped.lazyWeakReadWriteLock(4096); + + @Override + public ReadWriteLock get(String repositoryId, String objectId) { + ReadWriteLock lock = locks.get(new UniqueObjectId(repositoryId, objectId)); + return lock; + } + + @Override + public Lock getWriteLock(String repositoryId, String objectId) { + return get(repositoryId, objectId).writeLock(); + } + + @Override + public Lock getReadLock(String repositoryId, String objectId) { + return get(repositoryId, objectId).readLock(); + } + + @Override + public List readLocks(String repositoryId, List contents){ + List locks = new ArrayList<>(); + if(CollectionUtils.isNotEmpty(contents)){ + for(T content : contents){ + Lock lock = getReadLock(repositoryId, content.getId()); + locks.add(lock); + } + } + + return locks; + } + + @Override + public void bulkLock(List locks){ + for(Lock lock : locks){ + lock.lock(); + } + } + + @Override + public void bulkUnlock(List locks){ + for(Lock lock : locks){ + lock.unlock(); + } + } +} diff --git a/core/src/main/webapp/WEB-INF/classes/ehcache.xml b/core/src/main/webapp/WEB-INF/classes/ehcache.xml index e5e74081..1eae0fdd 100644 --- a/core/src/main/webapp/WEB-INF/classes/ehcache.xml +++ b/core/src/main/webapp/WEB-INF/classes/ehcache.xml @@ -1,15 +1,15 @@ - - - + + + + \ No newline at end of file diff --git a/core/src/main/webapp/WEB-INF/classes/ehcache.yml b/core/src/main/webapp/WEB-INF/classes/ehcache.yml new file mode 100644 index 00000000..9b7e2e60 --- /dev/null +++ b/core/src/main/webapp/WEB-INF/classes/ehcache.yml @@ -0,0 +1,36 @@ +default: + maxElementsInMemory: 10000 + overflowToDisc: false + eternal: false + timeToLiveSeconds: 3600 + timeToIdleSeconds: 3600 + +objectDataCache: + +propertisCache: + maxElementsInMemory: 100 + +typeCache: + maxElementsInMemory: 1 + +contentCache: + maxElementsInMemory: 1000000 + eternal: true +treeCache: + +versionSeriesCache: + +attachmentCache: + +changeEventCache: + +latestChangeTokenCache: + maxElementsInMemory: 1 + +userCache: + +usersCache: + +groupCache: + +groupsCache: diff --git a/core/src/main/webapp/WEB-INF/classes/nemakiware.properties b/core/src/main/webapp/WEB-INF/classes/nemakiware.properties index bb04c73e..22ea3704 100644 --- a/core/src/main/webapp/WEB-INF/classes/nemakiware.properties +++ b/core/src/main/webapp/WEB-INF/classes/nemakiware.properties @@ -68,6 +68,8 @@ log.after=false log.callcontext=false ###Cache +#Caches are created programmatically. +cache.config=ehcache.yml cache.cmis.enabled=true ###Auth token diff --git a/core/src/main/webapp/WEB-INF/classes/serviceContext.xml b/core/src/main/webapp/WEB-INF/classes/serviceContext.xml index 806529b1..d6906c1d 100644 --- a/core/src/main/webapp/WEB-INF/classes/serviceContext.xml +++ b/core/src/main/webapp/WEB-INF/classes/serviceContext.xml @@ -168,11 +168,11 @@ - + - - + + @@ -218,6 +218,9 @@ ${thread.max} + + + @@ -238,6 +241,9 @@ + + + @@ -292,6 +298,9 @@ + + + @@ -324,6 +333,9 @@ + + + @@ -347,6 +359,9 @@ + + + @@ -376,12 +391,12 @@ + + + - - - @@ -477,6 +492,9 @@ ${capability.extended.include.relationships} + + + @@ -568,12 +586,12 @@ + + + - - - @@ -648,4 +666,19 @@ + + + + + + jp.aegif.nemaki.util.lock.ThreadLockService + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/AllTest.java b/core/src/test/java/jp/aegif/nemaki/test/cmis/AllTest.java deleted file mode 100644 index a7c53ef9..00000000 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/AllTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package jp.aegif.nemaki.test.cmis; - -import org.junit.runner.RunWith; -import org.junit.runners.Suite; - -import jp.aegif.nemaki.test.cmis.tests.BasicsTestGroup; -import jp.aegif.nemaki.test.cmis.tests.ControlTestGroup; -import jp.aegif.nemaki.test.cmis.tests.CrudTestGroup; -import jp.aegif.nemaki.test.cmis.tests.FilingTestGroup; -import jp.aegif.nemaki.test.cmis.tests.QueryTestGroup; -import jp.aegif.nemaki.test.cmis.tests.TypesTestGroup; -import jp.aegif.nemaki.test.cmis.tests.VersioningTestGroup; - -@RunWith( Suite.class ) -@Suite.SuiteClasses( { - BasicsTestGroup.class, - ControlTestGroup.class, - CrudTestGroup.class, - FilingTestGroup.class, - QueryTestGroup.class, - TypesTestGroup.class, - VersioningTestGroup.class, -} ) -public class AllTest extends TckSuite{ - -} diff --git a/core/src/test/java/jp/aegif/nemaki/test/nemaki/MultiThreadTest.java b/core/src/test/java/jp/aegif/nemaki/test/nemaki/MultiThreadTest.java new file mode 100644 index 00000000..aaa77eae --- /dev/null +++ b/core/src/test/java/jp/aegif/nemaki/test/nemaki/MultiThreadTest.java @@ -0,0 +1,163 @@ +package jp.aegif.nemaki.test.nemaki; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +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.OperationContext; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.UnfileObject; +import org.joda.time.DateTime; +import org.joda.time.Duration; +import org.junit.Test; + +public class MultiThreadTest extends TestBase{ + + @Test + public void checkOutTest_single(){ + String folderId = createTestFolder(); + String docId = createDocument(folderId, "test.txt", "This is test"); + Document doc = (Document) session.getObject(docId); + + long start = System.currentTimeMillis(); + doc.checkOut(); + long end = System.currentTimeMillis(); + + System.out.println("start=" + start + ", end=" + end); + + Folder folder = (Folder) session.getObject(folderId); + folder.deleteTree(true, UnfileObject.DELETE, true); + } + + @Test + public void checkOutTest_All() throws InterruptedException, ExecutionException{ + //document ids + Folder folder = (Folder) session.getObject(testFolderId); + OperationContext oc = simpleOperationContext(); + oc.setMaxItemsPerPage(Integer.MAX_VALUE); + + ItemIterable children = folder.getChildren(oc); + + Iterator itr = children.iterator(); + List docs = new ArrayList<>(); + while(itr.hasNext()){ + CmisObject child = itr.next(); + if(child.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT){ + docs.add((Document)child); + } + } + + //checkout + List threads = new ArrayList<>(); + Integer taskNum = 1; + for(Document doc : docs){ + threads.add(new Thread(new CheckOutTask(taskNum, doc))); + taskNum++; + } + + for(Thread thread : threads){ + thread.start(); + } + + for(Thread thread : threads){ + thread.join(); + } + + System.out.println("test end: " + threads.size() + " threads processed"); + } + + public class CheckOutTask implements Runnable{ + private int taskId; + private Document doc; + + public CheckOutTask(int taskId, Document doc){ + this.taskId = taskId; + this.doc = doc; + } + + @Override + public void run() { + DateTime start = new DateTime(); + + ObjectId objectId = doc.checkOut(); + + DateTime end = new DateTime(); + + Duration duration = new Duration(start, end); + + System.out.println(taskId + ", " + duration.getMillis() + ", " + doc.getId() + ", " + start + ", " + end); + } + } + + @Test + public void readTest_All() throws InterruptedException{ + //document ids + Folder folder = (Folder) session.getObject(testFolderId); + OperationContext oc = simpleOperationContext(); + oc.setMaxItemsPerPage(Integer.MAX_VALUE); + + ItemIterable children = folder.getChildren(oc); + + Iterator itr = children.iterator(); + List docs = new ArrayList<>(); + while(itr.hasNext()){ + CmisObject child = itr.next(); + if(child.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT){ + docs.add((Document)child); + } + } + + //checkout + List threads = new ArrayList<>(); + Integer taskNum = 1; + for(Document doc : docs){ + threads.add(new Thread(new ReadTask(taskNum, doc.getId()))); + taskNum++; + } + + for(Thread thread : threads){ + thread.start(); + } + + for(Thread thread : threads){ + thread.join(); + } + + System.out.println("test end: " + threads.size() + " threads processed"); + } + + public class ReadTask implements Runnable{ + private int taskId; + private String objectId; + + public ReadTask(int taskId, String objectId){ + this.taskId = taskId; + this.objectId = objectId; + } + + @Override + public void run() { + System.out.println(taskId + ":start"); + + OperationContext oc = simpleOperationContext(); + oc.setCacheEnabled(false); + + DateTime start = new DateTime(); + + Document doc = (Document) session.getObject(objectId, oc); + + DateTime end = new DateTime(); + + System.out.println(taskId + ":end"); + + Duration duration = new Duration(start, end); + + System.out.println(taskId + ", " + duration.getMillis() + ", " + doc.getId() + ", " + start + ", " + end); + } + } +} diff --git a/core/src/test/java/jp/aegif/nemaki/test/nemaki/SessionUtil.java b/core/src/test/java/jp/aegif/nemaki/test/nemaki/SessionUtil.java new file mode 100644 index 00000000..c8df9712 --- /dev/null +++ b/core/src/test/java/jp/aegif/nemaki/test/nemaki/SessionUtil.java @@ -0,0 +1,51 @@ +package jp.aegif.nemaki.test.nemaki; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.chemistry.opencmis.client.api.OperationContext; +import org.apache.chemistry.opencmis.client.api.Session; +import org.apache.chemistry.opencmis.client.api.SessionFactory; +import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl; +import org.apache.chemistry.opencmis.commons.SessionParameter; +import org.apache.chemistry.opencmis.commons.enums.BindingType; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; + +public class SessionUtil { + + public static Session createCmisSession(String repositoryId, String userId, String password){ + Map parameter = new HashMap(); + + // user credentials + //TODO enable change a user + parameter.put(SessionParameter.USER, userId); + parameter.put(SessionParameter.PASSWORD, password); + + // session locale + parameter.put(SessionParameter.LOCALE_ISO3166_COUNTRY, ""); + parameter.put(SessionParameter.LOCALE_ISO639_LANGUAGE, ""); + + // repository + parameter.put(SessionParameter.REPOSITORY_ID, repositoryId); + //parameter.put(org.apache.chemistry.opencmis.commons.impl.Constants.PARAM_REPOSITORY_ID, NemakiConfig.getValue(PropertyKey.NEMAKI_CORE_URI_REPOSITORY)); + + parameter. put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value()); + + String coreAtomUri = "http://localhost:8080/core/atom/" + repositoryId; //TODO + parameter.put(SessionParameter.ATOMPUB_URL, coreAtomUri); + + //timeout + parameter.put(SessionParameter.CONNECT_TIMEOUT, "30000"); + parameter.put(SessionParameter.READ_TIMEOUT, "30000"); + + + + SessionFactory f = SessionFactoryImpl.newInstance(); + Session session = f.createSession(parameter); + OperationContext operationContext = session.createOperationContext(null, + true, true, false, IncludeRelationships.BOTH, null, false, null, true, 100); + session.setDefaultContext(operationContext); + + return session; + } +} diff --git a/core/src/test/java/jp/aegif/nemaki/test/nemaki/TestBase.java b/core/src/test/java/jp/aegif/nemaki/test/nemaki/TestBase.java new file mode 100644 index 00000000..57dc80ab --- /dev/null +++ b/core/src/test/java/jp/aegif/nemaki/test/nemaki/TestBase.java @@ -0,0 +1,202 @@ +package jp.aegif.nemaki.test.nemaki; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import javax.activation.FileTypeMap; + +import org.apache.chemistry.opencmis.client.api.Folder; +import org.apache.chemistry.opencmis.client.api.ObjectId; +import org.apache.chemistry.opencmis.client.api.OperationContext; +import org.apache.chemistry.opencmis.client.api.Session; +import org.apache.chemistry.opencmis.client.runtime.ObjectIdImpl; +import org.apache.chemistry.opencmis.client.runtime.OperationContextImpl; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; +import org.apache.chemistry.opencmis.commons.enums.UnfileObject; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +public class TestBase { + protected static Session session; + protected static String testFolderId; + + @BeforeClass + public static void before() throws Exception { + session = SessionUtil.createCmisSession("bedroom", "admin", "admin"); + testFolderId = prepareData(); + } + + @AfterClass + public static void after() throws Exception { + Folder folder = (Folder) session.getObject(testFolderId); + folder.deleteTree(true, UnfileObject.DELETE, true); + } + + public static String prepareData() throws Exception{ + int itemNumber = 100; + + String rootFolderId = session.getRepositoryInfo().getRootFolderId(); + + String testFolderId = createFolder(rootFolderId, "test_general_" + System.currentTimeMillis()); + + List tasks = new ArrayList<>(); + for(int i=1; i<=itemNumber; i++){ + tasks.add(new CreateDocumentTask("task_" + i , testFolderId, "task_" + i + ".txt", "これはテストです")); + } + + List> _results = new ArrayList<>(); + ExecutorService executor = Executors.newCachedThreadPool(); + _results = executor.invokeAll(tasks); + + List results = new ArrayList<>(); + for(Future _result : _results){ + results.add(_result.get()); + } + + System.out.println("data initilization completed: " + results.size() + " items"); + return testFolderId; + } + + public static String createFolder(String parentId, String name){ + Mapmap = new HashMap<>(); + map.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_FOLDER.value()); + map.put(PropertyIds.PARENT_ID, parentId); + map.put(PropertyIds.NAME, name); + ObjectId result = session.createFolder(map, new ObjectIdImpl(parentId)); + return result.getId(); + } + + private static class CreateDocumentTask implements Callable{ + String taskId; + String parentId; + String name; + String text; + + public CreateDocumentTask(String taskId, String parentId, String name, String text) { + super(); + this.taskId = taskId; + this.parentId = parentId; + this.name = name; + this.text = text; + } + @Override + public String call() throws Exception { + String objectId = createDocument(parentId, name, text); + System.out.println(objectId + "(" + name + ") is created."); + return objectId; + } + } + + protected static OperationContext simpleOperationContext(String...filters ){ + OperationContextImpl oc = new OperationContextImpl(); + if(filters.length > 0){ + Set _filters = new HashSet(Arrays.asList(PropertyIds.OBJECT_ID)); + oc.setFilter(_filters); + } + oc.setIncludeAllowableActions(false); + oc.setIncludeAcls(false); + oc.setIncludePolicies(false); + oc.setIncludeRelationships(IncludeRelationships.NONE); + + return oc; + } + + public static String createTestFolder(){ + String rootFolderId = session.getRepositoryInfo().getRootFolderId(); + + Mapmap = new HashMap<>(); + map.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_FOLDER.value()); + map.put(PropertyIds.PARENT_ID, rootFolderId); + map.put(PropertyIds.NAME, "testFolder_" + System.currentTimeMillis()); + ObjectId result = session.createFolder(map, new ObjectIdImpl(rootFolderId)); + return result.getId(); + } + + public static String createDocument(String parentId, String name, File file){ + Mapmap = new HashMap<>(); + map.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value()); + map.put(PropertyIds.NAME, name); + + ContentStream contentStream = convertFileToContentStream(session, file); + + ObjectId objectId = session.createDocument(map, new ObjectIdImpl(parentId), contentStream, VersioningState.MAJOR); + + return objectId.getId(); + } + + public static String createDocument(String parentId, String name, String string){ + Mapmap = new HashMap<>(); + map.put(PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value()); + map.put(PropertyIds.NAME, name); + + ContentStream contentStream = new ContentStreamImpl(name, "text/plain", string); + + ObjectId objectId = session.createDocument(map, new ObjectIdImpl(parentId), contentStream, VersioningState.MAJOR); + + return objectId.getId(); + } + + public static File convertInputStreamToFile(InputStream inputStream) + throws IOException { + + File file = File.createTempFile( + String.valueOf(System.currentTimeMillis()), null); + file.deleteOnExit(); + + OutputStream out = new FileOutputStream(file); + try { + int read = 0; + byte[] bytes = new byte[1024]; + while ((read = inputStream.read(bytes)) != -1) { + out.write(bytes, 0, read); + } + } catch (IOException e) { + System.out.println(e.getMessage()); + }finally{ + inputStream.close(); + + out.close(); + } + + return file; + } + + public static ContentStream convertFileToContentStream(Session session, + File file) { + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + FileTypeMap filetypeMap = FileTypeMap.getDefaultFileTypeMap(); + String mimetype = filetypeMap.getContentType(file); + + ContentStream cs = session.getObjectFactory().createContentStream( + file.getName(), file.length(), mimetype, fis); + return cs; + } +} diff --git a/core/src/test/java/jp/aegif/nemaki/test/tck/AllTest.java b/core/src/test/java/jp/aegif/nemaki/test/tck/AllTest.java new file mode 100644 index 00000000..58747c49 --- /dev/null +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/AllTest.java @@ -0,0 +1,26 @@ +package jp.aegif.nemaki.test.tck; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import jp.aegif.nemaki.test.tck.tests.BasicsTestGroup; +import jp.aegif.nemaki.test.tck.tests.ControlTestGroup; +import jp.aegif.nemaki.test.tck.tests.CrudTestGroup; +import jp.aegif.nemaki.test.tck.tests.FilingTestGroup; +import jp.aegif.nemaki.test.tck.tests.QueryTestGroup; +import jp.aegif.nemaki.test.tck.tests.TypesTestGroup; +import jp.aegif.nemaki.test.tck.tests.VersioningTestGroup; + +@RunWith( Suite.class ) +@Suite.SuiteClasses( { + BasicsTestGroup.class, + ControlTestGroup.class, + CrudTestGroup.class, + FilingTestGroup.class, + QueryTestGroup.class, + TypesTestGroup.class, + VersioningTestGroup.class, +} ) +public class AllTest extends TckSuite{ + +} diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/TckSuite.java b/core/src/test/java/jp/aegif/nemaki/test/tck/TckSuite.java similarity index 90% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/TckSuite.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/TckSuite.java index 2e7534bd..e54a2d28 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/TckSuite.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/TckSuite.java @@ -1,4 +1,4 @@ -package jp.aegif.nemaki.test.cmis; +package jp.aegif.nemaki.test.tck; import java.io.PrintWriter; import java.util.ArrayList; @@ -14,13 +14,14 @@ import org.apache.chemistry.opencmis.tck.report.XmlReport; import org.junit.ClassRule; import org.junit.rules.ExternalResource; -import jp.aegif.nemaki.test.cmis.tests.BasicsTestGroup; -import jp.aegif.nemaki.test.cmis.tests.ControlTestGroup; -import jp.aegif.nemaki.test.cmis.tests.CrudTestGroup; -import jp.aegif.nemaki.test.cmis.tests.FilingTestGroup; -import jp.aegif.nemaki.test.cmis.tests.QueryTestGroup; -import jp.aegif.nemaki.test.cmis.tests.TypesTestGroup; -import jp.aegif.nemaki.test.cmis.tests.VersioningTestGroup; + +import jp.aegif.nemaki.test.tck.tests.BasicsTestGroup; +import jp.aegif.nemaki.test.tck.tests.ControlTestGroup; +import jp.aegif.nemaki.test.tck.tests.CrudTestGroup; +import jp.aegif.nemaki.test.tck.tests.FilingTestGroup; +import jp.aegif.nemaki.test.tck.tests.QueryTestGroup; +import jp.aegif.nemaki.test.tck.tests.TypesTestGroup; +import jp.aegif.nemaki.test.tck.tests.VersioningTestGroup; public class TckSuite extends TestGroupBase{ diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/TestGroupBase.java b/core/src/test/java/jp/aegif/nemaki/test/tck/TestGroupBase.java similarity index 99% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/TestGroupBase.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/TestGroupBase.java index 1d27adc9..d77bf54f 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/TestGroupBase.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/TestGroupBase.java @@ -1,4 +1,4 @@ -package jp.aegif.nemaki.test.cmis; +package jp.aegif.nemaki.test.tck; import java.io.File; import java.io.FileInputStream; diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/TestHelper.java b/core/src/test/java/jp/aegif/nemaki/test/tck/TestHelper.java similarity index 97% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/TestHelper.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/TestHelper.java index 3845600c..7e3bea0e 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/TestHelper.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/TestHelper.java @@ -1,4 +1,4 @@ -package jp.aegif.nemaki.test.cmis; +package jp.aegif.nemaki.test.tck; import java.io.File; import java.util.Map; @@ -12,7 +12,7 @@ import org.apache.chemistry.opencmis.tck.runner.AbstractRunner; import org.junit.Assert; -import jp.aegif.nemaki.test.cmis.tests.BasicsTestGroup; +import jp.aegif.nemaki.test.tck.tests.BasicsTestGroup; public class TestHelper { private static final String PARAMETERS_FILE_NAME = "cmis-tck-parameters.properties"; diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/BasicsTestGroup.java b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/BasicsTestGroup.java similarity index 88% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/tests/BasicsTestGroup.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/tests/BasicsTestGroup.java index 8ded561a..7b4f84f1 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/BasicsTestGroup.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/BasicsTestGroup.java @@ -1,10 +1,11 @@ -package jp.aegif.nemaki.test.cmis.tests; +package jp.aegif.nemaki.test.tck.tests; import org.apache.chemistry.opencmis.tck.tests.basics.RepositoryInfoTest; import org.apache.chemistry.opencmis.tck.tests.basics.RootFolderTest; import org.apache.chemistry.opencmis.tck.tests.basics.SecurityTest; import org.junit.Test; -import jp.aegif.nemaki.test.cmis.TckSuite; + +import jp.aegif.nemaki.test.tck.TckSuite; public class BasicsTestGroup extends TckSuite{ diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/ControlTestGroup.java b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/ControlTestGroup.java similarity index 75% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/tests/ControlTestGroup.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/tests/ControlTestGroup.java index 43d811db..0dda627f 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/ControlTestGroup.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/ControlTestGroup.java @@ -1,9 +1,9 @@ -package jp.aegif.nemaki.test.cmis.tests; +package jp.aegif.nemaki.test.tck.tests; import org.apache.chemistry.opencmis.tck.tests.control.ACLSmokeTest; import org.junit.Test; -import jp.aegif.nemaki.test.cmis.TckSuite; +import jp.aegif.nemaki.test.tck.TckSuite; public class ControlTestGroup extends TckSuite{ @Test diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/CrudTestGroup.java b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/CrudTestGroup.java similarity index 97% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/tests/CrudTestGroup.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/tests/CrudTestGroup.java index e903c0de..2343ef57 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/CrudTestGroup.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/CrudTestGroup.java @@ -1,4 +1,4 @@ -package jp.aegif.nemaki.test.cmis.tests; +package jp.aegif.nemaki.test.tck.tests; import org.apache.chemistry.opencmis.tck.tests.crud.BulkUpdatePropertiesTest; import org.apache.chemistry.opencmis.tck.tests.crud.ChangeTokenTest; @@ -21,7 +21,7 @@ import org.apache.chemistry.opencmis.tck.tests.crud.WhitespaceInNameTest; import org.junit.Test; -import jp.aegif.nemaki.test.cmis.TckSuite; +import jp.aegif.nemaki.test.tck.TckSuite; public class CrudTestGroup extends TckSuite{ diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/FilingTestGroup.java b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/FilingTestGroup.java similarity index 84% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/tests/FilingTestGroup.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/tests/FilingTestGroup.java index 87971a1a..04a107bb 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/FilingTestGroup.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/FilingTestGroup.java @@ -1,10 +1,10 @@ -package jp.aegif.nemaki.test.cmis.tests; +package jp.aegif.nemaki.test.tck.tests; import org.apache.chemistry.opencmis.tck.tests.filing.MultifilingTest; import org.apache.chemistry.opencmis.tck.tests.filing.UnfilingTest; import org.junit.Test; -import jp.aegif.nemaki.test.cmis.TckSuite; +import jp.aegif.nemaki.test.tck.TckSuite; public class FilingTestGroup extends TckSuite{ @Test diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/QueryTestGroup.java b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/QueryTestGroup.java similarity index 93% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/tests/QueryTestGroup.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/tests/QueryTestGroup.java index 03355d70..a2831f0b 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/QueryTestGroup.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/QueryTestGroup.java @@ -1,4 +1,4 @@ -package jp.aegif.nemaki.test.cmis.tests; +package jp.aegif.nemaki.test.tck.tests; import org.apache.chemistry.opencmis.tck.tests.query.ContentChangesSmokeTest; import org.apache.chemistry.opencmis.tck.tests.query.QueryForObject; @@ -8,7 +8,7 @@ import org.apache.chemistry.opencmis.tck.tests.query.QuerySmokeTest; import org.junit.Test; -import jp.aegif.nemaki.test.cmis.TckSuite; +import jp.aegif.nemaki.test.tck.TckSuite; public class QueryTestGroup extends TckSuite{ @Test diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/TypesTestGroup.java b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/TypesTestGroup.java similarity index 89% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/tests/TypesTestGroup.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/tests/TypesTestGroup.java index b3aa3fbc..45f9b3cf 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/TypesTestGroup.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/TypesTestGroup.java @@ -1,11 +1,11 @@ -package jp.aegif.nemaki.test.cmis.tests; +package jp.aegif.nemaki.test.tck.tests; import org.apache.chemistry.opencmis.tck.tests.types.BaseTypesTest; import org.apache.chemistry.opencmis.tck.tests.types.CreateAndDeleteTypeTest; import org.apache.chemistry.opencmis.tck.tests.types.SecondaryTypesTest; import org.junit.Test; -import jp.aegif.nemaki.test.cmis.TckSuite; +import jp.aegif.nemaki.test.tck.TckSuite; public class TypesTestGroup extends TckSuite{ @Test diff --git a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/VersioningTestGroup.java b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/VersioningTestGroup.java similarity index 91% rename from core/src/test/java/jp/aegif/nemaki/test/cmis/tests/VersioningTestGroup.java rename to core/src/test/java/jp/aegif/nemaki/test/tck/tests/VersioningTestGroup.java index 7478d2fa..2ffed53b 100644 --- a/core/src/test/java/jp/aegif/nemaki/test/cmis/tests/VersioningTestGroup.java +++ b/core/src/test/java/jp/aegif/nemaki/test/tck/tests/VersioningTestGroup.java @@ -1,4 +1,4 @@ -package jp.aegif.nemaki.test.cmis.tests; +package jp.aegif.nemaki.test.tck.tests; import org.apache.chemistry.opencmis.tck.tests.versioning.CheckedOutTest; import org.apache.chemistry.opencmis.tck.tests.versioning.VersionDeleteTest; @@ -6,7 +6,7 @@ import org.apache.chemistry.opencmis.tck.tests.versioning.VersioningStateCreateTest; import org.junit.Test; -import jp.aegif.nemaki.test.cmis.TckSuite; +import jp.aegif.nemaki.test.tck.TckSuite; public class VersioningTestGroup extends TckSuite{ @Test diff --git a/core/src/test/resources/cmis-tck-filters.properties b/core/src/test/resources/cmis-tck-filters.properties index fc0f0f39..f5efb702 100644 --- a/core/src/test/resources/cmis-tck-filters.properties +++ b/core/src/test/resources/cmis-tck-filters.properties @@ -45,7 +45,7 @@ TypesTestGroup=true secondaryTypesTest=true VersioningTestGroup=true - versioningSmokeTest=false + versioningSmokeTest=true versionDeleteTest=true versioningStateCreateTest=true checkedOutTest=true \ No newline at end of file diff --git a/setup/installer/install.xml b/setup/installer/install.xml index c7dbccac..15607965 100755 --- a/setup/installer/install.xml +++ b/setup/installer/install.xml @@ -3,7 +3,7 @@ NemakiWare - 2.3.12 + 2.3.13 http://www.nemakiware.com/