/******************************************************************************* * Copyright (c) 2013 aegif. * * This file is part of NemakiWare. * * NemakiWare is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NemakiWare is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with NemakiWare. * If not, see <http://www.gnu.org/licenses/>. * * Contributors: * linzhixing(https://github.com/linzhixing) - initial API and implementation ******************************************************************************/ package jp.aegif.nemaki.cmis.service.impl; import java.util.ArrayList; import java.util.Collections; 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; import jp.aegif.nemaki.cmis.aspect.ExceptionService; import jp.aegif.nemaki.cmis.service.VersioningService; import jp.aegif.nemaki.model.Content; import jp.aegif.nemaki.model.Document; 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; import org.apache.chemistry.opencmis.commons.data.ExtensionsData; import org.apache.chemistry.opencmis.commons.data.ObjectData; import org.apache.chemistry.opencmis.commons.data.PermissionMapping; import org.apache.chemistry.opencmis.commons.data.Properties; import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; import org.apache.chemistry.opencmis.commons.server.CallContext; import org.apache.chemistry.opencmis.commons.spi.Holder; import org.apache.commons.lang.StringUtils; public class VersioningServiceImpl implements VersioningService { private ContentService contentService; private CompileService compileService; private ExceptionService exceptionService; private NemakiCachePool nemakiCachePool; private ThreadLockService threadLockService; @Override /** * Repository only allow the latest version to be checked out */ public void checkOut(CallContext callContext, String repositoryId, Holder<String> objectId, ExtensionsData extension, Holder<Boolean> contentCopied) { exceptionService.invalidArgumentRequiredHolderString("objectId", objectId); String originalId = objectId.getValue(); Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); try{ lock.lock(); nemakiCachePool.get(repositoryId).removeCmisCache(originalId); // ////////////////// // 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<Boolean> copied = new Holder<Boolean>(true); contentCopied = copied; }finally{ lock.unlock(); } } @Override public void cancelCheckOut(CallContext callContext, String repositoryId, String objectId, ExtensionsData extension) { exceptionService.invalidArgumentRequiredString("objectId", objectId); Lock lock = threadLockService.getWriteLock(repositoryId, objectId); try{ lock.lock(); nemakiCachePool.get(repositoryId).removeCmisCache(objectId); // ////////////////// // 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()); // ////////////////// // Body of the method // ////////////////// contentService.cancelCheckOut(callContext, repositoryId, objectId, extension); //remove cache Document latest = contentService.getDocumentOfLatestVersion(repositoryId, document.getVersionSeriesId()); //Latest document does not exit when pwc is created as the first version if(latest != null){ Lock latestLock = threadLockService.getWriteLock(repositoryId, latest.getId()); try{ latestLock.lock(); nemakiCachePool.get(repositoryId).removeCmisCache(latest.getId()); }finally{ latestLock.unlock(); } } }finally{ lock.unlock(); } } @Override public void checkIn(CallContext callContext, String repositoryId, Holder<String> objectId, Boolean major, Properties properties, ContentStream contentStream, String checkinComment, List<String> policies, Acl addAces, Acl removeAces, ExtensionsData extension) { exceptionService.invalidArgumentRequiredHolderString("objectId", objectId); Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue()); try{ lock.lock(); // ////////////////// // General Exception // ////////////////// Document pwc = contentService.getDocument(repositoryId, objectId.getValue()); nemakiCachePool.get(repositoryId).removeCmisCache(pwc.getId()); 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 checkedIn = contentService.checkIn(callContext, repositoryId, objectId, major, properties, contentStream, checkinComment, policies, addAces, removeAces, extension); objectId.setValue(checkedIn.getId()); //refresh latest version Document latest = contentService .getDocumentOfLatestVersion(repositoryId, pwc.getVersionSeriesId()); if(latest != null){ Lock latestLock = threadLockService.getWriteLock(repositoryId, latest.getId()); try{ latestLock.lock(); nemakiCachePool.get(repositoryId).removeCmisCache(latest.getId()); }finally{ latestLock.unlock(); } } }finally{ lock.unlock(); } } @Override public ObjectData getObjectOfLatestVersion(CallContext context, String repositoryId, String objectId, String versionSeriesId, Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) { // ////////////////// // General Exception // ////////////////// // Chemistry Atompub calls this method only with objectId if (versionSeriesId == null) { exceptionService .invalidArgumentRequiredString("objectId", objectId); Document d = contentService.getDocument(repositoryId, objectId); versionSeriesId = d.getVersionSeriesId(); } // Default to false Boolean _major = (major == null) ? false : major; Document document = null; if (_major) { document = contentService .getDocumentOfLatestMajorVersion(repositoryId, versionSeriesId); } else { document = contentService .getDocumentOfLatestVersion(repositoryId, versionSeriesId); } 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; }finally{ lock.unlock(); } } @Override public List<ObjectData> getAllVersions(CallContext context, String repositoryId, String objectId, String versionSeriesId, String filter, Boolean includeAllowableActions, ExtensionsData extension) { // ////////////////// // General Exception // ////////////////// // CMIS spec needs versionSeries as required, but Chemistry also takes // objectId if (StringUtils.isBlank(versionSeriesId)) { exceptionService .invalidArgumentRequiredString("objectId", objectId); Document d = contentService.getDocument(repositoryId, objectId); versionSeriesId = d.getVersionSeriesId(); } List<Document> allVersions = contentService .getAllVersions(context, repositoryId, versionSeriesId); exceptionService.objectNotFoundVersionSeries(versionSeriesId, allVersions); List<Lock> 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<ObjectData> result = new ArrayList<ObjectData>(); for (Content content : allVersions) { ObjectData objectData = compileService.compileObjectData( context, repositoryId, content, filter, includeAllowableActions, IncludeRelationships.NONE, null, true); result.add(objectData); } return result; }finally{ threadLockService.bulkUnlock(locks); } } /** * Descending order by cmis:creationDate * * @author linzhixing */ private class VersionComparator implements Comparator<Content> { @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; } } } public void setContentService(ContentService contentService) { this.contentService = contentService; } public void setCompileService(CompileService compileService) { this.compileService = compileService; } public void setExceptionService(ExceptionService exceptionService) { this.exceptionService = exceptionService; } public void setNemakiCachePool(NemakiCachePool nemakiCachePool) { this.nemakiCachePool = nemakiCachePool; } public void setThreadLockService(ThreadLockService threadLockService) { this.threadLockService = threadLockService; } }