diff --git a/config/alfresco/application-context-highlevel.xml b/config/alfresco/application-context-highlevel.xml index 0d6e846270..d53053cb80 100644 --- a/config/alfresco/application-context-highlevel.xml +++ b/config/alfresco/application-context-highlevel.xml @@ -17,6 +17,7 @@ + diff --git a/config/alfresco/model/cmisModel.xml b/config/alfresco/model/cmisModel.xml index 7a728d01bd..7c1e74fbb1 100644 --- a/config/alfresco/model/cmisModel.xml +++ b/config/alfresco/model/cmisModel.xml @@ -10,6 +10,8 @@ + + @@ -47,7 +49,7 @@ - + Object Id @@ -82,6 +84,17 @@ true + + Name + Name + d:text + false + false + false + + both + + Created by The authority who created this object @@ -135,23 +148,14 @@ false - - - - - - cmis:object - - - Name - Name - d:text - false + + Alfresco Node Ref + Alfresco Node Ref + cmis:id + true false false - - both - + @@ -159,14 +163,14 @@ Document Document Type - cmis:filesystemobject + cmisext:object Is Immutable Is the document immutable? d:boolean true - false + false false @@ -184,7 +188,7 @@ Is this a major version of the document? d:boolean true - false + false false @@ -193,7 +197,7 @@ Is this the latest major version of the document? d:boolean true - false + false false @@ -229,7 +233,7 @@ The authority who checked out this document version series d:text true - false + false false @@ -238,7 +242,7 @@ The checked out version series id cmis:id true - false + false false @@ -247,7 +251,7 @@ The checkin comment d:text true - false + false false @@ -256,7 +260,7 @@ The length of the content stream d:long true - false + false false false @@ -267,7 +271,7 @@ The content stream MIME type d:text true - false + false false false @@ -278,7 +282,7 @@ The content stream filename d:text true - false + false false true @@ -289,7 +293,7 @@ Id of the stream cmis:id true - false + false false @@ -299,7 +303,7 @@ Folder Folder Type - cmis:filesystemobject + cmisext:object Parent Id @@ -326,7 +330,7 @@ The allowed child object type ids cmis:id true - false + false true @@ -336,7 +340,7 @@ Relationship Relationship Type - cmis:filesystemobject + cmisext:object Source Id @@ -362,7 +366,7 @@ Policy Policy Type - cmis:object + cmisext:object Policy Text @@ -376,6 +380,11 @@ + + Aspects + Aspects Type + cmis:policy + \ No newline at end of file diff --git a/config/alfresco/opencmis-context.xml b/config/alfresco/opencmis-context.xml new file mode 100644 index 0000000000..b5b0f56ea3 --- /dev/null +++ b/config/alfresco/opencmis-context.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + doclib + + + + + webpreview + imgpreview + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.alfresco.repo.search.impl.querymodel.QueryEngine + + + + + + + + + + + + + + + + + + org.alfresco.repo.search.impl.querymodel.QueryEngine.executeQuery=ACL_ALLOW,AFTER_ACL_NODE.sys:base.Read + org.alfresco.repo.search.impl.querymodel.QueryEngine.getQueryModelFactory=ACL_ALLOW + + + + \ No newline at end of file diff --git a/source/java/org/alfresco/cmis/mapping/CMISMapping.java b/source/java/org/alfresco/cmis/mapping/CMISMapping.java index 8891a654e8..018cbde47f 100644 --- a/source/java/org/alfresco/cmis/mapping/CMISMapping.java +++ b/source/java/org/alfresco/cmis/mapping/CMISMapping.java @@ -70,6 +70,9 @@ public class CMISMapping implements InitializingBean public static String CMIS_MODEL_NS = "cmis"; public static String CMIS_MODEL_URI = "http://www.alfresco.org/model/cmis/1.0/cs01"; + public static String CMIS_EXT_NS = "cmisext"; + public static String CMIS_EXT_URI = "http://www.alfresco.org/model/cmis/1.0/cs01ext"; + /** * The Alfresco CMIS Model name. */ @@ -87,7 +90,7 @@ public class CMISMapping implements InitializingBean public static QName CMIS_DATATYPE_HTML = QName.createQName(CMIS_MODEL_URI, "html"); // CMIS Types - public static QName OBJECT_QNAME = QName.createQName(CMIS_MODEL_URI, "object"); + public static QName OBJECT_QNAME = QName.createQName(CMIS_EXT_URI, "object"); public static QName FILESYSTEM_OBJECT_QNAME = QName.createQName(CMIS_MODEL_URI, "filesystemobject"); public static QName DOCUMENT_QNAME = QName.createQName(CMIS_MODEL_URI, "document"); public static QName FOLDER_QNAME = QName.createQName(CMIS_MODEL_URI, "folder"); diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisService.java b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java new file mode 100644 index 0000000000..04a5013713 --- /dev/null +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisService.java @@ -0,0 +1,2613 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.math.BigInteger; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +import org.alfresco.cmis.CMISInvalidArgumentException; +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; +import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; +import org.alfresco.repo.node.integrity.IntegrityException; +import org.alfresco.repo.search.QueryParameterDefImpl; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.coci.CheckOutCheckInServiceException; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.EntityRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.search.QueryParameterDefinition; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.TempFileProvider; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.data.Acl; +import org.apache.chemistry.opencmis.commons.data.AllowableActions; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.data.ExtensionsData; +import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData; +import org.apache.chemistry.opencmis.commons.data.ObjectData; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderData; +import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList; +import org.apache.chemistry.opencmis.commons.data.ObjectList; +import org.apache.chemistry.opencmis.commons.data.ObjectParentData; +import org.apache.chemistry.opencmis.commons.data.Properties; +import org.apache.chemistry.opencmis.commons.data.RenditionData; +import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; +import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList; +import org.apache.chemistry.opencmis.commons.enums.AclPropagation; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; +import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection; +import org.apache.chemistry.opencmis.commons.enums.UnfileObject; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisVersioningException; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderContainerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectInFolderListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectParentDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl; +import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService; +import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl; +import org.apache.chemistry.opencmis.commons.impl.server.RenditionInfoImpl; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.ObjectInfo; +import org.apache.chemistry.opencmis.commons.server.RenditionInfo; +import org.apache.chemistry.opencmis.commons.spi.Holder; + +/** + * OpenCMIS service object. + * + * @author florian.mueller + */ +public class AlfrescoCmisService extends AbstractCmisService +{ + private CMISConnector connector; + private CallContext context; + private UserTransaction txn; + + public AlfrescoCmisService(CMISConnector connector) + { + this.connector = connector; + } + + public void beginCall(CallContext context) + { + this.context = context; + + // authenticate user + String user = context.getUsername(); + String password = context.getPassword(); + + if ((user == null) || (user.length() == 0)) + { + throw new CmisPermissionDeniedException("No user provided!"); + } + + if (password == null) + { + password = ""; + } + + try + { + connector.getAuthenticationService().authenticate(user, password.toCharArray()); + } catch (AuthenticationException ae) + { + throw new CmisPermissionDeniedException(ae.getMessage(), ae); + } + + // start read-only transaction + try + { + beginReadOnlyTransaction(); + } catch (Exception e) + { + connector.getAuthenticationService().clearCurrentSecurityContext(); + + if (e instanceof CmisBaseException) + { + throw (CmisBaseException) e; + } else + { + throw new CmisRuntimeException(e.getMessage(), e); + } + } + } + + @Override + public void close() + { + try + { + endReadOnlyTransaction(); + } catch (Exception e) + { + if (e instanceof CmisBaseException) + { + throw (CmisBaseException) e; + } else + { + throw new CmisRuntimeException(e.getMessage(), e); + } + } finally + { + // clean up + connector.getAuthenticationService().clearCurrentSecurityContext(); + context = null; + } + } + + /** + * Begins the embracing read-only transaction. + */ + protected void beginReadOnlyTransaction() + { + txn = null; + try + { + txn = connector.getTransactionService().getNonPropagatingUserTransaction(true); + txn.begin(); + } catch (Exception e) + { + throw new CmisRuntimeException(e.getMessage(), e); + } + } + + /** + * Ends embracing read-only transaction. + */ + protected void endReadOnlyTransaction() + { + try + { + if (txn != null) + { + // there isn't anything to commit, really + // we just have to end the transaction + if (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK) + { + txn.rollback(); + } else + { + txn.commit(); + } + txn = null; + } + } catch (Exception e) + { + throw new CmisRuntimeException(e.getMessage(), e); + } + } + + // --- repository service --- + + @Override + public List getRepositoryInfos(ExtensionsData extension) + { + return Collections.singletonList(connector.getRepositoryInfo()); + } + + @Override + public RepositoryInfo getRepositoryInfo(String repositoryId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + return connector.getRepositoryInfo(); + } + + @Override + public TypeDefinitionList getTypeChildren(String repositoryId, String typeId, Boolean includePropertyDefinitions, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + // set up the result + TypeDefinitionListImpl result = new TypeDefinitionListImpl(); + List list = new ArrayList(); + result.setList(list); + + // get the types from the dictionary + List childrenList; + if (typeId == null) + { + childrenList = connector.getOpenCMISDictionaryService().getBaseTypes(); + } else + { + TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); + if (tdw == null) + { + throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); + } + + childrenList = tdw.getChildren(); + } + + // create result + if (max > 0) + { + int lastIndex = (max + skip > childrenList.size() ? childrenList.size() : max + skip) - 1; + for (int i = skip; i <= lastIndex; i++) + { + list.add(childrenList.get(i).getTypeDefinition(includePropertyDefinitions)); + } + } + + result.setHasMoreItems(childrenList.size() - skip > result.getList().size()); + result.setNumItems(BigInteger.valueOf(childrenList.size())); + + return result; + } + + @Override + public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // find the type + TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); + if (tdw == null) + { + throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); + } + + // return type definition + return tdw.getTypeDefinition(true); + } + + @Override + public List getTypeDescendants(String repositoryId, String typeId, BigInteger depth, + Boolean includePropertyDefinitions, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + // check depth + int d = (depth == null ? -1 : depth.intValue()); + if (d == 0) + { + throw new CmisInvalidArgumentException("Depth must not be 0!"); + } + + if (typeId == null) + { + for (TypeDefinitionWrapper tdw : connector.getOpenCMISDictionaryService().getBaseTypes()) + { + result.add(getTypesDescendants(d, tdw, includePropertyDefinitions)); + } + } else + { + TypeDefinitionWrapper tdw = connector.getOpenCMISDictionaryService().findType(typeId); + if (tdw == null) + { + throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); + } + + if (tdw.getChildren() != null) + { + for (TypeDefinitionWrapper child : tdw.getChildren()) + { + result.add(getTypesDescendants(d, child, includePropertyDefinitions)); + } + } + } + + return result; + } + + /** + * Gathers the type descendants tree. + */ + private TypeDefinitionContainer getTypesDescendants(int depth, TypeDefinitionWrapper tdw, + boolean includePropertyDefinitions) + { + TypeDefinitionContainerImpl result = new TypeDefinitionContainerImpl(); + + result.setTypeDefinition(tdw.getTypeDefinition(includePropertyDefinitions)); + + if (depth != 0) + { + if (tdw.getChildren() != null) + { + result.setChildren(new ArrayList()); + for (TypeDefinitionWrapper tdc : tdw.getChildren()) + { + result.getChildren().add( + getTypesDescendants(depth < 0 ? -1 : depth - 1, tdc, includePropertyDefinitions)); + } + } + } + + return result; + } + + // --- navigation service --- + + /* + * Lucene based getChildren - deactivated + */ + public ObjectInFolderList XgetChildren(String repositoryId, String folderId, String filter, String orderBy, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + ObjectInFolderListImpl result = new ObjectInFolderListImpl(); + List list = new ArrayList(); + result.setObjects(list); + + // get the children references + NodeRef folderNodeRef = connector.getFolderNodeRef("Folder", folderId); + + // lucene part + QName PARAM_PARENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "parent"); + DataTypeDefinition nodeRefDataType = connector.getDictionaryService().getDataType(DataTypeDefinition.NODE_REF); + + SearchParameters params = new SearchParameters(); + params.setLanguage(SearchService.LANGUAGE_LUCENE); + params.addStore(folderNodeRef.getStoreRef()); + QueryParameterDefinition parentDef = new QueryParameterDefImpl(PARAM_PARENT, nodeRefDataType, true, + folderNodeRef.toString()); + params.addQueryParameterDefinition(parentDef); + + // Build a query for the appropriate types + StringBuilder query = new StringBuilder(1024).append("+PARENT:\"${cm:parent}\" -ASPECT:\"") + .append(ContentModel.ASPECT_WORKING_COPY).append("\" +TYPE:("); + + // Include doc type if necessary + query.append('"').append(ContentModel.TYPE_CONTENT).append('"'); + query.append(" "); + query.append('"').append(ContentModel.TYPE_FOLDER).append('"'); + + // Always exclude system folders + query.append(") -TYPE:\"").append(ContentModel.TYPE_SYSTEM_FOLDER).append("\""); + params.setQuery(query.toString()); + // parseOrderBy(orderBy, params); + ResultSet resultSet = null; + + List childrenList; + try + { + resultSet = connector.getSearchService().query(params); + childrenList = resultSet.getNodeRefs(); + } finally + { + if (resultSet != null) + resultSet.close(); + } + + if (max > 0) + { + int lastIndex = (max + skip > childrenList.size() ? childrenList.size() : max + skip) - 1; + for (int i = skip; i <= lastIndex; i++) + { + NodeRef child = childrenList.get(i); + + // create a child CMIS object + ObjectData object = connector.createCMISObject(child, filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false); + + ObjectInFolderDataImpl childData = new ObjectInFolderDataImpl(); + childData.setObject(object); + + // include path segment + if (includePathSegment) + { + childData.setPathSegment(connector.getName(child)); + } + + // add it + list.add(childData); + } + } + + result.setHasMoreItems(childrenList.size() - skip > result.getObjects().size()); + result.setNumItems(BigInteger.valueOf(childrenList.size())); + + return result; + } + + public ObjectInFolderList getChildren(String repositoryId, String folderId, String filter, String orderBy, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + ObjectInFolderListImpl result = new ObjectInFolderListImpl(); + List list = new ArrayList(); + result.setObjects(list); + + // get the children references + NodeRef folderNodeRef = connector.getFolderNodeRef("Folder", folderId); + List childrenList = connector.getNodeService().getChildAssocs(folderNodeRef); + + if (max > 0) + { + int lastIndex = (max + skip > childrenList.size() ? childrenList.size() : max + skip) - 1; + for (int i = skip; i <= lastIndex; i++) + { + ChildAssociationRef child = childrenList.get(i); + + try + { + // create a child CMIS object + ObjectData object = connector.createCMISObject(child.getChildRef(), filter, + includeAllowableActions, includeRelationships, renditionFilter, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, object.getId()); + } + + ObjectInFolderDataImpl childData = new ObjectInFolderDataImpl(); + childData.setObject(object); + + // include path segment + if (includePathSegment) + { + childData.setPathSegment(connector.getName(child.getChildRef())); + } + + // add it + list.add(childData); + } catch (InvalidNodeRefException e) + { + // ignore invalid children + } + } + } + + result.setHasMoreItems(childrenList.size() - skip > result.getObjects().size()); + result.setNumItems(BigInteger.valueOf(childrenList.size())); + + return result; + } + + @Override + public List getDescendants(String repositoryId, String folderId, BigInteger depth, + String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + getDescendantsTree(repositoryId, connector.getFolderNodeRef("Folder", folderId), depth.intValue(), filter, + includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, false, result); + + return result; + } + + @Override + public List getFolderTree(String repositoryId, String folderId, BigInteger depth, + String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + getDescendantsTree(repositoryId, connector.getFolderNodeRef("Folder", folderId), depth.intValue(), filter, + includeAllowableActions, includeRelationships, renditionFilter, includePathSegment, true, result); + + return result; + } + + private void getDescendantsTree(String repositoryId, NodeRef folderNodeRef, int depth, String filter, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includePathSegment, boolean foldersOnly, List list) + { + // get the children references + List childrenList = connector.getNodeService().getChildAssocs(folderNodeRef); + for (ChildAssociationRef child : childrenList) + { + try + { + TypeDefinitionWrapper type = connector.getType(child.getChildRef()); + if (type == null) + { + continue; + } + + boolean isFolder = (type instanceof FolderTypeDefintionWrapper); + + if (foldersOnly && !isFolder) + { + continue; + } + + // create a child CMIS object + ObjectInFolderDataImpl object = new ObjectInFolderDataImpl(); + object.setObject(connector.createCMISObject(child.getChildRef(), filter, includeAllowableActions, + includeRelationships, renditionFilter, false, false)); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, object.getObject().getId()); + } + + if (includePathSegment) + { + object.setPathSegment(connector.getName(child.getChildRef())); + } + + // create the container + ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl(); + container.setObject(object); + + if ((depth != 1) && isFolder) + { + container.setChildren(new ArrayList()); + getDescendantsTree(repositoryId, child.getChildRef(), depth - 1, filter, includeAllowableActions, + includeRelationships, renditionFilter, includePathSegment, foldersOnly, + container.getChildren()); + } + + // add it + list.add(container); + } catch (InvalidNodeRefException e) + { + // ignore invalid children + } + } + } + + @Override + public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the node ref + NodeRef nodeRef = connector.getFolderNodeRef("Folder", folderId); + + // the root folder has no parent + if (nodeRef.equals(connector.getRootNodeRef())) + { + throw new CmisInvalidArgumentException("Root folder has no parent!"); + } + + // get the parent + ChildAssociationRef parent = connector.getNodeService().getPrimaryParent(nodeRef); + if (parent == null) + { + throw new CmisRuntimeException("Folder has no parent and is not the root folder?!"); + } + + // create parent object + ObjectData result = connector.createCMISObject(parent.getParentRef(), filter, false, IncludeRelationships.NONE, + CMISConnector.RENDITION_NONE, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, result.getId()); + } + + return result; + } + + @Override + public List getObjectParents(String repositoryId, String objectId, String filter, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + Boolean includeRelativePathSegment, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + List result = new ArrayList(); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + if (variant != ObjectVariantEnum.ASSOC) + { + // versions are filed in the same folder -> cut off version suffix + String currentVersionId = connector.getCurrentVersionId(objectId); + NodeRef nodeRef = connector.getNodeRef(currentVersionId); + NodeRef rootNodeRef = connector.getRootNodeRef(); + + if (!nodeRef.equals(rootNodeRef)) + { + ChildAssociationRef parent = connector.getNodeService().getPrimaryParent(nodeRef); + if (parent != null) + { + ObjectData object = connector.createCMISObject(parent.getParentRef(), filter, + includeAllowableActions, includeRelationships, renditionFilter, false, false); + if (context.isObjectInfoRequired()) + { + getObjectInfo(repositoryId, object.getId()); + } + + ObjectParentDataImpl objectParent = new ObjectParentDataImpl(); + objectParent.setObject(object); + + // include relative path segment + if (includeRelativePathSegment) + { + objectParent.setRelativePathSegment(connector.getName(nodeRef)); + } + + result.add(objectParent); + } + } + } + + return result; + } + + @Override + public ObjectList getCheckedOutDocs(String repositoryId, String folderId, String filter, String orderBy, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + return new ObjectListImpl(); + } + + // --- object service --- + + @Override + public String create(String repositoryId, Properties properties, String folderId, ContentStream contentStream, + VersioningState versioningState, List policies, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // check properties + if (properties == null || properties.getProperties() == null) + { + throw new CmisInvalidArgumentException("Properties must be set!"); + } + + // get the type + String objectTypeId = connector.getObjectTypeIdProperty(properties); + + // find the type + TypeDefinitionWrapper type = connector.getOpenCMISDictionaryService().findType(objectTypeId); + if (type == null) + { + throw new CmisInvalidArgumentException("Type '" + objectTypeId + "' is unknown!"); + } + + // create object + String newId = null; + switch (type.getBaseTypeId()) + { + case CMIS_DOCUMENT: + newId = createDocument(repositoryId, properties, folderId, contentStream, versioningState, policies, null, + null, extension); + break; + case CMIS_FOLDER: + newId = createFolder(repositoryId, properties, folderId, policies, null, null, extension); + break; + case CMIS_POLICY: + newId = createPolicy(repositoryId, properties, folderId, policies, null, null, extension); + break; + } + + // check new object id + if (newId == null) + { + throw new CmisRuntimeException("Creation failed!"); + } + + if (context.isObjectInfoRequired()) + { + try + { + getObjectInfo(repositoryId, newId); + } catch (InvalidNodeRefException e) + { + throw new CmisRuntimeException("Creation failed! New object not found!"); + } + } + + // return the new object id + return newId; + } + + @Override + public String createFolder(String repositoryId, final Properties properties, String folderId, + final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final NodeRef parentNodeRef = connector.getFolderNodeRef("Parent folder", folderId); + + // get name and type + final String name = connector.getNameProperty(properties); + final String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_FOLDER); + + connector.checkChildObjectType(parentNodeRef, type.getTypeId()); + + // run transaction + endReadOnlyTransaction(); + NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + try + { + NodeRef nodeRef = connector.getFileFolderService() + .create(parentNodeRef, name, type.getAlfrescoClass()).getNodeRef(); + + connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, + PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + return nodeRef; + } catch (FileExistsException fee) + { + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return newNodeRef.toString(); + } + + @Override + public String createDocument(String repositoryId, final Properties properties, String folderId, + final ContentStream contentStream, final VersioningState versioningState, final List policies, + final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final NodeRef parentNodeRef = connector.getFolderNodeRef("Parent folder", folderId); + + // get name and type + final String name = connector.getNameProperty(properties); + final String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_DOCUMENT); + + connector.checkChildObjectType(parentNodeRef, type.getTypeId()); + + DocumentTypeDefinition docType = (DocumentTypeDefinition) type.getTypeDefinition(false); + + if ((docType.getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) && (contentStream != null)) + { + throw new CmisConstraintException("This document type does not support content!"); + } + + if ((docType.getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) && (contentStream == null)) + { + throw new CmisConstraintException("This document type does requires content!"); + } + + if (docType.isVersionable() && (versioningState == VersioningState.NONE)) + { + throw new CmisConstraintException("This document type is versionable!"); + } + + if (!docType.isVersionable() && (versioningState != VersioningState.NONE)) + { + throw new CmisConstraintException("This document type is not versionable!"); + } + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); + + // run transaction + endReadOnlyTransaction(); + NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + try + { + NodeRef nodeRef = connector.getFileFolderService() + .create(parentNodeRef, name, type.getAlfrescoClass()).getNodeRef(); + + connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, + PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + // handle content + if (contentStream != null) + { + // write content + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + + connector.applyVersioningState(nodeRef, versioningState); + + return nodeRef; + } catch (FileExistsException fee) + { + removeTempFile(tempFile); + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } catch (IntegrityException ie) + { + removeTempFile(tempFile); + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + removeTempFile(tempFile); + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + removeTempFile(tempFile); + + return connector.createObjectId(newNodeRef); + } + + @Override + public String createDocumentFromSource(String repositoryId, String sourceId, final Properties properties, + String folderId, final VersioningState versioningState, final List policies, final Acl addAces, + final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + final NodeRef parentNodeRef = connector.getFolderNodeRef("Parent folder", folderId); + + // get name and type + final String name = connector.getNameProperty(properties); + + // get source + ObjectVariantEnum variant = connector.getObjectVariant(sourceId); + connector.throwCommonExceptions(variant, "Source", sourceId); + + // check source + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisConstraintException("Source object is not a document!"); + } + + final NodeRef sourceNodeRef = connector.getNodeRef(sourceId); + final TypeDefinitionWrapper type = connector.getAndCheckType(sourceNodeRef); + + if (!(type instanceof DocumentTypeDefinitionWrapper)) + { + throw new CmisConstraintException("Source object is not a document!"); + } + + connector.checkChildObjectType(parentNodeRef, type.getTypeId()); + + // run transaction + endReadOnlyTransaction(); + NodeRef newNodeRef = connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + try + { + NodeRef newDocumentNodeRef = connector.getFileFolderService() + .copy(sourceNodeRef, parentNodeRef, name).getNodeRef(); + + connector.setProperties(newDocumentNodeRef, type, properties, new String[] { + PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(newDocumentNodeRef, type, policies); + connector.applyACL(newDocumentNodeRef, type, addAces, removeAces); + connector.applyVersioningState(newDocumentNodeRef, versioningState); + + return newDocumentNodeRef; + } catch (FileExistsException fee) + { + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return connector.createObjectId(newNodeRef); + } + + @Override + public String createPolicy(String repositoryId, Properties properties, String folderId, List policies, + Acl addAces, Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get the parent folder node ref + connector.getFolderNodeRef("Parent folder", folderId); + + String objectTypeId = connector.getObjectTypeIdProperty(properties); + connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_POLICY); + + // we should never get here - policies are not creatable! + throw new CmisRuntimeException("Polcies cannot be created!"); + } + + @Override + public String createRelationship(String repositoryId, Properties properties, List policies, Acl addAces, + Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get type + String objectTypeId = connector.getObjectTypeIdProperty(properties); + final TypeDefinitionWrapper type = connector.getTypeForCreate(objectTypeId, BaseTypeId.CMIS_RELATIONSHIP); + + // get source object + String sourceId = connector.getSourceIdProperty(properties); + ObjectVariantEnum sourceVariant = connector.getObjectVariant(sourceId); + connector.throwCommonExceptions(sourceVariant, "Source", sourceId); + + if (sourceVariant != ObjectVariantEnum.NODE) + { + throw new CmisInvalidArgumentException("Source is not a document or folder object!"); + } + + final NodeRef sourceNodeRef = connector.getNodeRefIfCurrent("Source", sourceId); + + // get target object + String targetId = connector.getTargetIdProperty(properties); + ObjectVariantEnum targetVariant = connector.getObjectVariant(targetId); + connector.throwCommonExceptions(targetVariant, "Target", sourceId); + + if (targetVariant != ObjectVariantEnum.NODE) + { + throw new CmisInvalidArgumentException("Target is not a document or folder object!"); + } + + final NodeRef targetNodeRef = connector.getNodeRefIfCurrent("Target", targetId); + + // check policies and ACLs + if ((policies != null) && (!policies.isEmpty())) + { + throw new CmisConstraintException("Relationships are not policy controllable!"); + } + + if ((addAces != null) && (addAces.getAces() != null) && (!addAces.getAces().isEmpty())) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + if ((removeAces != null) && (removeAces.getAces() != null) && (!removeAces.getAces().isEmpty())) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + // create relationship + endReadOnlyTransaction(); + AssociationRef assocRef = connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public AssociationRef execute() throws Exception + { + try + { + return connector.getNodeService().createAssociation(sourceNodeRef, targetNodeRef, + type.getAlfrescoClass()); + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return CMISConnector.ASSOC_ID_PREFIX + assocRef.getId(); + } + + @Override + public void setContentStream(String repositoryId, Holder objectId, Boolean overwriteFlag, + Holder changeToken, final ContentStream contentStream, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + ObjectVariantEnum variant = connector.getObjectVariant(objectId.getValue()); + connector.throwCommonExceptions(variant, "Object", objectId.getValue()); + + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisStreamNotSupportedException("Relationships don't support content!"); + } + + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId.getValue()); + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + if (!(type instanceof DocumentTypeDefinitionWrapper)) + { + throw new CmisStreamNotSupportedException("Object type doesn't support content!"); + } + + if (((DocumentTypeDefinition) type.getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) + { + throw new CmisStreamNotSupportedException("Document type doesn't allow content!"); + } + + boolean existed = connector.getContentService().getReader(nodeRef, ContentModel.PROP_CONTENT) != null; + if (existed && !overwriteFlag) + { + throw new CmisContentAlreadyExistsException("Content already exists!"); + } + + if ((contentStream == null) || (contentStream.getStream() == null)) + { + throw new CmisInvalidArgumentException("No content!"); + } + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = getEncoding(tempFile, contentStream.getMimeType()); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + + return null; + } catch (IntegrityException ie) + { + removeTempFile(tempFile); + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + removeTempFile(tempFile); + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + removeTempFile(tempFile); + + objectId.setValue(connector.createObjectId(nodeRef)); + } + + @Override + public void deleteContentStream(String repositoryId, Holder objectId, Holder changeToken, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + ObjectVariantEnum variant = connector.getObjectVariant(objectId.getValue()); + connector.throwCommonExceptions(variant, "Object", objectId.getValue()); + + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisStreamNotSupportedException("Relationships don't support content!"); + } + + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId.getValue()); + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + if (!(type instanceof DocumentTypeDefinitionWrapper)) + { + throw new CmisStreamNotSupportedException("Object type doesn't support content!"); + } + + if (((DocumentTypeDefinition) type.getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.REQUIRED) + { + throw new CmisInvalidArgumentException("Document type requires content!"); + } + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.getNodeService().setProperty(nodeRef, ContentModel.PROP_CONTENT, null); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + objectId.setValue(connector.createObjectId(nodeRef)); + } + + @Override + public void moveObject(String repositoryId, Holder objectId, String targetFolderId, String sourceFolderId, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get object and source and target parent + ObjectVariantEnum variant = connector.getObjectVariant(objectId.getValue()); + connector.throwCommonExceptions(variant, "Object", objectId.getValue()); + + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId.getValue()); + final NodeRef sourceNodeRef = connector.getFolderNodeRef("Source folder", sourceFolderId); + final NodeRef targetNodeRef = connector.getFolderNodeRef("Target folder", targetFolderId); + + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + connector.checkChildObjectType(targetNodeRef, type.getTypeId()); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + ChildAssociationRef primaryParentRef = connector.getNodeService().getPrimaryParent(nodeRef); + // if this is a primary child node, move it + if (primaryParentRef.getParentRef().equals(sourceNodeRef)) + { + connector.getNodeService().moveNode(nodeRef, targetNodeRef, + primaryParentRef.getTypeQName(), primaryParentRef.getQName()); + } else + { + // otherwise, reparent it + for (ChildAssociationRef parent : connector.getNodeService().getParentAssocs(nodeRef, + ContentModel.ASSOC_CONTAINS, RegexQNamePattern.MATCH_ALL)) + { + if (parent.getParentRef().equals(sourceNodeRef)) + { + connector.getNodeService().removeChildAssociation(parent); + connector.getNodeService().addChild(targetNodeRef, nodeRef, + ContentModel.ASSOC_CONTAINS, parent.getQName()); + return null; + } + } + throw new CMISInvalidArgumentException( + "Document is not a child of the source folder that was specified!"); + } + + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + @Override + public void updateProperties(String repositoryId, Holder objectId, Holder changeToken, + final Properties properties, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + final ObjectVariantEnum variant = connector.getObjectVariant(objectId.getValue()); + connector.throwCommonExceptions(variant, "Object", objectId.getValue()); + + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisInvalidArgumentException("Relationship properties cannot be updated!"); + } else + { + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId.getValue()); + final TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + // run transaction + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.setProperties(nodeRef, type, properties, new String[0]); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + objectId.setValue(connector.createObjectId(nodeRef)); + } + } + + @Override + public void deleteObject(String repositoryId, String objectId, Boolean allVersions, ExtensionsData extension) + { + deleteObjectOrCancelCheckOut(repositoryId, objectId, allVersions, extension); + } + + @Override + public void deleteObjectOrCancelCheckOut(String repositoryId, final String objectId, final Boolean allVersions, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + final ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + // run transaction + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Boolean execute() throws Exception + { + try + { + // handle relationships + if (variant == ObjectVariantEnum.ASSOC) + { + AssociationRef assocRef = connector.getAssociationRef(objectId); + connector.getNodeService().removeAssociation(assocRef.getSourceRef(), + assocRef.getTargetRef(), assocRef.getTypeQName()); + return true; + } + + NodeRef nodeRef = connector.getNodeRef(objectId); + + // handle PWC + if (variant == ObjectVariantEnum.PWC) + { + connector.getCheckOutCheckInService().cancelCheckout(nodeRef); + return true; + } + + TypeDefinitionWrapper type = connector.getType(nodeRef); + + // handle folders + if (type instanceof FolderTypeDefintionWrapper) + { + if (connector.getNodeService().getChildAssocs(nodeRef).size() > 0) + { + throw new CmisConstraintException("Could not delete folder with at least one Child"); + } + + connector.getNodeService().deleteNode(nodeRef); + return true; + } + + // handle versions + if (allVersions) + { + NodeRef workingCopy = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); + if (workingCopy != null) + { + connector.getCheckOutCheckInService().cancelCheckout(workingCopy); + } + } else if (variant == ObjectVariantEnum.VERSION) + { + Version version = connector.getVersion(objectId); + connector.getVersionService().deleteVersion(nodeRef, version); + return true; + } + + // remove not primary parent associations + List childAssociations = connector.getNodeService().getParentAssocs( + nodeRef); + if (childAssociations != null) + { + for (ChildAssociationRef childAssoc : childAssociations) + { + if (!childAssoc.isPrimary()) + { + connector.getNodeService().removeChildAssociation(childAssoc); + } + } + } + + // attempt to delete the node + connector.getNodeService().deleteNode(nodeRef); + return true; + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + endReadOnlyTransaction(); + } + + @Override + public FailedToDeleteData deleteTree(String repositoryId, String folderId, Boolean allVersions, + UnfileObject unfileObjects, final Boolean continueOnFailure, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (!allVersions) + { + throw new CmisInvalidArgumentException("Only allVersions=true supported!"); + } + + if (unfileObjects == UnfileObject.UNFILE) + { + throw new CmisInvalidArgumentException("Unfiling not supported!"); + } + + final NodeRef folderNodeRef = connector.getFolderNodeRef("Folder", folderId); + final FailedToDeleteDataImpl result = new FailedToDeleteDataImpl(); + + // run transaction + endReadOnlyTransaction(); + result.setIds(connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback>() + { + public List execute() throws Exception + { + return deleteBranch(folderNodeRef, continueOnFailure); + }; + }, false, true)); + beginReadOnlyTransaction(); + + return result; + } + + private List deleteBranch(NodeRef nodeRef, boolean continueOnFailure) + { + List result = new ArrayList(); + + try + { + // remove children + List childrenList = connector.getNodeService().getChildAssocs(nodeRef); + if (childrenList != null) + { + for (ChildAssociationRef child : childrenList) + { + List ftod = deleteBranch(child.getChildRef(), continueOnFailure); + if (!ftod.isEmpty()) + { + result.addAll(ftod); + if (!continueOnFailure) + { + return result; + } + } + } + } + + // remove not primary parent associations + List childAssociations = connector.getNodeService().getParentAssocs(nodeRef); + if (childAssociations != null) + { + for (ChildAssociationRef childAssoc : childAssociations) + { + if (!childAssoc.isPrimary()) + { + connector.getNodeService().removeChildAssociation(childAssoc); + } + } + } + + // attempt to delete the node + connector.getNodeService().deleteNode(nodeRef); + } catch (Exception e) + { + result.add(nodeRef.toString()); + } + + return result; + } + + @Override + public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, + Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + // create a CMIS object + if (variant == ObjectVariantEnum.ASSOC) + { + AssociationRef assocRef = connector.getAssociationRef(objectId); + return connector.createCMISObject(assocRef, filter, includeAllowableActions, includeRelationships, + renditionFilter, includePolicyIds, includeAcl); + } else + { + NodeRef nodeRef = connector.getNodeRef(objectId); + return connector.createCMISObject(nodeRef, filter, includeAllowableActions, includeRelationships, + renditionFilter, includePolicyIds, includeAcl); + } + } + + @Override + public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, + Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // start at the root node + NodeRef rootNodeRef = connector.getRootNodeRef(); + NodeRef nodeRef = null; + + if (path.equals("/")) + { + nodeRef = rootNodeRef; + } else + { + try + { + // resolve path and get the node ref + FileInfo info = connector.getFileFolderService().resolveNamePath(rootNodeRef, + Arrays.asList(path.substring(1).split("/"))); + nodeRef = info.getNodeRef(); + } catch (FileNotFoundException e) + { + throw new CmisObjectNotFoundException("Object not found: " + path); + } + } + + // create the CMIS object + return connector.createCMISObject(nodeRef, filter, includeAllowableActions, includeRelationships, + renditionFilter, includePolicyIds, includeAcl); + } + + @Override + public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + if (variant == ObjectVariantEnum.ASSOC) + { + AssociationRef assocRef = connector.getAssociationRef(objectId); + TypeDefinitionWrapper type = connector.getType(assocRef); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + return connector.getAssocProperties(assocRef, filter, type); + } else + { + NodeRef nodeRef = connector.getNodeRef(objectId); + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + return connector.getNodeProperties(nodeRef, filter, type); + } + } + + @Override + public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + TypeDefinitionWrapper type = null; + EntityRef ref = null; + + if (variant == ObjectVariantEnum.ASSOC) + { + AssociationRef assocRef = connector.getAssociationRef(objectId); + type = connector.getType(assocRef); + ref = assocRef; + } else + { + NodeRef nodeRef = connector.getNodeRef(objectId); + type = connector.getType(nodeRef); + ref = nodeRef; + } + + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + return connector.getAllowableActions(type, ref); + } + + @Override + public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset, + BigInteger length, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + // relationships cannot have content + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisInvalidArgumentException("Object is a relationship and cannot have content!"); + } + + // now get it + NodeRef nodeRef = connector.getNodeRef(objectId); + return connector.getContentStream(nodeRef, streamId, offset, length); + } + + @Override + public List getRenditions(String repositoryId, String objectId, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + if (variant == ObjectVariantEnum.ASSOC) + { + return Collections.emptyList(); + } else + { + NodeRef nodeRef = connector.getNodeRef(objectId); + return connector.getRendtions(nodeRef, renditionFilter, maxItems, skipCount); + } + } + + // --- versioning service --- + + @Override + public void checkOut(String repositoryId, final Holder objectId, ExtensionsData extension, + final Holder contentCopied) + { + checkRepositoryId(repositoryId); + + ObjectVariantEnum variant = connector.getObjectVariant(objectId.getValue()); + connector.throwCommonExceptions(variant, "Object", objectId.getValue()); + + // relationships cannot be checked out + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisInvalidArgumentException("Unable to check-out a relationship!"); + } + + // get object + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Document", objectId.getValue()); + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + if (!(type instanceof DocumentTypeDefinitionWrapper)) + { + throw new CmisInvalidArgumentException("Object is not a document!"); + } + + if (!((DocumentTypeDefinition) type.getTypeDefinition(false)).isVersionable()) + { + throw new CmisConstraintException("Document is not versionable!"); + } + + // check out + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + NodeRef pwcNodeRef = connector.getCheckOutCheckInService().checkout(nodeRef); + objectId.setValue(pwcNodeRef.toString()); + if (contentCopied != null) + { + contentCopied.setValue(connector.getFileFolderService().getReader(pwcNodeRef) != null); + } + return null; + } catch (CheckOutCheckInServiceException e) + { + throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); + + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + @Override + public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + // only accept a PWC + if (variant != ObjectVariantEnum.PWC) + { + throw new CmisVersioningException("Object is not a PWC!"); + } + + // get object + final NodeRef nodeRef = connector.getNodeRef(objectId); + + // cancel check out + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.getCheckOutCheckInService().cancelCheckout(nodeRef); + return null; + } catch (CheckOutCheckInServiceException e) + { + throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); + + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + @Override + public void checkIn(String repositoryId, final Holder objectId, final Boolean major, + final Properties properties, final ContentStream contentStream, final String checkinComment, + final List policies, final Acl addAces, final Acl removeAces, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + ObjectVariantEnum variant = connector.getObjectVariant(objectId.getValue()); + connector.throwCommonExceptions(variant, "Object", objectId.getValue()); + + // only accept a PWC + if (variant != ObjectVariantEnum.PWC) + { + throw new CmisVersioningException("Object is not a PWC!"); + } + + // get object + final NodeRef nodeRef = connector.getNodeRef(objectId.getValue()); + final TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + // copy stream to temp file + final File tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); + + // check in + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + // update PWC + connector.setProperties(nodeRef, type, properties, + new String[] { PropertyIds.OBJECT_TYPE_ID }); + connector.applyPolicies(nodeRef, type, policies); + connector.applyACL(nodeRef, type, addAces, removeAces); + + // handle content + if (contentStream != null) + { + // write content + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(contentStream.getMimeType()); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + + // check aspect + if (connector.getNodeService().hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) + { + Map props = new HashMap(); + props.put(ContentModel.PROP_INITIAL_VERSION, false); + props.put(ContentModel.PROP_AUTO_VERSION, false); + connector.getNodeService().addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); + } + + // create version properties + Map versionProperties = new HashMap(5); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, major ? VersionType.MAJOR + : VersionType.MINOR); + if (checkinComment != null) + { + versionProperties.put(VersionModel.PROP_DESCRIPTION, checkinComment); + } + + // check in + NodeRef newNodeRef = connector.getCheckOutCheckInService().checkin(nodeRef, + versionProperties); + + objectId.setValue(connector.createObjectId(newNodeRef)); + + return null; + } catch (FileExistsException fee) + { + removeTempFile(tempFile); + throw new CmisContentAlreadyExistsException("An object with this name already exists!", fee); + } catch (IntegrityException ie) + { + removeTempFile(tempFile); + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (CheckOutCheckInServiceException e) + { + removeTempFile(tempFile); + throw new CmisVersioningException("Check out failed: " + e.getMessage(), e); + + } catch (AccessDeniedException ade) + { + removeTempFile(tempFile); + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + removeTempFile(tempFile); + } + + @Override + public List getAllVersions(String repositoryId, String objectId, String versionSeriesId, String filter, + Boolean includeAllowableActions, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (objectId != null) + { + // it's an AtomPub call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + if (versionSeriesId == null) + { + throw new CmisInvalidArgumentException("Object Id or Object Series Id must be set!"); + } + + List result = new ArrayList(); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(versionSeriesId); + connector.throwCommonExceptions(variant, "Version Series", versionSeriesId); + + if (variant == ObjectVariantEnum.ASSOC) + { + // the relationship history is always empty + return result; + } + + if (variant != ObjectVariantEnum.NODE) + { + // the version series id is the id of current version, which is a + // node + throw new CmisInvalidArgumentException("Version Series does not exist!"); + } + + // get current version and it's history + NodeRef nodeRef = connector.getNodeRef(versionSeriesId); + + VersionHistory versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + + if (versionHistory == null) + { + // add current version + result.add(connector.createCMISObject(nodeRef, filter, includeAllowableActions, IncludeRelationships.NONE, + CMISConnector.RENDITION_NONE, false, false)); + } else + { + NodeRef pwcNodeRef = connector.getCheckOutCheckInService().getWorkingCopy(nodeRef); + if (pwcNodeRef != null) + { + result.add(connector.createCMISObject(pwcNodeRef, filter, includeAllowableActions, + IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); + } + + // convert the version history + for (Version version : versionHistory.getAllVersions()) + { + result.add(connector.createCMISObject(version.getFrozenStateNodeRef(), filter, includeAllowableActions, + IncludeRelationships.NONE, CMISConnector.RENDITION_NONE, false, false)); + } + } + + return result; + } + + @Override + public ObjectData getObjectOfLatestVersion(String repositoryId, String objectId, String versionSeriesId, + Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, + String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (objectId != null) + { + // it's an AtomPub call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(versionSeriesId); + connector.throwCommonExceptions(variant, "Version series", versionSeriesId); + + // create a CMIS object + if (variant == ObjectVariantEnum.ASSOC) + { + AssociationRef assocRef = connector.getAssociationRef(versionSeriesId); + return connector.createCMISObject(assocRef, filter, includeAllowableActions, includeRelationships, + renditionFilter, includePolicyIds, includeAcl); + } else + { + NodeRef nodeRef = connector.getLatestVersionNodeRef(versionSeriesId, major); + return connector.createCMISObject(nodeRef, filter, includeAllowableActions, includeRelationships, + renditionFilter, includePolicyIds, includeAcl); + } + } + + @Override + public Properties getPropertiesOfLatestVersion(String repositoryId, String objectId, String versionSeriesId, + Boolean major, String filter, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (objectId != null) + { + // it's an AtomPub call + versionSeriesId = connector.getCurrentVersionId(objectId); + } + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(versionSeriesId); + connector.throwCommonExceptions(variant, "Version series", versionSeriesId); + + if (variant == ObjectVariantEnum.ASSOC) + { + AssociationRef assocRef = connector.getAssociationRef(versionSeriesId); + TypeDefinitionWrapper type = connector.getType(assocRef); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + return connector.getAssocProperties(assocRef, filter, type); + } else + { + NodeRef nodeRef = connector.getLatestVersionNodeRef(versionSeriesId, major); + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + return connector.getNodeProperties(nodeRef, filter, type); + } + } + + // --- multifiling service --- + + @Override + public void addObjectToFolder(String repositoryId, String objectId, String folderId, Boolean allVersions, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (!allVersions) + { + throw new CmisInvalidArgumentException("Only allVersions=true supported!"); + } + + // get node ref + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + if (variant != ObjectVariantEnum.NODE) + { + throw new CmisInvalidArgumentException("Object is not a current version of a document!"); + } + + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId); + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + if (!(type instanceof DocumentTypeDefinitionWrapper)) + { + throw new CmisInvalidArgumentException("Object is not a document!"); + } + + // get the folder node ref + final NodeRef folderNodeRef = connector.getFolderNodeRef("Folder", folderId); + + connector.checkChildObjectType(folderNodeRef, type.getTypeId()); + + final QName name = QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName((String) connector.getNodeService().getProperty(nodeRef, + ContentModel.PROP_NAME))); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.getNodeService().addChild(folderNodeRef, nodeRef, ContentModel.ASSOC_CONTAINS, + name); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + @Override + public void removeObjectFromFolder(String repositoryId, String objectId, String folderId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // get node ref + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + if (variant != ObjectVariantEnum.NODE) + { + throw new CmisInvalidArgumentException("Object is not a current version of a document!"); + } + + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId); + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + if (!(type instanceof DocumentTypeDefinitionWrapper)) + { + throw new CmisInvalidArgumentException("Object is not a document!"); + } + + // get the folder node ref + final NodeRef folderNodeRef = connector.getFolderNodeRef("Folder", folderId); + + // check primary parent + if (connector.getNodeService().getPrimaryParent(nodeRef).getParentRef().equals(folderNodeRef)) + { + throw new CmisConstraintException( + "Unfiling from primary parent folder is not supported! Use deleteObject() instead."); + } + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.getNodeService().removeChild(folderNodeRef, nodeRef); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + } + + // --- discovery service --- + + @Override + public ObjectList getContentChanges(String repositoryId, Holder changeLogToken, Boolean includeProperties, + String filter, Boolean includePolicyIds, Boolean includeAcl, BigInteger maxItems, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + return connector.getContentChanges(changeLogToken, maxItems); + } + + @Override + public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions, + Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (searchAllVersions.booleanValue()) + { + throw new CmisInvalidArgumentException("Search all version is not supported!"); + } + + return connector.query(statement, includeAllowableActions, includeRelationships, renditionFilter, maxItems, + skipCount); + } + + // --- relationship service --- + + @Override + public ObjectList getObjectRelationships(String repositoryId, String objectId, Boolean includeSubRelationshipTypes, + RelationshipDirection relationshipDirection, String typeId, String filter, Boolean includeAllowableActions, + BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + // create a CMIS object + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisInvalidArgumentException("Object is a relationship!"); + } + + NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId); + return connector.getObjectRelationships(nodeRef, relationshipDirection, typeId, filter, + includeAllowableActions, maxItems, skipCount); + } + + // --- policy service --- + + @Override + public void applyPolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId); + TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + connector.applyPolicies(nodeRef, type, Collections.singletonList(policyId)); + } + + @Override + public void removePolicy(String repositoryId, String policyId, String objectId, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId); + connector.getAndCheckType(nodeRef); + + throw new CmisConstraintException("Object is not policy controllable!"); + } + + @Override + public List getAppliedPolicies(String repositoryId, String objectId, String filter, + ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + // what kind of object is it? + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + NodeRef nodeRef = connector.getNodeRef(objectId); + connector.getAndCheckType(nodeRef); + + // policies are not supported -> return empty list + return Collections.emptyList(); + } + + // --- ACL service --- + + @Override + public Acl applyAcl(String repositoryId, String objectId, final Acl addAces, final Acl removeAces, + AclPropagation aclPropagation, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + if (aclPropagation == AclPropagation.OBJECTONLY) + { + throw new CmisInvalidArgumentException("ACL propagation 'objectonly' is not supported!"); + } + + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + // relationships don't have ACLs + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Object id", objectId); + final TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.applyACL(nodeRef, type, addAces, removeAces); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return connector.getACL(nodeRef, false); + } + + @Override + public Acl applyAcl(String repositoryId, String objectId, final Acl aces, AclPropagation aclPropagation) + { + checkRepositoryId(repositoryId); + + if (aclPropagation == AclPropagation.OBJECTONLY) + { + throw new CmisInvalidArgumentException("ACL propagation 'objectonly' is not supported!"); + } + + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + // relationships don't have ACLs + if (variant == ObjectVariantEnum.ASSOC) + { + throw new CmisConstraintException("Relationships are not ACL controllable!"); + } + + final NodeRef nodeRef = connector.getNodeRefIfCurrent("Object", objectId); + final TypeDefinitionWrapper type = connector.getAndCheckType(nodeRef); + + endReadOnlyTransaction(); + connector.getTransactionService().getRetryingTransactionHelper() + .doInTransaction(new RetryingTransactionCallback() + { + public Object execute() throws Exception + { + try + { + connector.applyACL(nodeRef, type, aces); + return null; + } catch (IntegrityException ie) + { + throw new CmisConstraintException("Constraint violation: " + ie.getMessage(), ie); + } catch (AccessDeniedException ade) + { + throw new CmisPermissionDeniedException("Permission denied!", ade); + } + }; + }, false, true); + beginReadOnlyTransaction(); + + return connector.getACL(nodeRef, false); + } + + @SuppressWarnings("unchecked") + @Override + public Acl getAcl(String repositoryId, String objectId, Boolean onlyBasicPermissions, ExtensionsData extension) + { + checkRepositoryId(repositoryId); + + ObjectVariantEnum variant = connector.getObjectVariant(objectId); + connector.throwCommonExceptions(variant, "Object", objectId); + + // relationships don't have ACLs + if (variant == ObjectVariantEnum.ASSOC) + { + return new AccessControlListImpl(Collections.EMPTY_LIST); + } + + // get the ACL + String currentVersionId = connector.getCurrentVersionId(objectId); + NodeRef nodeRef = connector.getNodeRef(currentVersionId); + return connector.getACL(nodeRef, onlyBasicPermissions); + } + + // -------------------------------------------------------- + + /** + * Collects the {@link ObjectInfo} about an object. + * + * (Provided by OpenCMIS, but optimized for Alfresco.) + */ + protected ObjectInfo getObjectInfoIntern(String repositoryId, ObjectData object) + { + // if the object has no properties, stop here + if (object.getProperties() == null || object.getProperties().getProperties() == null) + { + throw new CmisRuntimeException("No properties!"); + } + + ObjectInfoImpl info = new ObjectInfoImpl(); + + // general properties + info.setObject(object); + info.setId(object.getId()); + info.setName(getStringProperty(object, PropertyIds.NAME)); + info.setCreatedBy(getStringProperty(object, PropertyIds.CREATED_BY)); + info.setCreationDate(getDateTimeProperty(object, PropertyIds.CREATED_BY)); + info.setLastModificationDate(getDateTimeProperty(object, PropertyIds.LAST_MODIFICATION_DATE)); + info.setTypeId(getIdProperty(object, PropertyIds.OBJECT_TYPE_ID)); + info.setBaseType(object.getBaseTypeId()); + + if (object.getBaseTypeId() == BaseTypeId.CMIS_RELATIONSHIP) + { + // versioning + info.setIsCurrentVersion(false); + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + info.setVersionSeriesId(getIdProperty(object, PropertyIds.VERSION_SERIES_ID)); + info.setIsCurrentVersion(true); + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + + // content + info.setHasContent(false); + info.setContentType(null); + info.setFileName(null); + + // parent + info.setHasParent(false); + + // policies and relationships + info.setSupportsRelationships(false); + info.setSupportsPolicies(false); + + // renditions + info.setRenditionInfos(null); + + // relationships + info.setRelationshipSourceIds(null); + info.setRelationshipTargetIds(null); + + // global settings + info.setHasAcl(false); + info.setSupportsDescendants(false); + info.setSupportsFolderTree(false); + } else + { + // versioning + info.setIsCurrentVersion(object.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT); + info.setWorkingCopyId(null); + info.setWorkingCopyOriginalId(null); + info.setVersionSeriesId(null); + + if (object.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT) + { + info.setVersionSeriesId(getIdProperty(object, PropertyIds.VERSION_SERIES_ID)); + if (info.getVersionSeriesId() != null) + { + String versionLabel = getStringProperty(object, PropertyIds.VERSION_LABEL); + if (CMISConnector.PWC_VERSION_LABEL.equals(versionLabel)) + { + info.setIsCurrentVersion(false); + info.setWorkingCopyId(null); + + // get latest version + List versions = getAllVersions(repositoryId, null, info.getVersionSeriesId(), null, + Boolean.FALSE, null); + if (versions != null && versions.size() > 0) + { + info.setWorkingCopyOriginalId(versions.get(0).getId()); + } + } else + { + Boolean isLatest = getBooleanProperty(object, PropertyIds.IS_LATEST_VERSION); + info.setIsCurrentVersion(isLatest == null ? true : isLatest.booleanValue()); + + Boolean isCheckedOut = getBooleanProperty(object, PropertyIds.IS_VERSION_SERIES_CHECKED_OUT); + if (isCheckedOut != null && isCheckedOut.booleanValue()) + { + info.setWorkingCopyId(getIdProperty(object, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID)); + + // get latest version + List versions = getAllVersions(repositoryId, object.getId(), + info.getVersionSeriesId(), null, Boolean.FALSE, null); + if (versions != null && versions.size() > 0) + { + info.setWorkingCopyOriginalId(versions.get(0).getId()); + } + } + } + } + } + + // content + info.setHasContent(false); + info.setContentType(null); + info.setFileName(null); + + if (object.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT) + { + String fileName = getStringProperty(object, PropertyIds.CONTENT_STREAM_FILE_NAME); + String mimeType = getStringProperty(object, PropertyIds.CONTENT_STREAM_MIME_TYPE); + String streamId = getIdProperty(object, PropertyIds.CONTENT_STREAM_ID); + BigInteger length = getIntegerProperty(object, PropertyIds.CONTENT_STREAM_LENGTH); + boolean hasContent = fileName != null || mimeType != null || streamId != null || length != null; + if (hasContent) + { + info.setHasContent(hasContent); + info.setContentType(mimeType); + info.setFileName(fileName); + } + } + + // parent + info.setHasParent(true); + + if (object.getBaseTypeId() == BaseTypeId.CMIS_FOLDER) + { + List parents = getObjectParents(repositoryId, object.getId(), null, Boolean.FALSE, + IncludeRelationships.NONE, "cmis:none", Boolean.FALSE, null); + info.setHasParent(parents.size() > 0); + } + + // policies and relationships + info.setSupportsRelationships(true); + info.setSupportsPolicies(true); + + // renditions + info.setRenditionInfos(null); + + if (object.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT) + { + List renditions = object.getRenditions(); + if (renditions != null && renditions.size() > 0) + { + List renditionInfos = new ArrayList(); + for (RenditionData rendition : renditions) + { + RenditionInfoImpl renditionInfo = new RenditionInfoImpl(); + renditionInfo.setId(rendition.getStreamId()); + renditionInfo.setKind(rendition.getKind()); + renditionInfo.setContentType(rendition.getMimeType()); + renditionInfo.setTitle(rendition.getTitle()); + renditionInfo.setLength(rendition.getBigLength()); + renditionInfos.add(renditionInfo); + } + info.setRenditionInfos(renditionInfos); + } + } + + // relationships + info.setRelationshipSourceIds(null); + info.setRelationshipTargetIds(null); + List relationships = object.getRelationships(); + if (relationships != null && relationships.size() > 0) + { + List sourceIds = new ArrayList(); + List targetIds = new ArrayList(); + for (ObjectData relationship : relationships) + { + String sourceId = getIdProperty(relationship, PropertyIds.SOURCE_ID); + String targetId = getIdProperty(relationship, PropertyIds.TARGET_ID); + if (object.getId().equals(sourceId)) + { + sourceIds.add(relationship.getId()); + } + if (object.getId().equals(targetId)) + { + targetIds.add(relationship.getId()); + } + } + if (sourceIds.size() > 0) + { + info.setRelationshipSourceIds(sourceIds); + } + if (targetIds.size() > 0) + { + info.setRelationshipTargetIds(targetIds); + } + } + + // global settings + info.setHasAcl(true); + info.setSupportsDescendants(true); + info.setSupportsFolderTree(true); + } + + return info; + } + + // -------------------------------------------------------- + + private void checkRepositoryId(String repositoryId) + { + if (!connector.getRepositoryInfo().getId().equals(repositoryId)) + { + throw new CmisObjectNotFoundException("Unknown repository '" + repositoryId + "'!"); + } + } + + private Charset getEncoding(File tempFile, String mimeType) + { + Charset encoding = null; + + try + { + InputStream tfis = new BufferedInputStream(new FileInputStream(tempFile)); + ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder(); + encoding = charsetFinder.getCharset(tfis, mimeType); + tfis.close(); + } catch (Exception e) + { + throw new CmisStorageException("Unable to read content: " + e.getMessage(), e); + } + + return encoding; + } + + private File copyToTempFile(ContentStream contentStream) + { + File tempFile = null; + + if (contentStream == null) + { + return tempFile; + } + + int bufferSize = 40 * 1014; + + try + { + tempFile = TempFileProvider.createTempFile("cmis", "content"); + if (contentStream.getStream() != null) + { + OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile), bufferSize); + InputStream in = new BufferedInputStream(contentStream.getStream(), bufferSize); + + byte[] buffer = new byte[bufferSize]; + int i; + while ((i = in.read(buffer)) > -1) + { + out.write(buffer, 0, i); + } + + in.close(); + out.close(); + } + } catch (Exception e) + { + throw new CmisStorageException("Unable to store content: " + e.getMessage(), e); + } + + return tempFile; + } + + private void removeTempFile(File tempFile) + { + if (tempFile == null) + { + return; + } + + try + { + tempFile.delete(); + } catch (Exception e) + { + // ignore - file will be removed by TempFileProvider + } + } +} diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java new file mode 100644 index 0000000000..43a3f8a576 --- /dev/null +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.util.Map; + +import org.apache.chemistry.opencmis.commons.impl.server.AbstractServiceFactory; +import org.apache.chemistry.opencmis.commons.server.CallContext; +import org.apache.chemistry.opencmis.commons.server.CmisService; +import org.apache.chemistry.opencmis.server.support.CmisServiceWrapper; + +/** + * Factory for OpenCMIS service objects. + * + * @author florian.mueller + */ +public class AlfrescoCmisServiceFactory extends AbstractServiceFactory +{ + private ThreadLocal> threadLocalService = new ThreadLocal>(); + + private CMISConnector connector; + + @Override + public void init(Map parameters) + { + } + + /** + * Sets the CMIS connector. + */ + public void setCmisConnector(CMISConnector connector) + { + this.connector = connector; + } + + @Override + public void destroy() + { + threadLocalService = null; + } + + @Override + public CmisService getService(CallContext context) + { + CmisServiceWrapper wrapperService = threadLocalService.get(); + if (wrapperService == null) + { + wrapperService = new CmisServiceWrapper(new AlfrescoCmisService(connector), + connector.getTypesDefaultMaxItems(), connector.getTypesDefaultDepth(), + connector.getObjectsDefaultMaxItems(), connector.getObjectsDefaultDepth()); + threadLocalService.set(wrapperService); + } + + wrapperService.getWrappedService().beginCall(context); + + return wrapperService; + } +} diff --git a/source/java/org/alfresco/opencmis/CMISActionEvaluator.java b/source/java/org/alfresco/opencmis/CMISActionEvaluator.java new file mode 100644 index 0000000000..08bc454c5c --- /dev/null +++ b/source/java/org/alfresco/opencmis/CMISActionEvaluator.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import org.apache.chemistry.opencmis.commons.enums.Action; + +public interface CMISActionEvaluator +{ + /** + * Gets the CMIS Allowed Action + * + * @return + */ + public Action getAction(); + + /** + * Determines if an action is allowed on an object + * + * @param object + * @return + */ + public boolean isAllowed(T object); +} diff --git a/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java b/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java new file mode 100644 index 0000000000..cd00c19a79 --- /dev/null +++ b/source/java/org/alfresco/opencmis/CMISChangeLogDataExtractor.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.io.Serializable; +import java.util.HashMap; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.repo.audit.extractor.AbstractDataExtractor; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; + +/** + * An extractor that allows to filter data using the following rule: Audit + * records should only be created for items in the CMIS domain model. + * + * @author Stas Sokolovsky + */ +public class CMISChangeLogDataExtractor extends AbstractDataExtractor +{ + public static final String KEY_NODE_REF = "nodeRef"; + public static final String KEY_OBJECT_ID = "objectId"; + + private NodeService nodeService; + private CMISDictionaryService cmisDictionaryService; + + /** + * Extracts relevant node refs and Ids from auditing data + * + * @see org.alfresco.repo.audit.extractor.DataExtractor.extractData(java.io. + * Serializable) + */ + public Serializable extractData(Serializable value) throws Throwable + { + NodeRef nodeRef = getNodeRef(value); + + QName typeQName = nodeService.getType(nodeRef); + TypeDefinitionWrapper type = cmisDictionaryService.findNodeType(typeQName); + + HashMap result = new HashMap(5); + result.put(KEY_NODE_REF, nodeRef); + // Support version nodes by recording the object ID + result.put(KEY_OBJECT_ID, type.getPropertyById(PropertyIds.OBJECT_ID).getPropertyAccessor().getValue(nodeRef)); + + return result; + } + + /** + * @return Returns true if items in the CMIS domain model + * @see org.alfresco.repo.audit.extractor.DataExtractor.isSupported(java.io. + * Serializable) + */ + public boolean isSupported(Serializable data) + { + if (data != null) + { + NodeRef nodeRef = getNodeRef(data); + if (nodeRef != null) + { + QName typeQName = nodeService.getType(nodeRef); + TypeDefinitionWrapper type = cmisDictionaryService.findNodeType(typeQName); + + return (type != null) + && (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT || type.getBaseTypeId() == BaseTypeId.CMIS_FOLDER); + } + } + return false; + } + + /** + * Gets the NodeRef from auditing data + * + * @param data + * audit data + * @return Node Reference + */ + private NodeRef getNodeRef(Serializable data) + { + NodeRef nodeRef = null; + if (data instanceof FileInfo) + { + nodeRef = ((FileInfo) data).getNodeRef(); + } else if (data instanceof NodeRef) + { + nodeRef = (NodeRef) data; + } + return nodeRef; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) + { + this.cmisDictionaryService = cmisDictionaryService; + } +} diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java new file mode 100644 index 0000000000..87bb09747c --- /dev/null +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -0,0 +1,2909 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.dictionary.CMISAllowedActionEnum; +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.DocumentTypeDefinitionWrapper; +import org.alfresco.opencmis.dictionary.FolderTypeDefintionWrapper; +import org.alfresco.opencmis.dictionary.PropertyDefintionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.opencmis.search.CMISQueryOptions; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; +import org.alfresco.opencmis.search.CMISQueryService; +import org.alfresco.opencmis.search.CMISResultSet; +import org.alfresco.opencmis.search.CMISResultSetColumn; +import org.alfresco.opencmis.search.CMISResultSetRow; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.security.permissions.PermissionReference; +import org.alfresco.repo.security.permissions.impl.AccessPermissionImpl; +import org.alfresco.repo.security.permissions.impl.ModelDAO; +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantDeployer; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.version.VersionBaseModel; +import org.alfresco.repo.version.VersionModel; +import org.alfresco.service.cmr.audit.AuditQueryParameters; +import org.alfresco.service.cmr.audit.AuditService; +import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.InvalidAspectException; +import org.alfresco.service.cmr.model.FileExistsException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.AspectMissingException; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.EntityRef; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.Path.ChildAssocElement; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionDoesNotExistException; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.version.VersionType; +import org.alfresco.service.descriptor.Descriptor; +import org.alfresco.service.descriptor.DescriptorService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.service.transaction.TransactionService; +import org.apache.chemistry.opencmis.commons.BasicPermissions; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.data.Ace; +import org.apache.chemistry.opencmis.commons.data.Acl; +import org.apache.chemistry.opencmis.commons.data.AllowableActions; +import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement; +import org.apache.chemistry.opencmis.commons.data.ContentStream; +import org.apache.chemistry.opencmis.commons.data.ObjectData; +import org.apache.chemistry.opencmis.commons.data.ObjectList; +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.PropertyId; +import org.apache.chemistry.opencmis.commons.data.PropertyString; +import org.apache.chemistry.opencmis.commons.data.RenditionData; +import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; +import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition; +import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; +import org.apache.chemistry.opencmis.commons.enums.AclPropagation; +import org.apache.chemistry.opencmis.commons.enums.Action; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.CapabilityAcl; +import org.apache.chemistry.opencmis.commons.enums.CapabilityChanges; +import org.apache.chemistry.opencmis.commons.enums.CapabilityContentStreamUpdates; +import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; +import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; +import org.apache.chemistry.opencmis.commons.enums.CapabilityRenditions; +import org.apache.chemistry.opencmis.commons.enums.Cardinality; +import org.apache.chemistry.opencmis.commons.enums.ChangeType; +import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; +import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; +import org.apache.chemistry.opencmis.commons.enums.RelationshipDirection; +import org.apache.chemistry.opencmis.commons.enums.SupportedPermissions; +import org.apache.chemistry.opencmis.commons.enums.Updatability; +import org.apache.chemistry.opencmis.commons.enums.VersioningState; +import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisStreamNotSupportedException; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyData; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AclCapabilitiesDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChangeEventInfoDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.CmisExtensionElementImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionDefinitionDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PermissionMappingDataImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryCapabilitiesImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.RepositoryInfoImpl; +import org.apache.chemistry.opencmis.commons.spi.Holder; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ApplicationContextEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * Bridge connecting Alfresco and OpenCMIS. + * + * @author florian.mueller + */ +public class CMISConnector implements ApplicationContextAware, ApplicationListener, + TenantDeployer +{ + public static final char ID_SEPERATOR = ';'; + public static final String ASSOC_ID_PREFIX = "assoc:"; + public static final String PWC_VERSION_LABEL = "pwc"; + public static final String UNVERSIONED_VERSION_LABEL = "1.0"; + + public static final String RENDITION_NONE = "cmis:none"; + + public static final String CMIS_CHANGELOG_AUDIT_APPLICATION = "CMISChangeLog"; + + public static final String ALFRESCO_EXTENSION_NAMESPACE = "http://www.alfresco.org"; + public static final String CMIS_NAMESPACE = "http://docs.oasis-open.org/ns/cmis/core/200908/"; + public static final String ASPECTS = "aspects"; + public static final String SET_ASPECTS = "setAspects"; + public static final String APPLIED_ASPECTS = "appliedAspects"; + public static final String ASPECTS_TO_ADD = "aspectsToAdd"; + public static final String ASPECTS_TO_REMOVE = "aspectsToRemove"; + public static final String PROPERTIES = "properties"; + + private static final BigInteger TYPES_DEFAULT_MAX_ITEMS = BigInteger.valueOf(50); + private static final BigInteger TYPES_DEFAULT_DEPTH = BigInteger.valueOf(-1); + private static final BigInteger OBJECTS_DEFAULT_MAX_ITEMS = BigInteger.valueOf(200); + private static final BigInteger OBJECTS_DEFAULT_DEPTH = BigInteger.valueOf(10); + + private static final String QUERY_NAME_OBJECT_ID = "cmis:objectId"; + private static final String QUERY_NAME_OBJECT_TYPE_ID = "cmis:objectTypeId"; + private static final String QUERY_NAME_BASE_TYPE_ID = "cmis:baseTypeId"; + + // lifecycle + private ProcessorLifecycle lifecycle = new ProcessorLifecycle(); + + // Alfresco objects + private DescriptorService descriptorService; + private NodeService nodeService; + private VersionService versionService; + private CheckOutCheckInService checkOutCheckInService; + private ContentService contentService; + private RenditionService renditionService; + private FileFolderService fileFolderService; + private TenantAdminService tenantAdminService; + private TransactionService transactionService; + private AuthenticationService authenticationService; + private PermissionService permissionService; + private ModelDAO permissionModelDao; + private CMISDictionaryService cmisDictionaryService; + private CMISQueryService cmisQueryService; + private MimetypeService mimetypeService; + private AuditService auditService; + private NamespaceService namespaceService; + private SearchService searchService; + private DictionaryService dictionaryService; + + private StoreRef storeRef; + private String rootPath; + private Map> kindToRenditionNames; + private Map rootNodeRefs = new ConcurrentHashMap(1); + private Map renditionMapping = new ConcurrentHashMap(1); + + // OpenCMIS objects + private BigInteger typesDefaultMaxItems = TYPES_DEFAULT_MAX_ITEMS; + private BigInteger typesDefaultDepth = TYPES_DEFAULT_DEPTH; + private BigInteger objectsDefaultMaxItems = OBJECTS_DEFAULT_MAX_ITEMS; + private BigInteger objectsDefaultDepth = OBJECTS_DEFAULT_DEPTH; + + private List repositoryPermissions; + private Map permissionMappings; + + // -------------------------------------------------------------- + // Configuration + // -------------------------------------------------------------- + + /** + * Sets the root store. + * + * @param store + * store_type://store_id + */ + public void setStore(String store) + { + this.storeRef = new StoreRef(store); + } + + /** + * Sets the root path. + * + * @param path + * path within default store + */ + public void setRootPath(String path) + { + rootPath = path; + } + + public BigInteger getTypesDefaultMaxItems() + { + return typesDefaultMaxItems; + } + + public void setTypesDefaultMaxItems(BigInteger typesDefaultMaxItems) + { + this.typesDefaultMaxItems = typesDefaultMaxItems; + } + + public BigInteger getTypesDefaultDepth() + { + return typesDefaultDepth; + } + + public void setTypesDefaultDepth(BigInteger typesDefaultDepth) + { + this.typesDefaultDepth = typesDefaultDepth; + } + + public BigInteger getObjectsDefaultMaxItems() + { + return objectsDefaultMaxItems; + } + + public void setObjectsDefaultMaxItems(BigInteger objectsDefaultMaxItems) + { + this.objectsDefaultMaxItems = objectsDefaultMaxItems; + } + + public BigInteger getObjectsDefaultDepth() + { + return objectsDefaultDepth; + } + + public void setObjectsDefaultDepth(BigInteger objectsDefaultDepth) + { + this.objectsDefaultDepth = objectsDefaultDepth; + } + + /** + * Set rendition kind mapping. + */ + public void setRenditionKindMapping(Map> renditionKinds) + { + this.kindToRenditionNames = renditionKinds; + } + + /** + * Sets the descriptor service. + */ + public void setDescriptorService(DescriptorService descriptorService) + { + this.descriptorService = descriptorService; + } + + public DescriptorService getDescriptorService() + { + return descriptorService; + } + + /** + * Sets the node service. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public NodeService getNodeService() + { + return nodeService; + } + + /** + * Sets the version service. + */ + public void setVersionService(VersionService versionService) + { + this.versionService = versionService; + } + + public VersionService getVersionService() + { + return versionService; + } + + /** + * Sets the checkOut/checkIn service. + */ + public void setCheckOutCheckInService(CheckOutCheckInService checkOutCheckInService) + { + this.checkOutCheckInService = checkOutCheckInService; + } + + public CheckOutCheckInService getCheckOutCheckInService() + { + return checkOutCheckInService; + } + + /** + * Sets the content service. + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public ContentService getContentService() + { + return contentService; + } + + /** + * Sets the rendition service. + */ + public void setrenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + + /** + * Sets the file folder service. + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + public FileFolderService getFileFolderService() + { + return fileFolderService; + } + + /** + * Sets the tenant admin service. + */ + public void setTenantAdminService(TenantAdminService tenantAdminService) + { + this.tenantAdminService = tenantAdminService; + } + + /** + * Sets the transaction service. + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public TransactionService getTransactionService() + { + return transactionService; + } + + /** + * Sets the authentication service. + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public AuthenticationService getAuthenticationService() + { + return authenticationService; + } + + /** + * Sets the permission service. + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * Sets the permission model DAO. + */ + public void setPermissionModelDao(ModelDAO permissionModelDao) + { + this.permissionModelDao = permissionModelDao; + } + + public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) + { + this.cmisDictionaryService = cmisDictionaryService; + } + + /** + * Sets the OpenCMIS query service. + */ + public void setOpenCMISQueryService(CMISQueryService cmisQueryService) + { + this.cmisQueryService = cmisQueryService; + } + + public CMISDictionaryService getOpenCMISDictionaryService() + { + return cmisDictionaryService; + } + + /** + * Sets the MIME type service. + */ + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + public MimetypeService getMimetypeService() + { + return mimetypeService; + } + + /** + * Sets the audit service. + */ + public void setAuditService(AuditService auditService) + { + this.auditService = auditService; + } + + /** + * Sets the namespace service. + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * Sets the search service. + */ + public void setSearchService(SearchService searchService) + { + this.searchService = searchService; + } + + public SearchService getSearchService() + { + return searchService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public DictionaryService getDictionaryService() + { + return dictionaryService; + } + + // -------------------------------------------------------------- + // Lifecycle methods + // -------------------------------------------------------------- + + public void init() + { + // initialise root node ref + tenantAdminService.register(this); + + // set up rendition mapping + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + renditionMapping.put(tenantDomain, new CMISRenditionMapping(nodeService, contentService, renditionService, + transactionService, kindToRenditionNames)); + + // cache root node ref + getRootNodeRef(); + + // cache permission definitions and permission mappings + repositoryPermissions = getRepositoryPermissions(); + permissionMappings = getPermissionMappings(); + } + + public void destroy() + { + rootNodeRefs.remove(tenantAdminService.getCurrentUserDomain()); + } + + public void onEnableTenant() + { + init(); + } + + public void onDisableTenant() + { + destroy(); + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + lifecycle.setApplicationContext(applicationContext); + } + + public void onApplicationEvent(ApplicationContextEvent event) + { + lifecycle.onApplicationEvent(event); + } + + /** + * Hooks into Spring Application Lifecycle. + */ + private class ProcessorLifecycle extends AbstractLifecycleBean + { + @Override + protected void onBootstrap(ApplicationEvent event) + { + init(); + } + + @Override + protected void onShutdown(ApplicationEvent event) + { + } + } + + // -------------------------------------------------------------- + // Alfresco methods + // -------------------------------------------------------------- + + public StoreRef getRootStoreRef() + { + return getRootNodeRef().getStoreRef(); + } + + /** + * Returns the root folder node ref. + */ + public NodeRef getRootNodeRef() + { + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + NodeRef rootNodeRef = rootNodeRefs.get(tenantDomain); + if (rootNodeRef == null) + { + rootNodeRef = AuthenticationUtil.runAs(new RunAsWork() + { + public NodeRef doWork() throws Exception + { + return transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback() + { + public NodeRef execute() throws Exception + { + NodeRef root = nodeService.getRootNode(storeRef); + List rootNodes = searchService.selectNodes(root, rootPath, null, + namespaceService, false); + if (rootNodes.size() != 1) + { + throw new CmisRuntimeException("Unable to locate CMIS root path " + rootPath); + } + return rootNodes.get(0); + }; + }, true); + } + }, AuthenticationUtil.getSystemUserName()); + + if (rootNodeRef == null) + { + throw new CmisObjectNotFoundException("Root folder path '" + rootPath + "' not found!"); + } + + rootNodeRefs.put(tenantDomain, rootNodeRef); + } + + return rootNodeRef; + } + + public String getName(NodeRef nodeRef) + { + Object name = nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + return (name instanceof String ? (String) name : null); + } + + /** + * Cuts of the version information from an object id. + */ + public String getCurrentVersionId(String objectId) + { + if (objectId == null) + { + return null; + } + + int sepIndex = objectId.lastIndexOf(ID_SEPERATOR); + if (sepIndex > -1) + { + return objectId.substring(0, sepIndex); + } + + return objectId; + } + + /** + * Returns the variant of the object. + */ + public ObjectVariantEnum getObjectVariant(String objectId) + { + if (objectId == null) + { + return ObjectVariantEnum.INVALID_ID; + } + + try + { + String nodeRefString = objectId; + String versionString = null; + + // is it a version? + int sepIndex = objectId.lastIndexOf(ID_SEPERATOR); + if (sepIndex > -1) + { + nodeRefString = objectId.substring(0, sepIndex); + versionString = objectId.substring(sepIndex + 1); + } + + if (NodeRef.isNodeRef(nodeRefString)) + { + NodeRef nodeRef = new NodeRef(nodeRefString); + + // check for existence + if (!nodeService.exists(nodeRef)) + { + return ObjectVariantEnum.NOT_EXISTING; + } + + // check PWC + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY)) + { + return ObjectVariantEnum.PWC; + } + + if (versionString == null) + { + return ObjectVariantEnum.NODE; + } + + // check version + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); + if (versionHistory == null) + { + if (versionString.equals(UNVERSIONED_VERSION_LABEL)) + { + return ObjectVariantEnum.NODE; + } + return ObjectVariantEnum.NOT_EXISTING; + } + + try + { + versionHistory.getVersion(versionString); + return ObjectVariantEnum.VERSION; + } catch (VersionDoesNotExistException e) + { + return ObjectVariantEnum.NOT_EXISTING; + } + } else if (objectId.startsWith(ASSOC_ID_PREFIX)) + { + // check the association id + Long assocId = null; + try + { + assocId = new Long(objectId.substring(ASSOC_ID_PREFIX.length())); + } catch (NumberFormatException nfe) + { + return ObjectVariantEnum.INVALID_ID; + } + + // check the association + AssociationRef associationRef = nodeService.getAssoc(assocId); + if (associationRef == null) + { + return ObjectVariantEnum.NOT_EXISTING; + } + + return ObjectVariantEnum.ASSOC; + } else + { + return ObjectVariantEnum.INVALID_ID; + } + } catch (AccessDeniedException e) + { + return ObjectVariantEnum.PERMISSION_DENIED; + } + } + + public void throwCommonExceptions(ObjectVariantEnum variant, String what, String objectId) + { + switch (variant) + { + case INVALID_ID: + throw new CmisInvalidArgumentException(what + " id is invalid: " + objectId); + case NOT_EXISTING: + throw new CmisObjectNotFoundException(what + " not found: " + objectId); + case PERMISSION_DENIED: + throw new CmisPermissionDeniedException("Permission denied!"); + } + } + + /** + * Returns a node ref from an object id. + */ + public NodeRef getNodeRef(String objectId) + { + String nodeRefString = objectId; + String versionString = null; + + // is it a version? + int sepIndex = objectId.lastIndexOf(ID_SEPERATOR); + if (sepIndex > -1) + { + nodeRefString = objectId.substring(0, sepIndex); + versionString = objectId.substring(sepIndex + 1); + } + + NodeRef nodeRef = new NodeRef(nodeRefString); + if (versionString != null) + { + Version version = versionService.getCurrentVersion(nodeRef); + if (version == null) + { + if (versionString.equals(UNVERSIONED_VERSION_LABEL)) + { + return nodeRef; + } else + { + throw new CmisInvalidArgumentException("Invalid version: " + objectId); + } + } else + { + if (!versionString.equals(version.getVersionLabel())) + { + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); + version = versionHistory.getVersion(versionString); + + nodeRef = version.getFrozenStateNodeRef(); + } + } + } + + return nodeRef; + } + + /** + * Returns a node ref from a document id if its the current version. + */ + public NodeRef getNodeRefIfCurrent(String what, String objectId) + { + String nodeRefString = objectId; + String versionString = null; + + // is it a version? + int sepIndex = objectId.lastIndexOf(ID_SEPERATOR); + if (sepIndex > -1) + { + nodeRefString = objectId.substring(0, sepIndex); + versionString = objectId.substring(sepIndex + 1); + } + + NodeRef nodeRef = new NodeRef(nodeRefString); + if (versionString != null) + { + Version version = versionService.getCurrentVersion(nodeRef); + if (version == null) + { + if (versionString.equals(UNVERSIONED_VERSION_LABEL)) + { + return nodeRef; + } else + { + throw new CmisInvalidArgumentException(what + " id has an invalid version label: " + objectId); + } + } + + if (!version.getVersionLabel().equals(versionString)) + { + throw new CmisInvalidArgumentException(what + " id does not refer to the current version: " + objectId); + } + } + + return nodeRef; + } + + /** + * Returns an association ref from an object id. + */ + public AssociationRef getAssociationRef(String objectId) + { + Long assocId = new Long(objectId.substring(ASSOC_ID_PREFIX.length())); + return nodeService.getAssoc(assocId); + } + + /** + * Returns the node ref of the latest version. + */ + public NodeRef getLatestVersionNodeRef(String versionSeriesId, boolean major) + { + if (!major) + { + return getNodeRef(getCurrentVersionId(versionSeriesId)); + } + + NodeRef nodeRef = getNodeRef(versionSeriesId); + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); + + // if there is no history, return the current version + if (versionHistory == null) + { + // there are no versions + return getNodeRef(getCurrentVersionId(versionSeriesId)); + } + + // find the latest major version + for (Version version : versionHistory.getAllVersions()) + { + if (version.getVersionType() == VersionType.MAJOR) + { + return version.getFrozenStateNodeRef(); + } + } + + throw new CmisObjectNotFoundException("There is no major version!"); + } + + /** + * Returns the version to a given object id or null if it isn't + * a version. + */ + public Version getVersion(String objectId) + { + String nodeRefString = objectId; + String versionString = null; + + // is it a version? + int sepIndex = objectId.lastIndexOf(ID_SEPERATOR); + if (sepIndex > -1) + { + nodeRefString = objectId.substring(0, sepIndex); + versionString = objectId.substring(sepIndex + 1); + } else + { + return null; + } + + try + { + NodeRef nodeRef = new NodeRef(nodeRefString); + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); + return versionHistory.getVersion(versionString); + } catch (Exception e) + { + return null; + } + } + + /** + * Returns a folder node ref. If the given id is invalid or does no belong + * to a folder, an exception will be thrown. + */ + public NodeRef getFolderNodeRef(String what, String folderId) + { + if ((folderId == null) || (folderId.lastIndexOf(ID_SEPERATOR) > -1) || !NodeRef.isNodeRef(folderId)) + { + throwCommonExceptions(ObjectVariantEnum.INVALID_ID, what, folderId); + } + + NodeRef nodeRef = null; + try + { + nodeRef = new NodeRef(folderId); + + // check for existence + if (!nodeService.exists(nodeRef)) + { + throwCommonExceptions(ObjectVariantEnum.NOT_EXISTING, what, folderId); + } + + // check type + TypeDefinitionWrapper type = getType(nodeRef); + if (type == null) + { + throwCommonExceptions(ObjectVariantEnum.NOT_EXISTING, what, folderId); + } + + if (!(type instanceof FolderTypeDefintionWrapper)) + { + throw new CmisInvalidArgumentException(what + " is not a folder!"); + } + } catch (AccessDeniedException e) + { + throwCommonExceptions(ObjectVariantEnum.PERMISSION_DENIED, what, folderId); + } + + return nodeRef; + } + + /** + * Compiles a CMIS object if for a live node. + */ + public String createObjectId(NodeRef currentVersionNodeRef) + { + Serializable versionLabel = getNodeService() + .getProperty(currentVersionNodeRef, ContentModel.PROP_VERSION_LABEL); + if (versionLabel == null) + { + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + } + + return currentVersionNodeRef.toString() + CMISConnector.ID_SEPERATOR + versionLabel; + } + + /** + * Returns the type definition of a node or null if no type + * definition could be found. + */ + public TypeDefinitionWrapper getType(NodeRef nodeRef) + { + QName typeQName = nodeService.getType(nodeRef); + return cmisDictionaryService.findNodeType(typeQName); + } + + /** + * Returns the type definition of a node or throws a + * CmisObjectNotFoundException if no type definition could be found. + */ + public TypeDefinitionWrapper getAndCheckType(NodeRef nodeRef) + { + TypeDefinitionWrapper type = getType(nodeRef); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + return type; + } + + /** + * Returns the type definition of an association or null if no + * type definition could be found. + */ + public TypeDefinitionWrapper getType(AssociationRef assocRef) + { + QName typeQName = assocRef.getTypeQName(); + return cmisDictionaryService.findAssocType(typeQName); + } + + /** + * Returns the type definition of a node or null if no type + * definition could be found. + */ + public TypeDefinitionWrapper getType(String cmisTypeId) + { + return cmisDictionaryService.findType(cmisTypeId); + } + + /** + * Returns the definition after it has checked if the type can be used for + * object creation. + */ + public TypeDefinitionWrapper getTypeForCreate(String cmisTypeId, BaseTypeId baseTypeId) + { + TypeDefinitionWrapper type = getType(cmisTypeId); + if ((type == null) || (type.getBaseTypeId() != baseTypeId)) + { + switch (baseTypeId) + { + case CMIS_DOCUMENT: + throw new CmisConstraintException("Type is not a document type!"); + case CMIS_FOLDER: + throw new CmisConstraintException("Type is not a folder type!"); + case CMIS_RELATIONSHIP: + throw new CmisConstraintException("Type is not a relationship type!"); + case CMIS_POLICY: + throw new CmisConstraintException("Type is not a policy type!"); + } + } + + if (!type.getTypeDefinition(false).isCreatable()) + { + throw new CmisConstraintException("Type is not creatable!"); + } + + return type; + } + + /** + * Applies a versioning state to a document. + */ + public void applyVersioningState(NodeRef nodeRef, VersioningState versioningState) + { + if (versioningState == VersioningState.CHECKEDOUT) + { + if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + Map props = new HashMap(); + props.put(ContentModel.PROP_INITIAL_VERSION, false); + props.put(ContentModel.PROP_AUTO_VERSION, false); + nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); + } + getCheckOutCheckInService().checkout(nodeRef); + } else if ((versioningState == VersioningState.MAJOR) || (versioningState == VersioningState.MINOR)) + { + if (!nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) + { + Map props = new HashMap(); + props.put(ContentModel.PROP_INITIAL_VERSION, false); + props.put(ContentModel.PROP_AUTO_VERSION, false); + nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); + } + + Map versionProperties = new HashMap(5); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, + versioningState == VersioningState.MAJOR ? VersionType.MAJOR : VersionType.MINOR); + versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version"); + + versionService.createVersion(nodeRef, versionProperties); + } + } + + /** + * Checks if a child of a given type can be added to a given folder. + */ + @SuppressWarnings("unchecked") + public void checkChildObjectType(NodeRef folderNodeRef, String childType) + { + TypeDefinitionWrapper targetType = getType(folderNodeRef); + PropertyDefintionWrapper allowableChildObjectTypeProperty = targetType + .getPropertyById(PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS); + List childTypes = (List) allowableChildObjectTypeProperty.getPropertyAccessor().getValue( + folderNodeRef); + + if ((childTypes == null) || childTypes.isEmpty()) + { + return; + } + + if (!childTypes.contains(childType)) + { + throw new CmisConstraintException("Objects of type '" + childType + "' cannot be added to this folder!"); + } + } + + /** + * Creates the CMIS object for a node. + */ + public ObjectData createCMISObject(NodeRef nodeRef, String filter, boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds, + boolean includeAcl) + { + TypeDefinitionWrapper type = getType(nodeRef); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + // get the current version + NodeRef currentVersionNodeRef = nodeRef; + if (type instanceof DocumentTypeDefinitionWrapper) + { + try + { + VersionHistory versionHistory = versionService.getVersionHistory(nodeRef); + if (versionHistory != null) + { + Version currentVersion = versionHistory.getHeadVersion(); + currentVersionNodeRef = currentVersion.getVersionedNodeRef(); + + Serializable versionLabel = getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + if (currentVersion.getVersionLabel().equals(versionLabel)) + { + nodeRef = currentVersionNodeRef; + } + } + } catch (AspectMissingException e) + { + } + } + + ObjectDataImpl result = new ObjectDataImpl(); + + // set properties + result.setProperties(getNodeProperties(nodeRef, filter, type)); + + // set allowable actions + if (includeAllowableActions) + { + result.setAllowableActions(getAllowableActions(type, nodeRef)); + } + + // set relationships + if (includeRelationships != IncludeRelationships.NONE) + { + result.setRelationships(getRelationships(nodeRef, includeRelationships)); + } + + // set renditions + if (!RENDITION_NONE.equals(renditionFilter)) + { + List renditions = getRendtions(nodeRef, renditionFilter, null, null); + if ((renditions != null) && (!renditions.isEmpty())) + { + result.setRenditions(renditions); + } + } + + // set policy ids + if (includePolicyIds) + { + result.setPolicyIds(new PolicyIdListImpl()); + } + + // set ACL + if (includeAcl) + { + result.setAcl(getACL(currentVersionNodeRef, false)); + } + + // add aspects + List extensions = getAspectExtensions(nodeRef, filter, result.getProperties() + .getProperties().keySet()); + if (!extensions.isEmpty()) + { + result.getProperties().setExtensions( + Collections.singletonList((CmisExtensionElement) new CmisExtensionElementImpl( + ALFRESCO_EXTENSION_NAMESPACE, ASPECTS, null, extensions))); + } + + return result; + } + + /** + * Creates the CMIS object for an association. + */ + @SuppressWarnings("unchecked") + public ObjectData createCMISObject(AssociationRef assocRef, String filter, boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, boolean includePolicyIds, + boolean includeAcl) + { + TypeDefinitionWrapper type = getType(assocRef); + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + + ObjectDataImpl result = new ObjectDataImpl(); + + // set properties + result.setProperties(getAssocProperties(assocRef, filter, type)); + + // set allowable actions + if (includeAllowableActions) + { + result.setAllowableActions(getAllowableActions(type, assocRef)); + } + + // set policy ids + if (includePolicyIds) + { + result.setPolicyIds(new PolicyIdListImpl()); + } + + // set ACL + if (includeAcl) + { + // association have no ACL - return an empty list of ACEs + result.setAcl(new AccessControlListImpl((List) Collections.EMPTY_LIST)); + } + + return result; + } + + public String getPath(NodeRef nodeRef) + { + Path path = nodeService.getPath(nodeRef); + + // skip to CMIS root path + NodeRef rootNode = getRootNodeRef(); + int i = 0; + while (i < path.size()) + { + Path.Element element = path.get(i); + if (element instanceof ChildAssocElement) + { + ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); + NodeRef node = assocRef.getChildRef(); + if (node.equals(rootNode)) + { + break; + } + } + i++; + } + + StringBuilder displayPath = new StringBuilder(64); + + if (path.size() - i == 1) + { + // render root path + displayPath.append("/"); + } else + { + // render CMIS scoped path + i++; + while (i < path.size()) + { + Path.Element element = path.get(i); + if (element instanceof ChildAssocElement) + { + ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); + NodeRef node = assocRef.getChildRef(); + displayPath.append("/"); + displayPath.append(nodeService.getProperty(node, ContentModel.PROP_NAME)); + } + i++; + } + } + + return displayPath.toString(); + } + + /** + * Gets the content from the repository. + */ + public ContentStream getContentStream(NodeRef nodeRef, String streamId, BigInteger offset, BigInteger length) + { + // get the type and check if the object can have content + TypeDefinitionWrapper type = getType(nodeRef); + checkDocumentTypeForContent(type); + + // looks like a document, now get the content + ContentStreamImpl result = new ContentStreamImpl(); + result.setFileName(getName(nodeRef)); + + // if streamId is set, fetch other content + NodeRef streamNodeRef = nodeRef; + if ((streamId != null) && (streamId.length() > 0)) + { + ObjectVariantEnum variant = getObjectVariant(streamId); + throwCommonExceptions(variant, "Stream", streamId); + if (variant != ObjectVariantEnum.NODE) + { + throw new CmisInvalidArgumentException("Stream id is invalid: " + streamId); + } + + streamNodeRef = getNodeRef(streamId); + type = getType(streamNodeRef); + checkDocumentTypeForContent(type); + } + + // get the stream now + try + { + ContentReader contentReader = contentService.getReader(streamNodeRef, ContentModel.PROP_CONTENT); + if (contentReader == null) + { + throw new CmisConstraintException("Document has no content!"); + } + + result.setMimeType(contentReader.getMimetype()); + + if ((offset == null) && (length == null)) + { + result.setStream(contentReader.getContentInputStream()); + result.setLength(BigInteger.valueOf(contentReader.getSize())); + } else + { + long off = (offset == null ? 0 : offset.longValue()); + long len = (length == null ? contentReader.getSize() : length.longValue()) - off; + if (len > contentReader.getSize()) + { + len = contentReader.getSize() - off; + } + + result.setStream(new RangeInputStream(contentReader.getContentInputStream(), off, len)); + result.setLength(BigInteger.valueOf(len)); + } + } catch (Exception e) + { + if (e instanceof CmisBaseException) + { + throw (CmisBaseException) e; + } else + { + throw new CmisRuntimeException("Failed to retrieve content: " + e.getMessage(), e); + } + } + + return result; + } + + private void checkDocumentTypeForContent(TypeDefinitionWrapper type) + { + if (type == null) + { + throw new CmisObjectNotFoundException("No corresponding type found! Not a CMIS object?"); + } + if (!(type instanceof DocumentTypeDefinitionWrapper)) + { + throw new CmisStreamNotSupportedException("Object is not a docuemnt!"); + } + if (((DocumentTypeDefinition) type.getTypeDefinition(false)).getContentStreamAllowed() == ContentStreamAllowed.NOTALLOWED) + { + throw new CmisConstraintException("Document cannot have content!"); + } + } + + public Properties getNodeProperties(NodeRef nodeRef, String filter, TypeDefinitionWrapper type) + { + PropertiesImpl result = new PropertiesImpl(); + + Set filterSet = splitFilter(filter); + + for (PropertyDefintionWrapper propDef : type.getProperties()) + { + if (!propDef.getPropertyId().equals(PropertyIds.OBJECT_ID)) + { + // don't filter the object id + if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) + { + // skip properties that are not in the filter + continue; + } + } + + Serializable value = propDef.getPropertyAccessor().getValue(nodeRef); + result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value)); + } + + return result; + } + + public Properties getAssocProperties(AssociationRef assocRef, String filter, TypeDefinitionWrapper type) + { + PropertiesImpl result = new PropertiesImpl(); + + Set filterSet = splitFilter(filter); + + for (PropertyDefintionWrapper propDef : type.getProperties()) + { + if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) + { + // skip properties that are not in the filter + continue; + } + + Serializable value = propDef.getPropertyAccessor().getValue(assocRef); + result.addProperty(getProperty(propDef.getPropertyDefinition().getPropertyType(), propDef, value)); + } + + return result; + } + + /** + * Builds aspect extension. + */ + public List getAspectExtensions(NodeRef nodeRef, String filter, + Set alreadySetProperties) + { + List extensions = new ArrayList(); + Set propertyIds = new HashSet(alreadySetProperties); + Set filterSet = splitFilter(filter); + + Set aspects = nodeService.getAspects(nodeRef); + for (QName aspect : aspects) + { + TypeDefinitionWrapper aspectType = cmisDictionaryService.findNodeType(aspect); + if (aspectType == null) + { + continue; + } + + extensions.add(new CmisExtensionElementImpl(ALFRESCO_EXTENSION_NAMESPACE, APPLIED_ASPECTS, null, aspectType + .getTypeId())); + + List propertyExtensionList = new ArrayList(); + for (PropertyDefintionWrapper propDef : aspectType.getProperties()) + { + if (propertyIds.contains(propDef.getPropertyId())) + { + // skip properties that have already been added + continue; + } + + if ((filterSet != null) && (!filterSet.contains(propDef.getPropertyDefinition().getQueryName()))) + { + // skip properties that are not in the filter + continue; + } + + Serializable value = propDef.getPropertyAccessor().getValue(nodeRef); + propertyExtensionList.add(createAspectPropertyExtension(propDef.getPropertyDefinition(), value)); + + // mark property as 'added' + propertyIds.add(propDef.getPropertyId()); + } + + if (!propertyExtensionList.isEmpty()) + { + CmisExtensionElementImpl propertiesExtension = new CmisExtensionElementImpl( + ALFRESCO_EXTENSION_NAMESPACE, "properties", null, propertyExtensionList); + extensions.addAll(Collections.singletonList(propertiesExtension)); + } + } + + return extensions; + } + + /** + * Creates a property extension element. + */ + @SuppressWarnings("rawtypes") + private CmisExtensionElement createAspectPropertyExtension(PropertyDefinition propertyDefintion, Object value) + { + String name; + switch (propertyDefintion.getPropertyType()) + { + case BOOLEAN: + name = "propertyBoolean"; + break; + case DATETIME: + name = "propertyDateTime"; + break; + case DECIMAL: + name = "propertyDecimal"; + break; + case INTEGER: + name = "propertyInteger"; + break; + case ID: + name = "propertyId"; + break; + default: + name = "propertyString"; + } + + Map attributes = new HashMap(); + attributes.put("propertyDefinitionId", propertyDefintion.getId()); + + List propertyValues = new ArrayList(); + if (value != null) + { + if (value instanceof List) + { + for (Object o : ((List) value)) + { + propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null, + convertAspectPropertyValue(o))); + } + } else + { + propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null, + convertAspectPropertyValue(value))); + } + } + + return new CmisExtensionElementImpl(CMIS_NAMESPACE, name, attributes, propertyValues); + } + + private String convertAspectPropertyValue(Object value) + { + if (value instanceof GregorianCalendar) + { + DatatypeFactory df; + try + { + df = DatatypeFactory.newInstance(); + } catch (DatatypeConfigurationException e) + { + throw new IllegalArgumentException("Aspect conversation exception: " + e.getMessage(), e); + } + return df.newXMLGregorianCalendar((GregorianCalendar) value).toXMLFormat(); + } + + return value.toString(); + } + + @SuppressWarnings("unchecked") + private AbstractPropertyData getProperty(PropertyType propType, PropertyDefintionWrapper propDef, + Serializable value) + { + AbstractPropertyData result = null; + switch (propType) + { + case BOOLEAN: + result = new PropertyBooleanImpl(); + if (value instanceof List) + { + ((PropertyBooleanImpl) result).setValues((List) value); + } else + { + ((PropertyBooleanImpl) result).setValue((Boolean) value); + } + break; + case DATETIME: + result = new PropertyDateTimeImpl(); + if (value instanceof List) + { + ((PropertyDateTimeImpl) result).setValues((List) DefaultTypeConverter.INSTANCE + .convert(GregorianCalendar.class, (List) value)); + } else + { + ((PropertyDateTimeImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(GregorianCalendar.class, + value)); + } + break; + case DECIMAL: + result = new PropertyDecimalImpl(); + if (value instanceof List) + { + ((PropertyDecimalImpl) result).setValues((List) DefaultTypeConverter.INSTANCE.convert( + BigDecimal.class, (List) value)); + } else + { + ((PropertyDecimalImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigDecimal.class, value)); + } + break; + case HTML: + result = new PropertyHtmlImpl(); + if (value instanceof List) + { + ((PropertyHtmlImpl) result).setValues((List) value); + } else + { + ((PropertyHtmlImpl) result).setValue((String) value); + } + break; + case ID: + result = new PropertyIdImpl(); + if (value instanceof List) + { + ((PropertyIdImpl) result).setValues((List) value); + } else + { + if (value instanceof NodeRef) + { + ((PropertyIdImpl) result).setValue(value.toString()); + } else + { + ((PropertyIdImpl) result).setValue((String) value); + } + } + break; + case INTEGER: + result = new PropertyIntegerImpl(); + if (value instanceof List) + { + ((PropertyIntegerImpl) result).setValues((List) DefaultTypeConverter.INSTANCE.convert( + BigInteger.class, (List) value)); + } else + { + ((PropertyIntegerImpl) result).setValue(DefaultTypeConverter.INSTANCE.convert(BigInteger.class, value)); + } + break; + case STRING: + result = new PropertyStringImpl(); + if (value instanceof List) + { + ((PropertyStringImpl) result).setValues((List) value); + } else + { + ((PropertyStringImpl) result).setValue((String) value); + } + break; + case URI: + result = new PropertyUriImpl(); + if (value instanceof List) + { + ((PropertyUriImpl) result).setValues((List) value); + } else + { + ((PropertyUriImpl) result).setValue((String) value); + } + break; + default: + throw new RuntimeException("Unknown datatype! Spec change?"); + } + + if (propDef != null) + { + result.setId(propDef.getPropertyDefinition().getId()); + result.setQueryName(propDef.getPropertyDefinition().getQueryName()); + result.setDisplayName(propDef.getPropertyDefinition().getDisplayName()); + result.setLocalName(propDef.getPropertyDefinition().getLocalName()); + } + + return result; + } + + private Set splitFilter(String filter) + { + if (filter == null) + { + return null; + } + + if (filter.trim().length() == 0) + { + return null; + } + + Set result = new HashSet(); + for (String s : filter.split(",")) + { + s = s.trim(); + if (s.equals("*")) + { + return null; + } else if (s.length() > 0) + { + result.add(s); + } + } + + // set a few base properties + result.add(QUERY_NAME_OBJECT_ID); + result.add(QUERY_NAME_OBJECT_TYPE_ID); + result.add(QUERY_NAME_BASE_TYPE_ID); + + return result; + } + + @SuppressWarnings("unchecked") + public AllowableActions getAllowableActions(TypeDefinitionWrapper type, EntityRef ref) + { + AllowableActionsImpl result = new AllowableActionsImpl(); + Set allowableActions = new HashSet(); + result.setAllowableActions(allowableActions); + + for (@SuppressWarnings("rawtypes") + CMISActionEvaluator evaluator : type.getActionEvaluators().values()) + { + if (evaluator.isAllowed(ref)) + { + allowableActions.add(evaluator.getAction()); + } + } + + return result; + } + + public List getRelationships(NodeRef nodeRef, IncludeRelationships includeRelationships) + { + List result = new ArrayList(); + + if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + // relationships from and to versions are not preserved + return result; + } + + // get relationships + List assocs = new ArrayList(); + if (includeRelationships == IncludeRelationships.SOURCE || includeRelationships == IncludeRelationships.BOTH) + { + assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + } + if (includeRelationships == IncludeRelationships.TARGET || includeRelationships == IncludeRelationships.BOTH) + { + assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + } + + // filter relationships that not map the CMIS domain model + for (AssociationRef assocRef : assocs) + { + TypeDefinitionWrapper assocTypeDef = cmisDictionaryService.findAssocType(assocRef.getTypeQName()); + if (assocTypeDef != null) + { + result.add(createCMISObject(assocRef, null, false, IncludeRelationships.NONE, RENDITION_NONE, false, + false)); + } + } + + return result; + } + + public ObjectList getObjectRelationships(NodeRef nodeRef, RelationshipDirection relationshipDirection, + String typeId, String filter, Boolean includeAllowableActions, BigInteger maxItems, BigInteger skipCount) + { + ObjectListImpl result = new ObjectListImpl(); + result.setHasMoreItems(false); + result.setNumItems(BigInteger.ZERO); + result.setObjects(new ArrayList()); + + if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + // relationships from and to versions are not preserved + return result; + } + + // get relationships + List assocs = new ArrayList(); + if (relationshipDirection == RelationshipDirection.SOURCE + || relationshipDirection == RelationshipDirection.EITHER) + { + assocs.addAll(nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + } + if (relationshipDirection == RelationshipDirection.TARGET + || relationshipDirection == RelationshipDirection.EITHER) + { + assocs.addAll(nodeService.getSourceAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)); + } + + int skip = (skipCount == null ? 0 : skipCount.intValue()); + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int counter = 0; + boolean hasMore = false; + + if (max > 0) + { + // filter relationships that not map the CMIS domain model + for (AssociationRef assocRef : assocs) + { + TypeDefinitionWrapper assocTypeDef = cmisDictionaryService.findAssocType(assocRef.getTypeQName()); + if (assocTypeDef != null) + { + if ((typeId != null) && !assocRef.getId().equals(typeId)) + { + continue; + } + + counter++; + + if (skip > 0) + { + skip--; + continue; + } + + max--; + if (max > 0) + { + result.getObjects().add( + createCMISObject(assocRef, filter, includeAllowableActions, IncludeRelationships.NONE, + RENDITION_NONE, false, false)); + } else + { + hasMore = true; + } + } + } + } + + result.setNumItems(BigInteger.valueOf(counter)); + result.setHasMoreItems(hasMore); + + return result; + } + + public List getRendtions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems, + BigInteger skipCount) + { + String tenantDomain = tenantAdminService.getCurrentUserDomain(); + CMISRenditionMapping mapping = renditionMapping.get(tenantDomain); + + return mapping.getRenditions(nodeRef, renditionFilter, maxItems, skipCount); + } + + public Acl getACL(NodeRef nodeRef, boolean onlyBasicPermissions) + { + AccessControlListImpl result = new AccessControlListImpl(); + + // get the permissions and sort them + ArrayList ordered = new ArrayList( + permissionService.getAllSetPermissions(nodeRef)); + Collections.sort(ordered, new AccessPermissionComparator()); + + // remove denied permissions and create OpenCMIS objects + Map> aceMap = new HashMap>(); + for (AccessPermission entry : ordered) + { + if (entry.getAccessStatus() == AccessStatus.ALLOWED) + { + // add allowed entries + Map directAce = aceMap.get(entry.getAuthority()); + if (directAce == null) + { + directAce = new HashMap(); + aceMap.put(entry.getAuthority(), directAce); + } + + AccessControlEntryImpl ace = directAce.get(entry.isSetDirectly()); + if (ace == null) + { + ace = new AccessControlEntryImpl(); + ace.setPrincipal(new AccessControlPrincipalDataImpl(entry.getAuthority())); + ace.setPermissions(new ArrayList()); + ace.setDirect(entry.isSetDirectly()); + directAce.put(entry.isSetDirectly(), ace); + } + + ace.getPermissions().add(entry.getPermission()); + } else if (entry.getAccessStatus() == AccessStatus.DENIED) + { + // remove denied entries + Map directAce = aceMap.get(entry.getAuthority()); + if (directAce != null) + { + for (AccessControlEntryImpl ace : directAce.values()) + { + ace.getPermissions().remove(entry.getPermission()); + } + } + } + } + + // adjust permissions, add CMIS permissions and add ACEs to ACL + List aces = new ArrayList(); + result.setAces(aces); + for (Map bothAces : aceMap.values()) + { + // get, translate and set direct ACE + AccessControlEntryImpl directAce = bothAces.get(true); + if ((directAce != null) && (!directAce.getPermissions().isEmpty())) + { + directAce.setPermissions(translatePermmissionsToCMIS(directAce.getPermissions(), onlyBasicPermissions)); + aces.add(directAce); + } + + // get, translate, remove duplicate permissions and set indirect ACE + AccessControlEntryImpl indirectAce = bothAces.get(false); + if ((indirectAce != null) && (!indirectAce.getPermissions().isEmpty())) + { + indirectAce.setPermissions(translatePermmissionsToCMIS(indirectAce.getPermissions(), + onlyBasicPermissions)); + + // remove permissions that are already set in the direct ACE + if ((directAce != null) && (!directAce.getPermissions().isEmpty())) + { + indirectAce.getPermissions().removeAll(directAce.getPermissions()); + } + + aces.add(indirectAce); + } + } + + result.setExact(!onlyBasicPermissions); + + return result; + } + + private List translatePermmissionsToCMIS(List permissions, boolean onlyBasicPermissions) + { + Set result = new TreeSet(); + + for (String permission : permissions) + { + PermissionReference permissionReference = permissionModelDao.getPermissionReference(null, permission); + + // check for full permissions + if (permissionModelDao.hasFull(permissionReference)) + { + result.add(BasicPermissions.READ); + result.add(BasicPermissions.WRITE); + result.add(BasicPermissions.ALL); + } + + // check short forms + Set longForms = permissionModelDao.getGranteePermissions(permissionReference); + + HashSet shortForms = new HashSet(); + for (PermissionReference longForm : longForms) + { + shortForms.add(permissionModelDao.isUnique(longForm) ? longForm.getName() : longForm.toString()); + } + + for (String perm : shortForms) + { + if (PermissionService.READ.equals(perm)) + { + result.add(BasicPermissions.READ); + } else if (PermissionService.WRITE.equals(perm)) + { + result.add(BasicPermissions.WRITE); + } else if (PermissionService.ALL_PERMISSIONS.equals(perm)) + { + result.add(BasicPermissions.READ); + result.add(BasicPermissions.WRITE); + result.add(BasicPermissions.ALL); + } + } + + // check the permission + if (PermissionService.READ.equals(permission)) + { + result.add(BasicPermissions.READ); + } else if (PermissionService.WRITE.equals(permission)) + { + result.add(BasicPermissions.WRITE); + } else if (PermissionService.ALL_PERMISSIONS.equals(permission)) + { + result.add(BasicPermissions.READ); + result.add(BasicPermissions.WRITE); + result.add(BasicPermissions.ALL); + } + + // expand native permissions + if (!onlyBasicPermissions) + { + if (permission.startsWith("{")) + { + result.add(permission); + } else + { + result.add(permissionReference.toString()); + } + } + } + + return new ArrayList(result); + } + + public static class AccessPermissionComparator implements Comparator + { + public int compare(AccessPermission left, AccessPermission right) + { + if (left.getPosition() != right.getPosition()) + { + return right.getPosition() - left.getPosition(); + } else + { + if (left.getAccessStatus() != right.getAccessStatus()) + { + return (left.getAccessStatus() == AccessStatus.DENIED) ? -1 : 1; + } else + { + int compare = left.getAuthority().compareTo(right.getAuthority()); + if (compare != 0) + { + return compare; + } else + { + return (left.getPermission().compareTo(right.getPermission())); + } + + } + + } + } + } + + /** + * Applies the given ACLs. + */ + public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl addAces, Acl removeAces) + { + boolean hasAdd = (addAces != null) && (addAces.getAces() != null) && !addAces.getAces().isEmpty(); + boolean hasRemove = (removeAces != null) && (removeAces.getAces() != null) && !removeAces.getAces().isEmpty(); + + if (!hasAdd && !hasRemove) + { + return; + } + + if (!type.getTypeDefinition(false).isControllableAcl()) + { + throw new CmisConstraintException("Object is not ACL controllable!"); + } + + // remove permissions + if (hasRemove) + { + Set permissions = permissionService.getAllSetPermissions(nodeRef); + for (Ace ace : removeAces.getAces()) + { + for (String permission : translatePermmissionsFromCMIS(ace.getPermissions())) + { + AccessPermission toCheck = new AccessPermissionImpl(permission, AccessStatus.ALLOWED, + ace.getPrincipalId(), 0); + if (!permissions.contains(toCheck)) + { + throw new CmisConstraintException("No matching ACE found to remove!"); + } + + permissionService.deletePermission(nodeRef, ace.getPrincipalId(), permission); + } + } + } + + // add permissions + if (hasAdd) + { + for (Ace ace : addAces.getAces()) + { + for (String permission : translatePermmissionsFromCMIS(ace.getPermissions())) + { + permissionService.setPermission(nodeRef, ace.getPrincipalId(), permission, true); + } + } + } + } + + /** + * Sets the given ACL. + */ + public void applyACL(NodeRef nodeRef, TypeDefinitionWrapper type, Acl aces) + { + boolean hasAces = (aces != null) && (aces.getAces() != null) && !aces.getAces().isEmpty(); + + if (!hasAces) + { + return; + } + + if (!type.getTypeDefinition(false).isControllableAcl()) + { + throw new CmisConstraintException("Object is not ACL controllable!"); + } + + // remove all permissions + permissionService.deletePermissions(nodeRef); + + // set new permissions + for (Ace ace : aces.getAces()) + { + for (String permission : translatePermmissionsFromCMIS(ace.getPermissions())) + { + permissionService.setPermission(nodeRef, ace.getPrincipalId(), permission, true); + } + } + } + + private List translatePermmissionsFromCMIS(List permissions) + { + List result = new ArrayList(); + + if (permissions == null) + { + return result; + } + + for (String permission : permissions) + { + if (permission == null) + { + throw new CmisConstraintException("Invalid null permission!"); + } + + if (BasicPermissions.READ.equals(permission)) + { + result.add(PermissionService.READ); + } else if (BasicPermissions.WRITE.equals(permission)) + { + result.add(PermissionService.WRITE); + } else if (BasicPermissions.ALL.equals(permission)) + { + result.add(PermissionService.ALL_PERMISSIONS); + } else if (!permission.startsWith("{")) + { + result.add(permission); + } else + { + int sepIndex = permission.lastIndexOf('.'); + if (sepIndex == -1) + { + result.add(permission); + } else + { + result.add(permission.substring(sepIndex + 1)); + } + } + } + + return result; + } + + public void applyPolicies(NodeRef nodeRef, TypeDefinitionWrapper type, List policies) + { + if ((policies == null) || (policies.isEmpty())) + { + return; + } + + if (!type.getTypeDefinition(false).isControllablePolicy()) + { + throw new CmisConstraintException("Object is not policy controllable!"); + } + + // nothing else to do... + } + + public ObjectList query(String statement, Boolean includeAllowableActions, + IncludeRelationships includeRelationships, String renditionFilter, BigInteger maxItems, BigInteger skipCount) + { + // prepare results + ObjectListImpl result = new ObjectListImpl(); + result.setObjects(new ArrayList()); + + // prepare query + CMISQueryOptions options = new CMISQueryOptions(statement, getRootStoreRef()); + options.setQueryMode(CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS); + + int skip = 0; + if ((skipCount != null) && (skipCount.intValue() >= 0)) + { + skip = skipCount.intValue(); + options.setSkipCount(skip); + } + + if ((maxItems != null) && (maxItems.intValue() >= 0)) + { + options.setMaxItems(maxItems.intValue()); + } + + boolean fetchObject = includeAllowableActions || (includeRelationships != IncludeRelationships.NONE) + || (!RENDITION_NONE.equals(renditionFilter)); + + // query + CMISResultSet rs = cmisQueryService.query(options); + try + { + CMISResultSetColumn[] columns = rs.getMetaData().getColumns(); + + for (CMISResultSetRow row : rs) + { + ObjectDataImpl hit = new ObjectDataImpl(); + PropertiesImpl properties = new PropertiesImpl(); + hit.setProperties(properties); + + Map values = row.getValues(); + + for (CMISResultSetColumn column : columns) + { + AbstractPropertyData property = getProperty(column.getCMISDataType(), + column.getCMISPropertyDefinition(), values.get(column.getName())); + property.setQueryName(column.getName()); + properties.addProperty(property); + } + + if (fetchObject) + { + NodeRef nodeRef = row.getNodeRef(); + TypeDefinitionWrapper type = getType(nodeRef); + if (type == null) + { + continue; + } + + // set allowable actions + if (includeAllowableActions) + { + hit.setAllowableActions(getAllowableActions(type, nodeRef)); + } + + // set relationships + if (includeRelationships != IncludeRelationships.NONE) + { + hit.setRelationships(getRelationships(nodeRef, includeRelationships)); + } + + // set renditions + if (!RENDITION_NONE.equals(renditionFilter)) + { + List renditions = getRendtions(nodeRef, renditionFilter, null, null); + if ((renditions != null) && (!renditions.isEmpty())) + { + hit.setRenditions(renditions); + } + } + } + + result.getObjects().add(hit); + } + + result.setNumItems(null); + result.setHasMoreItems(rs.hasMore()); + + } finally + { + rs.close(); + } + + return result; + } + + /** + * Sets property values. + */ + public void setProperties(NodeRef nodeRef, TypeDefinitionWrapper type, Properties properties, String... exclude) + { + if (properties == null) + { + return; + } + + for (PropertyData property : properties.getPropertyList()) + { + if (Arrays.binarySearch(exclude, property.getId()) < 0) + { + setProperty(nodeRef, type, property); + } + } + + List extensions = properties.getExtensions(); + if (extensions != null) + { + for (CmisExtensionElement extension : extensions) + { + if (ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace()) + && SET_ASPECTS.equals(extension.getName())) + { + setAspectProperties(nodeRef, extension); + break; + } + } + } + } + + private void setAspectProperties(NodeRef nodeRef, CmisExtensionElement aspectExtension) + { + if (aspectExtension.getChildren() == null) + { + return; + } + + List aspectsToAdd = new ArrayList(); + List aspectsToRemove = new ArrayList(); + Map> aspectProperties = new HashMap>(); + + for (CmisExtensionElement extension : aspectExtension.getChildren()) + { + if (!ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace())) + { + continue; + } + + if (ASPECTS_TO_ADD.equals(extension.getName()) && (extension.getValue() != null)) + { + aspectsToAdd.add(extension.getValue()); + } else if (ASPECTS_TO_REMOVE.equals(extension.getName()) && (extension.getValue() != null)) + { + aspectsToRemove.add(extension.getValue()); + } else if (PROPERTIES.equals(extension.getName()) && (extension.getChildren() != null)) + { + for (CmisExtensionElement property : extension.getChildren()) + { + if (!property.getName().startsWith("property")) + { + continue; + } + + String propertyId = (property.getAttributes() == null ? null : property.getAttributes().get( + "propertyDefinitionId")); + if ((propertyId == null) || (property.getChildren() == null)) + { + continue; + } + + PropertyType propertyType = PropertyType.STRING; + DatatypeFactory df = null; + if (property.getName().equals("propertyBoolean")) + { + propertyType = PropertyType.BOOLEAN; + } else if (property.getName().equals("propertyInteger")) + { + propertyType = PropertyType.INTEGER; + } else if (property.getName().equals("propertyDateTime")) + { + propertyType = PropertyType.DATETIME; + try + { + df = DatatypeFactory.newInstance(); + } catch (DatatypeConfigurationException e) + { + throw new CmisRuntimeException("Aspect conversation exception: " + e.getMessage(), e); + } + } else if (property.getName().equals("propertyDecimal")) + { + propertyType = PropertyType.DECIMAL; + } + + ArrayList values = new ArrayList(); + if (property.getChildren() != null) + { + try + { + for (CmisExtensionElement valueElement : property.getChildren()) + { + if ("value".equals(valueElement.getName())) + { + switch (propertyType) + { + case BOOLEAN: + values.add(Boolean.parseBoolean(valueElement.getValue())); + break; + case DATETIME: + values.add(df.newXMLGregorianCalendar(valueElement.getValue()) + .toGregorianCalendar()); + break; + case INTEGER: + values.add(new BigInteger(valueElement.getValue())); + break; + case DECIMAL: + values.add(new BigDecimal(valueElement.getValue())); + break; + default: + values.add(valueElement.getValue()); + } + } + } + } catch (Exception e) + { + throw new CmisInvalidArgumentException("Invalid property aspect value: " + propertyId, e); + } + } + + aspectProperties.put(QName.createQName(propertyId, namespaceService), values); + } + } + } + + // remove and add aspects + String aspectType = null; + try + { + for (String aspect : aspectsToRemove) + { + aspectType = aspect; + nodeService.removeAspect(nodeRef, getType(aspect).getAlfrescoName()); + } + + for (String aspect : aspectsToAdd) + { + aspectType = aspect; + nodeService.addAspect(nodeRef, getType(aspect).getAlfrescoName(), + Collections. emptyMap()); + } + } catch (InvalidAspectException e) + { + throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); + } catch (InvalidNodeRefException e) + { + throw new CmisInvalidArgumentException("Invalid node: " + nodeRef); + } + + // set property + for (Map.Entry> property : aspectProperties.entrySet()) + { + if (property.getValue().isEmpty()) + { + nodeService.removeProperty(nodeRef, property.getKey()); + } else + { + nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property + .getValue().get(0) : (Serializable) property.getValue()); + } + } + } + + /** + * Sets a property value. + */ + public void setProperty(NodeRef nodeRef, TypeDefinitionWrapper type, PropertyData property) + { + if (property == null) + { + throw new CmisInvalidArgumentException("Cannot process not null property!"); + } + + PropertyDefintionWrapper propDef = type.getPropertyById(property.getId()); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Property " + property.getId() + " is unknown!"); + } + + if ((propDef.getPropertyDefinition().getUpdatability() == Updatability.READONLY) + || (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY))) + { + throw new CmisInvalidArgumentException("Property " + property.getId() + " is read-only!"); + } + + QName propertyQName = propDef.getPropertyAccessor().getMappedProperty(); + if (propertyQName == null) + { + throw new CmisConstraintException("Unable to set property " + property.getId() + "!"); + } + + // get the value + Serializable value = getValue(property, propDef.getPropertyDefinition().getCardinality() == Cardinality.MULTI); + + if (property.getId().equals(PropertyIds.NAME)) + { + if (!(value instanceof String)) + { + throw new CmisInvalidArgumentException("Object name must be a string!"); + } + + try + { + fileFolderService.rename(nodeRef, value.toString()); + } catch (FileExistsException e) + { + throw new CmisContentAlreadyExistsException("An object with this name already exists!", e); + } catch (FileNotFoundException e) + { + throw new CmisInvalidArgumentException("Object with id " + nodeRef.toString() + " not found!"); + } + } else + { + if (value == null) + { + nodeService.removeProperty(nodeRef, propertyQName); + } else + { + nodeService.setProperty(nodeRef, propertyQName, value); + } + } + } + + private Serializable getValue(PropertyData property, boolean isMultiValue) + { + if ((property.getValues() == null) || (property.getValues().isEmpty())) + { + return null; + } + + if (isMultiValue) + { + return (Serializable) property.getValues(); + } + + return (Serializable) property.getValues().get(0); + } + + /** + * Returns content changes. + */ + public ObjectList getContentChanges(Holder changeLogToken, BigInteger maxItems) + { + final ObjectListImpl result = new ObjectListImpl(); + result.setObjects(new ArrayList()); + + EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true) + { + @Override + public boolean handleAuditEntry(Long entryId, String user, long time, Map values) + { + result.getObjects().addAll(createChangeEvents(time, values)); + return super.handleAuditEntry(entryId, user, time, values); + } + }; + + Long from = null; + if ((changeLogToken != null) && (changeLogToken.getValue() != null)) + { + try + { + from = Long.parseLong(changeLogToken.getValue()); + } catch (NumberFormatException e) + { + throw new CmisInvalidArgumentException("Invalid change log token: " + changeLogToken); + } + } + + AuditQueryParameters params = new AuditQueryParameters(); + params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION); + params.setForward(true); + params.setFromId(from); + + int maxResults = (maxItems == null ? 0 : maxItems.intValue()); + maxResults = (maxResults < 1 ? 0 : maxResults + 1); + + auditService.auditQuery(changeLogCollectingCallback, params, maxResults); + + String newChangeLogToken = null; + if (maxResults > 0) + { + if (result.getObjects().size() >= maxResults) + { + newChangeLogToken = result.getObjects().remove(result.getObjects().size() - 1).getId(); + result.setHasMoreItems(true); + } else + { + result.setHasMoreItems(false); + } + } + + if (changeLogToken != null) + { + changeLogToken.setValue(newChangeLogToken); + } + + return result; + } + + @SuppressWarnings("unchecked") + private List createChangeEvents(long time, Map values) + { + List result = new ArrayList(); + + if ((values == null) || (values.size() == 0)) + { + return result; + } + + GregorianCalendar changeTime = new GregorianCalendar(); + changeTime.setTimeInMillis(time); + + String appPath = "/" + CMIS_CHANGELOG_AUDIT_APPLICATION + "/"; + + for (Entry entry : values.entrySet()) + { + if ((entry.getKey() == null) || (!(entry.getValue() instanceof Map))) + { + continue; + } + + String path = entry.getKey(); + if (!path.startsWith(appPath)) + { + continue; + } + + ChangeType changeType = null; + String changePath = path.substring(appPath.length()).toLowerCase(); + for (ChangeType c : ChangeType.values()) + { + if (changePath.startsWith(c.value().toLowerCase())) + { + changeType = c; + break; + } + } + + if (changeType == null) + { + continue; + } + + Map valueMap = (Map) entry.getValue(); + String objectId = (String) valueMap.get(CMISChangeLogDataExtractor.KEY_OBJECT_ID); + + // build object + ObjectDataImpl object = new ObjectDataImpl(); + result.add(object); + + PropertiesImpl properties = new PropertiesImpl(); + object.setProperties(properties); + PropertyIdImpl objectIdProperty = new PropertyIdImpl(PropertyIds.OBJECT_ID, objectId); + properties.addProperty(objectIdProperty); + + ChangeEventInfoDataImpl changeEvent = new ChangeEventInfoDataImpl(); + object.setChangeEventInfo(changeEvent); + changeEvent.setChangeType(changeType); + changeEvent.setChangeTime(changeTime); + } + + return result; + } + + private class EntryIdCallback implements AuditQueryCallback + { + private final boolean valuesRequired; + private Long entryId; + + public EntryIdCallback(boolean valuesRequired) + { + this.valuesRequired = valuesRequired; + } + + public String getEntryId() + { + return entryId == null ? null : entryId.toString(); + } + + public boolean valuesRequired() + { + return this.valuesRequired; + } + + public final boolean handleAuditEntry(Long entryId, String applicationName, String user, long time, + Map values) + { + if (applicationName.equals(CMIS_CHANGELOG_AUDIT_APPLICATION)) + { + return handleAuditEntry(entryId, user, time, values); + } + return true; + } + + public boolean handleAuditEntry(Long entryId, String user, long time, Map values) + { + this.entryId = entryId; + return true; + } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new CmisRuntimeException("Audit entry " + entryId + ": " + errorMsg, error); + } + }; + + // -------------------------------------------------------------- + // OpenCMIS methods + // -------------------------------------------------------------- + + /** + * Returns the value of the given property if it exists and is of the + * correct type. + */ + public String getStringProperty(Properties properties, String propertyId) + { + if ((properties == null) || (properties.getProperties() == null)) + { + return null; + } + + PropertyData property = properties.getProperties().get(propertyId); + if (!(property instanceof PropertyString)) + { + return null; + } + + return ((PropertyString) property).getFirstValue(); + } + + /** + * Returns the value of the given property if it exists and is of the + * correct type. + */ + public String getIdProperty(Properties properties, String propertyId) + { + if ((properties == null) || (properties.getProperties() == null)) + { + return null; + } + + PropertyData property = properties.getProperties().get(propertyId); + if (!(property instanceof PropertyId)) + { + return null; + } + + return ((PropertyId) property).getFirstValue(); + } + + public String getNameProperty(Properties properties) + { + String name = getStringProperty(properties, PropertyIds.NAME); + if ((name == null) || (name.trim().length() == 0)) + { + throw new CmisInvalidArgumentException("Property " + PropertyIds.NAME + " must be set!"); + } + + return name; + } + + public String getObjectTypeIdProperty(Properties properties) + { + String objectTypeId = getIdProperty(properties, PropertyIds.OBJECT_TYPE_ID); + if ((objectTypeId == null) || (objectTypeId.trim().length() == 0)) + { + throw new CmisInvalidArgumentException("Property " + PropertyIds.OBJECT_TYPE_ID + " must be set!"); + } + + return objectTypeId; + } + + public String getSourceIdProperty(Properties properties) + { + String id = getIdProperty(properties, PropertyIds.SOURCE_ID); + if ((id == null) || (id.trim().length() == 0)) + { + throw new CmisInvalidArgumentException("Property " + PropertyIds.SOURCE_ID + " must be set!"); + } + + return id; + } + + public String getTargetIdProperty(Properties properties) + { + String id = getIdProperty(properties, PropertyIds.TARGET_ID); + if ((id == null) || (id.trim().length() == 0)) + { + throw new CmisInvalidArgumentException("Property " + PropertyIds.TARGET_ID + " must be set!"); + } + + return id; + } + + /** + * Returns the repository info object. + */ + public RepositoryInfo getRepositoryInfo() + { + return createRepositoryInfo(); + } + + /** + * Creates the repository info object. + */ + private RepositoryInfo createRepositoryInfo() + { + Descriptor currentDescriptor = descriptorService.getCurrentRepositoryDescriptor(); + + // get change token + String latestChangeLogToken = null; + + EntryIdCallback auditQueryCallback = new EntryIdCallback(false); + AuditQueryParameters params = new AuditQueryParameters(); + params.setApplicationName(CMIS_CHANGELOG_AUDIT_APPLICATION); + params.setForward(false); + auditService.auditQuery(auditQueryCallback, params, 1); + latestChangeLogToken = auditQueryCallback.getEntryId(); + + if (repositoryPermissions == null) + { + repositoryPermissions = getRepositoryPermissions(); + } + + if (permissionMappings == null) + { + + } + + // compile repository info + RepositoryInfoImpl ri = new RepositoryInfoImpl(); + + ri.setId(currentDescriptor.getId()); + ri.setName(currentDescriptor.getName()); + ri.setDescription(currentDescriptor.getName()); + ri.setVendorName("Alfresco"); + ri.setProductName("Alfresco Repository (" + currentDescriptor.getEdition() + ")"); + ri.setProductVersion(currentDescriptor.getVersion()); + ri.setRootFolder(getRootNodeRef().toString()); + ri.setCmisVersionSupported("1.0"); + + ri.setChangesIncomplete(true); + ri.setChangesOnType(Arrays.asList(new BaseTypeId[] { BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER })); + ri.setLatestChangeLogToken(latestChangeLogToken); + ri.setPrincipalAnonymous(AuthenticationUtil.getGuestUserName()); + ri.setPrincipalAnyone(PermissionService.ALL_AUTHORITIES); + + RepositoryCapabilitiesImpl repCap = new RepositoryCapabilitiesImpl(); + ri.setCapabilities(repCap); + + repCap.setAllVersionsSearchable(false); + repCap.setCapabilityAcl(CapabilityAcl.MANAGE); + repCap.setCapabilityChanges(auditService.isAuditEnabled(CMIS_CHANGELOG_AUDIT_APPLICATION, "/" + + CMIS_CHANGELOG_AUDIT_APPLICATION) ? CapabilityChanges.OBJECTIDSONLY : CapabilityChanges.NONE); + repCap.setCapabilityContentStreamUpdates(CapabilityContentStreamUpdates.ANYTIME); + repCap.setCapabilityJoin(CapabilityJoin.NONE); + repCap.setCapabilityQuery(CapabilityQuery.BOTHCOMBINED); + repCap.setCapabilityRendition(CapabilityRenditions.READ); + repCap.setIsPwcSearchable(true); + repCap.setIsPwcUpdatable(true); + repCap.setSupportsGetDescendants(true); + repCap.setSupportsGetFolderTree(true); + repCap.setSupportsMultifiling(true); + repCap.setSupportsUnfiling(false); + repCap.setSupportsVersionSpecificFiling(false); + + AclCapabilitiesDataImpl aclCap = new AclCapabilitiesDataImpl(); + ri.setAclCapabilities(aclCap); + + aclCap.setAclPropagation(AclPropagation.PROPAGATE); + aclCap.setSupportedPermissions(SupportedPermissions.BOTH); + aclCap.setPermissionDefinitionData(repositoryPermissions); + aclCap.setPermissionMappingData(permissionMappings); + + return ri; + } + + private List getRepositoryPermissions() + { + ArrayList result = new ArrayList(); + + Set all = permissionModelDao.getAllExposedPermissions(); + for (PermissionReference pr : all) + { + result.add(createPermissionDefinition(pr)); + } + + PermissionReference allPermission = permissionModelDao.getPermissionReference(null, + PermissionService.ALL_PERMISSIONS); + result.add(createPermissionDefinition(allPermission)); + + PermissionDefinitionDataImpl cmisPermission; + + cmisPermission = new PermissionDefinitionDataImpl(); + cmisPermission.setPermission(BasicPermissions.READ); + cmisPermission.setDescription("CMIS Read"); + result.add(cmisPermission); + + cmisPermission = new PermissionDefinitionDataImpl(); + cmisPermission.setPermission(BasicPermissions.WRITE); + cmisPermission.setDescription("CMIS Write"); + result.add(cmisPermission); + + cmisPermission = new PermissionDefinitionDataImpl(); + cmisPermission.setPermission(BasicPermissions.ALL); + cmisPermission.setDescription("CMIS All"); + result.add(cmisPermission); + + return result; + } + + private PermissionDefinition createPermissionDefinition(PermissionReference pr) + { + PermissionDefinitionDataImpl permission = new PermissionDefinitionDataImpl(); + permission.setPermission(pr.getQName().toString() + "." + pr.getName()); + permission.setDescription(permission.getId()); + + return permission; + } + + private Map getPermissionMappings() + { + Map result = new HashMap(); + + for (CMISAllowedActionEnum e : EnumSet.allOf(CMISAllowedActionEnum.class)) + { + for (Map.Entry> m : e.getPermissionMapping().entrySet()) + { + PermissionMappingDataImpl mapping = new PermissionMappingDataImpl(); + mapping.setKey(m.getKey()); + mapping.setPermissions(m.getValue()); + + result.put(mapping.getKey(), mapping); + } + } + + return result; + } +} diff --git a/source/java/org/alfresco/opencmis/CMISLifecycleBean.java b/source/java/org/alfresco/opencmis/CMISLifecycleBean.java new file mode 100644 index 0000000000..131bb3a85d --- /dev/null +++ b/source/java/org/alfresco/opencmis/CMISLifecycleBean.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.util.HashMap; + +import javax.servlet.ServletContext; + +import org.apache.chemistry.opencmis.commons.server.CmisServiceFactory; +import org.apache.chemistry.opencmis.server.impl.CmisRepositoryContextListener; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.web.context.ServletContextAware; + +/** + * This bean controls the lifecycle of the CMIS factory. + * + * @author florian.mueller + */ +public class CMISLifecycleBean implements ServletContextAware, InitializingBean, DisposableBean +{ + private ServletContext servletContext; + private CmisServiceFactory factory; + + @Override + public void setServletContext(ServletContext servletContext) + { + this.servletContext = servletContext; + } + + public void setCmisServiceFactory(CmisServiceFactory factory) + { + this.factory = factory; + } + + @Override + public void afterPropertiesSet() throws Exception + { + if (factory != null && servletContext != null) + { + factory.init(new HashMap()); + servletContext.setAttribute(CmisRepositoryContextListener.SERVICES_FACTORY, factory); + } + } + + @Override + public void destroy() throws Exception + { + if (factory != null) + { + factory.destroy(); + } + } +} diff --git a/source/java/org/alfresco/opencmis/CMISRenditionMapping.java b/source/java/org/alfresco/opencmis/CMISRenditionMapping.java new file mode 100644 index 0000000000..8aa6f2a2a8 --- /dev/null +++ b/source/java/org/alfresco/opencmis/CMISRenditionMapping.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.io.Serializable; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.rendition.executer.ImageRenderingEngine; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.chemistry.opencmis.commons.data.RenditionData; +import org.apache.chemistry.opencmis.commons.exceptions.CmisFilterNotValidException; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.RenditionDataImpl; + +public class CMISRenditionMapping +{ + private NodeService nodeService; + private ContentService contentService; + private RenditionService renditionService; + private TransactionService transactionService; + + private Map> kindToRenditionNames; + private Map renditionNamesToKind; + private Map renditionNameToSize; + + public CMISRenditionMapping(NodeService nodeService, ContentService contentService, + RenditionService renditionService, TransactionService transactionService, + Map> renditionKinds) + { + this.nodeService = nodeService; + this.contentService = contentService; + this.renditionService = renditionService; + this.transactionService = transactionService; + + if (renditionKinds == null) + { + this.kindToRenditionNames = new HashMap>(); + } else + { + this.kindToRenditionNames = renditionKinds; + } + renditionNamesToKind = new HashMap(); + for (Entry> entry : renditionKinds.entrySet()) + { + for (String renditionName : entry.getValue()) + { + renditionNamesToKind.put(renditionName, entry.getKey()); + } + } + + cacheRenditionSizes(); + } + + private void cacheRenditionSizes() + { + renditionNameToSize = AuthenticationUtil.runAs(new RunAsWork>() + { + public Map doWork() throws Exception + { + return transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionCallback>() + { + public Map execute() throws Exception + { + Map rn2s = new HashMap(); + + List allRenditionDefs = renditionService + .loadRenditionDefinitions(); + for (RenditionDefinition rd : allRenditionDefs) + { + QName renditionDefinitionName = rd.getRenditionName(); + + Number width = (Number) rd + .getParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH); + Number height = (Number) rd + .getParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT); + + if ((width != null) || (height != null)) + { + BigInteger[] size = new BigInteger[2]; + size[0] = (width == null ? null : BigInteger.valueOf(width.longValue())); + size[1] = (height == null ? null : BigInteger.valueOf(height.longValue())); + + rn2s.put(renditionDefinitionName.getLocalName(), size); + } + } + + return rn2s; + }; + }, true); + } + }, AuthenticationUtil.getSystemUserName()); + } + + public List getRenditions(NodeRef nodeRef, String renditionFilter, BigInteger maxItems, + BigInteger skipCount) + { + List result = new ArrayList(); + + // split the filter + Set filterSet = splitRenditionFilter(renditionFilter); + if ((filterSet != null) && (filterSet.contains(CMISConnector.RENDITION_NONE))) + { + // "cmis:none" found -> no renditions + return result; + } + + // convert BigIntegers to int + int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue()); + int skip = (skipCount == null || skipCount.intValue() < 0 ? 0 : skipCount.intValue()); + + if (max > 0) + { + // find all renditions and filter them + List renditionList = renditionService.getRenditions(nodeRef); + + int lastIndex = (max + skip > renditionList.size() ? renditionList.size() : max + skip) - 1; + for (int i = skip; i <= lastIndex; i++) + { + ChildAssociationRef rendition = renditionList.get(i); + NodeRef rendNodeRef = rendition.getChildRef(); + String rendName = rendition.getQName().getLocalName(); + + // get and check content + QName contentProperty = ContentModel.PROP_CONTENT; + Serializable contentPropertyName = nodeService.getProperty(rendNodeRef, + ContentModel.PROP_CONTENT_PROPERTY_NAME); + if (contentPropertyName != null) + { + contentProperty = (QName) contentPropertyName; + } + + ContentReader reader = contentService.getReader(rendNodeRef, contentProperty); + if ((reader == null) || (reader.exists())) + { + // no content -> no rendition + continue; + } + + // get and clean MIME type + String mimeType = reader.getMimetype(); + if (mimeType.indexOf(';') > 3) + { + mimeType = mimeType.substring(0, mimeType.indexOf(';')).trim(); + } + + // if a filter is set, check it + if (filterSet != null) + { + boolean include = false; + for (String f : filterSet) + { + if (f.indexOf('/') == -1) + { + // found a kind, not a MIME type + List renditionNames = kindToRenditionNames.get(f); + if (renditionNames.contains(rendName)) + { + include = true; + break; + } + } else if (f.endsWith("*")) + { + // found MIME type with wildcard + if (mimeType.startsWith(f.substring(0, f.length() - 2))) + { + include = true; + break; + } + } else + { + // found complete MIME type + if (mimeType.equals(f)) + { + include = true; + break; + } + } + } + + // if no filter matches, skip this rendition + if (!include) + { + continue; + } + } + + // gather rendition data + String title = rendName; + String kind = (renditionNamesToKind.containsKey(rendName) ? renditionNamesToKind.get(rendName) + : rendName); + BigInteger length = BigInteger.valueOf(reader.getSize()); + + BigInteger width = null; + BigInteger height = null; + if (renditionNameToSize.containsKey(rendName)) + { + BigInteger[] size = renditionNameToSize.get(rendName); + width = size[0]; + height = size[1]; + } + + // finally add this rendition + result.add(createRenditionData(rendNodeRef, mimeType, title, kind, length, width, height)); + } + } + + return result; + } + + private Set splitRenditionFilter(String filter) + { + if (filter == null) + { + return null; + } + + if (filter.trim().length() == 0) + { + return null; + } + + Set result = new HashSet(); + for (String s : filter.split(",")) + { + s = s.trim(); + if (s.equals("*")) + { + return null; + } else if (s.indexOf('*') > -1) + { + if (!s.endsWith("*")) + { + throw new CmisFilterNotValidException("Rendition filter is invalid: " + s); + } + result.add(s); + } else if (s.equalsIgnoreCase(CMISConnector.RENDITION_NONE)) + { + result.clear(); + result.add(CMISConnector.RENDITION_NONE); + break; + } else if (s.length() > 0) + { + result.add(s); + } + } + + return result; + } + + private RenditionData createRenditionData(NodeRef rendNodeRef, String mimeType, String title, String kind, + BigInteger length, BigInteger width, BigInteger height) + { + RenditionDataImpl result = new RenditionDataImpl(); + + result.setStreamId(rendNodeRef.toString()); + result.setMimeType(mimeType); + + result.setTitle(title); + result.setKind(kind); + result.setBigLength(length); + + result.setBigWidth(width); + result.setBigHeight(height); + + result.setRenditionDocumentId(rendNodeRef.toString()); + + return result; + } +} diff --git a/source/java/org/alfresco/opencmis/CMISUtils.java b/source/java/org/alfresco/opencmis/CMISUtils.java new file mode 100644 index 0000000000..5624ffb00b --- /dev/null +++ b/source/java/org/alfresco/opencmis/CMISUtils.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; + +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; + +public class CMISUtils +{ + @SuppressWarnings("unchecked") + public static T copy(T source) + { + T target = null; + try + { + CopyOutputStream cos = new CopyOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(cos); + out.writeObject(source); + out.flush(); + out.close(); + + ObjectInputStream in = new ObjectInputStream(cos.getInputStream()); + target = (T) in.readObject(); + } catch (Exception e) + { + throw new CmisRuntimeException("Object copy failed!", e); + } + + return target; + } + + private static class CopyOutputStream extends OutputStream + { + protected byte[] buf = null; + protected int size = 0; + + public CopyOutputStream() + { + this(16 * 1024); + } + + public CopyOutputStream(int initSize) + { + this.size = 0; + this.buf = new byte[initSize]; + } + + private void verifyBufferSize(int sz) + { + if (sz > buf.length) + { + byte[] old = buf; + buf = new byte[Math.max(sz, 2 * buf.length)]; + System.arraycopy(old, 0, buf, 0, old.length); + old = null; + } + } + + public final void write(byte b[]) + { + verifyBufferSize(size + b.length); + System.arraycopy(b, 0, buf, size, b.length); + size += b.length; + } + + public final void write(byte b[], int off, int len) + { + verifyBufferSize(size + len); + System.arraycopy(b, off, buf, size, len); + size += len; + } + + public final void write(int b) + { + verifyBufferSize(size + 1); + buf[size++] = (byte) b; + } + + public InputStream getInputStream() + { + return new CopyInputStream(buf, size); + } + } + + private static class CopyInputStream extends InputStream + { + protected byte[] buf = null; + protected int count = 0; + protected int pos = 0; + + public CopyInputStream(byte[] buf, int count) + { + this.buf = buf; + this.count = count; + } + + public final int available() + { + return count - pos; + } + + public final int read() + { + return (pos < count) ? (buf[pos++] & 0xff) : -1; + } + + public final int read(byte[] b, int off, int len) + { + if (pos >= count) + { + return -1; + } + + if ((pos + len) > count) + { + len = (count - pos); + } + + System.arraycopy(buf, pos, b, off, len); + pos += len; + + return len; + } + + public final long skip(long n) + { + if ((pos + n) > count) + { + n = count - pos; + } + + if (n < 0) + { + return 0; + } + + pos += n; + + return n; + } + } +} diff --git a/source/java/org/alfresco/opencmis/ObjectVariantEnum.java b/source/java/org/alfresco/opencmis/ObjectVariantEnum.java new file mode 100644 index 0000000000..cbc7230160 --- /dev/null +++ b/source/java/org/alfresco/opencmis/ObjectVariantEnum.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +public enum ObjectVariantEnum +{ + INVALID_ID, // not a valid object id + NOT_EXISTING, // valid id but object doesn't exist + NODE, // object is a node + VERSION, // object is a version (not updatable) + PWC, // object is a PWC + ASSOC, // object is a relationship + PERMISSION_DENIED + // user has no permissions +} diff --git a/source/java/org/alfresco/opencmis/RangeInputStream.java b/source/java/org/alfresco/opencmis/RangeInputStream.java new file mode 100644 index 0000000000..1cdf0ba1dc --- /dev/null +++ b/source/java/org/alfresco/opencmis/RangeInputStream.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis; + +import java.io.IOException; +import java.io.InputStream; + +public class RangeInputStream extends InputStream +{ + + private InputStream inputStream; + private long bytesRead; + private long length; + + public RangeInputStream(InputStream inputStream, long offset, long length) throws IOException + { + super(); + + this.inputStream = inputStream; + this.length = length; + this.bytesRead = 0; + + long l = this.inputStream.skip(offset); + if (l < offset) + { + this.inputStream.skip(offset); + } + } + + @Override + public int read() throws IOException + { + if (bytesRead < length) + { + bytesRead++; + return inputStream.read(); + } else + { + return -1; + } + } + + @Override + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException + { + if (len > length - bytesRead) + { + len = (int) (length - bytesRead); + } + int readed = inputStream.read(b, off, len); + bytesRead += readed; + return readed; + } + + @Override + public int available() throws IOException + { + return (int) (length - bytesRead + 1); + } + + @Override + public void close() throws IOException + { + inputStream.close(); + } + + @Override + public long skip(long n) throws IOException + { + if (bytesRead + n > length) + { + n = (length - n) > 0 ? (length - n) : length - bytesRead; + } + n = inputStream.skip(n); + bytesRead += n; + + return n; + } + +} diff --git a/source/java/org/alfresco/opencmis/dictionary/AbstractTypeDefinitionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/AbstractTypeDefinitionWrapper.java new file mode 100644 index 0000000000..424ba77310 --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/AbstractTypeDefinitionWrapper.java @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.opencmis.CMISActionEvaluator; +import org.alfresco.opencmis.dictionary.CMISAbstractDictionaryService.DictionaryRegistry; +import org.alfresco.opencmis.mapping.AbstractProperty; +import org.alfresco.opencmis.mapping.CMISMapping; +import org.alfresco.opencmis.mapping.DirectProperty; +import org.alfresco.repo.dictionary.IndexTokenisationMode; +import org.alfresco.repo.dictionary.constraint.ListOfValuesConstraint; +import org.alfresco.repo.dictionary.constraint.NumericRangeConstraint; +import org.alfresco.repo.dictionary.constraint.StringLengthConstraint; +import org.alfresco.repo.search.impl.lucene.analysis.DateAnalyser; +import org.alfresco.repo.search.impl.lucene.analysis.DateTimeAnalyser; +import org.alfresco.repo.search.impl.lucene.analysis.DoubleAnalyser; +import org.alfresco.repo.search.impl.lucene.analysis.FloatAnalyser; +import org.alfresco.repo.search.impl.lucene.analysis.IntegerAnalyser; +import org.alfresco.repo.search.impl.lucene.analysis.LongAnalyser; +import org.alfresco.repo.search.impl.lucene.analysis.PathAnalyser; +import org.alfresco.repo.search.impl.lucene.analysis.VerbatimAnalyser; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.Constraint; +import org.alfresco.service.cmr.dictionary.ConstraintDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.abdera.ext.utils.ISO8601DateFormat; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.definitions.Choice; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; +import org.apache.chemistry.opencmis.commons.enums.Action; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.Cardinality; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; +import org.apache.chemistry.opencmis.commons.enums.Updatability; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyDefinition; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractTypeDefinition; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.ChoiceImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringDefinitionImpl; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriDefinitionImpl; + +/** + * Base class for type definition wrappers. + * + * @author florian.mueller + */ +public abstract class AbstractTypeDefinitionWrapper implements TypeDefinitionWrapper, Serializable +{ + private static final long serialVersionUID = 1L; + + protected AbstractTypeDefinition typeDef; + protected AbstractTypeDefinition typeDefInclProperties; + + protected TypeDefinitionWrapper parent; + protected List children; + + protected QName alfrescoName = null; + protected QName alfrescoClass = null; + protected Map> actionEvaluators; + + protected Map propertiesById = new HashMap(); + protected Map propertiesByQueryName = new HashMap(); + protected Map propertiesByQName = new HashMap(); + + // interface + + public TypeDefinition getTypeDefinition(boolean includePropertyDefinitions) + { + if (includePropertyDefinitions) + { + return typeDefInclProperties; + } else + { + return typeDef; + } + } + + protected void setTypeDefinition(AbstractTypeDefinition typeDef, AbstractTypeDefinition typeDefInclProperties) + { + this.typeDef = typeDef; + this.typeDefInclProperties = typeDefInclProperties; + } + + @Override + public String getTypeId() + { + return typeDef.getId(); + } + + @Override + public BaseTypeId getBaseTypeId() + { + return typeDef.getBaseTypeId(); + } + + @Override + public boolean isBaseType() + { + return typeDef.getId().equals(typeDef.getBaseTypeId().value()); + } + + @Override + public QName getAlfrescoName() + { + return alfrescoName; + } + + @Override + public QName getAlfrescoClass() + { + return alfrescoClass; + } + + @Override + public TypeDefinitionWrapper getParent() + { + return parent; + } + + @Override + public List getChildren() + { + return children; + } + + @Override + public Map> getActionEvaluators() + { + return actionEvaluators; + } + + @Override + public Collection getProperties() + { + return propertiesById.values(); + } + + @Override + public PropertyDefintionWrapper getPropertyById(String propertyId) + { + return propertiesById.get(propertyId); + } + + @Override + public PropertyDefintionWrapper getPropertyByQueryName(String queryName) + { + return propertiesByQueryName.get(queryName); + } + + @Override + public PropertyDefintionWrapper getPropertyByQName(QName name) + { + return propertiesByQName.get(name); + } + + // create + + public abstract void connectParentAndSubTypes(CMISMapping cmisMapping, DictionaryRegistry registry, + DictionaryService dictionaryService); + + public abstract void resolveInheritance(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, + DictionaryRegistry registry, DictionaryService dictionaryService); + + public void assertComplete() + { + if (typeDef == null) + throw new IllegalStateException("typeDef is not set"); + if (typeDefInclProperties == null) + throw new IllegalStateException("typeDefInclProperties is not set"); + if (alfrescoName == null) + throw new IllegalStateException("alfrescoName is not set"); + if (alfrescoClass == null) + throw new IllegalStateException("alfrescoClass is not set"); + if (propertiesById == null) + throw new IllegalStateException("propertiesById is not set"); + if (propertiesByQueryName == null) + throw new IllegalStateException("propertiesByQueryName is not set"); + if (propertiesByQName == null) + throw new IllegalStateException("propertiesByQName is not set"); + if (propertiesById.size() == 0) + throw new IllegalStateException("property map empty"); + if (propertiesById.size() != propertiesByQueryName.size()) + throw new IllegalStateException("property map mismatch"); + if (propertiesById.size() != propertiesByQName.size()) + throw new IllegalStateException("property map mismatch"); + } + + /** + * Adds all property definitions owned by that type. + */ + protected void createOwningPropertyDefinitions(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, + ClassDefinition cmisClassDef) + { + PropertyDefinition propertyDefintion; + + for (org.alfresco.service.cmr.dictionary.PropertyDefinition alfrescoPropDef : cmisClassDef.getProperties() + .values()) + { + if (!isBaseType()) + { + if (!alfrescoPropDef.getContainerClass().equals(cmisClassDef)) + { + continue; + } + } + + // compile property id + String propertyId = cmisMapping.buildPrefixEncodedString(alfrescoPropDef.getName()); + + // create property definition + propertyDefintion = createPropertyDefinition(cmisMapping, propertyId, alfrescoPropDef.getName(), + alfrescoPropDef, false); + + // if the datatype is not supported, the property defintion will be + // null + if (propertyDefintion != null) + { + AbstractProperty propertyAccessor = cmisMapping.getPropertyAccessor(propertyId); + if (propertyAccessor == null) + { + propertyAccessor = new DirectProperty(serviceRegistry, propertyId, alfrescoPropDef.getName()); + } + + registerProperty(new BasePropertyDefintionWrapper(propertyDefintion, alfrescoPropDef.getName(), this, + propertyAccessor, propertyAccessor)); + } + } + } + + /** + * Registers a property definition with this type + */ + protected void registerProperty(PropertyDefintionWrapper propDefWrapper) + { + if (propDefWrapper == null) + { + return; + } + + if (propertiesById.containsKey(propDefWrapper.getPropertyId())) + { + throw new AlfrescoRuntimeException("Property defintion " + propDefWrapper.getPropertyId() + + " already exists on type " + typeDef.getId()); + } + + propertiesById.put(propDefWrapper.getPropertyId(), propDefWrapper); + propertiesByQueryName.put(propDefWrapper.getPropertyDefinition().getQueryName(), propDefWrapper); + propertiesByQName.put(propDefWrapper.getAlfrescoName(), propDefWrapper); + typeDefInclProperties.addPropertyDefinition(propDefWrapper.getPropertyDefinition()); + } + + /** + * Creates a property definition object. + */ + protected PropertyDefinition createPropertyDefinition(CMISMapping cmisMapping, String id, + QName alfrescoPropName, org.alfresco.service.cmr.dictionary.PropertyDefinition propDef, boolean inherited) + { + PropertyType datatype = cmisMapping.getDataType(propDef.getDataType()); + if (datatype == null) + { + return null; + } + + AbstractPropertyDefinition result = null; + + switch (datatype) + { + case BOOLEAN: + result = new PropertyBooleanDefinitionImpl(); + break; + case DATETIME: + result = new PropertyDateTimeDefinitionImpl(); + break; + case DECIMAL: + result = new PropertyDecimalDefinitionImpl(); + break; + case HTML: + result = new PropertyHtmlDefinitionImpl(); + break; + case ID: + result = new PropertyIdDefinitionImpl(); + break; + case INTEGER: + result = new PropertyIntegerDefinitionImpl(); + break; + case STRING: + result = new PropertyStringDefinitionImpl(); + break; + case URI: + result = new PropertyUriDefinitionImpl(); + break; + default: + throw new RuntimeException("Unknown datatype! Spec change?"); + } + + if (id.equals(PropertyIds.OBJECT_TYPE_ID) || id.equals(PropertyIds.SOURCE_ID) + || id.equals(PropertyIds.TARGET_ID)) + { + // the CMIS spec requirement + result.setUpdatability(Updatability.ONCREATE); + } else + { + result.setUpdatability(propDef.isProtected() ? Updatability.READONLY : Updatability.READWRITE); + } + + result.setId(id); + result.setLocalName(alfrescoPropName.getLocalName()); + result.setLocalNamespace(alfrescoPropName.getNamespaceURI()); + result.setDisplayName(propDef.getTitle() != null ? propDef.getTitle() : id); + result.setDescription(propDef.getDescription() != null ? propDef.getDescription() : result.getDisplayName()); + result.setPropertyType(datatype); + result.setCardinality(propDef.isMultiValued() ? Cardinality.MULTI : Cardinality.SINGLE); + result.setIsInherited(inherited); + result.setIsRequired(propDef.isMandatory()); + addDefaultValue(propDef.getDefaultValue(), result); + + // query and order + result.setQueryName(cmisMapping.buildPrefixEncodedString(alfrescoPropName)); + result.setIsQueryable(propDef.isIndexed()); + result.setIsOrderable(false); + + if (result.isQueryable()) + { + if (result.getCardinality() == Cardinality.SINGLE) + { + IndexTokenisationMode indexTokenisationMode = IndexTokenisationMode.TRUE; + if (propDef.getIndexTokenisationMode() != null) + { + indexTokenisationMode = propDef.getIndexTokenisationMode(); + } + + switch (indexTokenisationMode) + { + case BOTH: + case FALSE: + result.setIsOrderable(true); + break; + case TRUE: + default: + String analyserClassName = propDef.getDataType().getAnalyserClassName(); + if (propDef.getDataType().getName().equals(DataTypeDefinition.BOOLEAN)) + { + result.setIsOrderable(true); + } else if (analyserClassName.equals(DateTimeAnalyser.class.getCanonicalName()) + || analyserClassName.equals(DateAnalyser.class.getCanonicalName()) + || analyserClassName.equals(DoubleAnalyser.class.getCanonicalName()) + || analyserClassName.equals(FloatAnalyser.class.getCanonicalName()) + || analyserClassName.equals(IntegerAnalyser.class.getCanonicalName()) + || analyserClassName.equals(LongAnalyser.class.getCanonicalName()) + || analyserClassName.equals(PathAnalyser.class.getCanonicalName()) + || analyserClassName.equals(VerbatimAnalyser.class.getCanonicalName())) + { + result.setIsOrderable(true); + } + } + } + } + + // constraints and choices + for (ConstraintDefinition constraintDef : propDef.getConstraints()) + { + Constraint constraint = constraintDef.getConstraint(); + if (constraint instanceof ListOfValuesConstraint) + { + addChoiceList((ListOfValuesConstraint) constraint, result); + } + + if ((constraint instanceof StringLengthConstraint) && (result instanceof PropertyStringDefinitionImpl)) + { + StringLengthConstraint slc = (StringLengthConstraint) constraint; + ((PropertyStringDefinitionImpl) result).setMaxLength(BigInteger.valueOf(slc.getMaxLength())); + } + + if (constraint instanceof NumericRangeConstraint) + { + NumericRangeConstraint nrc = (NumericRangeConstraint) constraint; + if (result instanceof PropertyIntegerDefinitionImpl) + { + ((PropertyIntegerDefinitionImpl) result) + .setMinValue(BigInteger.valueOf(((Double) nrc.getMinValue()).longValue())); + ((PropertyIntegerDefinitionImpl) result) + .setMaxValue(BigInteger.valueOf(((Double) nrc.getMaxValue()).longValue())); + } + if (result instanceof PropertyDecimalDefinitionImpl) + { + ((PropertyDecimalDefinitionImpl) result).setMinValue(BigDecimal.valueOf(nrc.getMinValue())); + ((PropertyDecimalDefinitionImpl) result).setMaxValue(BigDecimal.valueOf(nrc.getMaxValue())); + } + } + } + + return result; + } + + @SuppressWarnings("unchecked") + private T convertValueFromString(String value, PropertyType datatype) + { + if (value == null) + { + return null; + } + + switch (datatype) + { + case BOOLEAN: + return (T) Boolean.valueOf(value); + case DATETIME: + GregorianCalendar cal = new GregorianCalendar(); + cal.setTime(ISO8601DateFormat.parse(value)); + return (T) cal; + case DECIMAL: + return (T) new BigDecimal(value); + case HTML: + return (T) value; + case ID: + return (T) value; + case INTEGER: + return (T) new BigInteger(value); + case STRING: + return (T) value; + case URI: + return (T) value; + default: + throw new RuntimeException("Unknown datatype! Spec change?"); + } + } + + /** + * Adds the default value to a property definition. + */ + private void addDefaultValue(String value, PropertyDefinition propDef) + { + if (value == null) + { + return; + } + + if (propDef instanceof PropertyBooleanDefinitionImpl) + { + PropertyBooleanDefinitionImpl propDefImpl = (PropertyBooleanDefinitionImpl) propDef; + propDefImpl.setDefaultValue(Collections.singletonList((Boolean) convertValueFromString(value, + PropertyType.BOOLEAN))); + } else if (propDef instanceof PropertyDateTimeDefinitionImpl) + { + PropertyDateTimeDefinitionImpl propDefImpl = (PropertyDateTimeDefinitionImpl) propDef; + propDefImpl.setDefaultValue(Collections.singletonList((GregorianCalendar) convertValueFromString(value, + PropertyType.DATETIME))); + } else if (propDef instanceof PropertyDecimalDefinitionImpl) + { + PropertyDecimalDefinitionImpl propDefImpl = (PropertyDecimalDefinitionImpl) propDef; + propDefImpl.setDefaultValue(Collections.singletonList((BigDecimal) convertValueFromString(value, + PropertyType.DECIMAL))); + } else if (propDef instanceof PropertyHtmlDefinitionImpl) + { + PropertyHtmlDefinitionImpl propDefImpl = (PropertyHtmlDefinitionImpl) propDef; + propDefImpl.setDefaultValue(Collections.singletonList((String) convertValueFromString(value, + PropertyType.HTML))); + } else if (propDef instanceof PropertyIdDefinitionImpl) + { + PropertyIdDefinitionImpl propDefImpl = (PropertyIdDefinitionImpl) propDef; + propDefImpl.setDefaultValue(Collections.singletonList((String) convertValueFromString(value, + PropertyType.ID))); + } else if (propDef instanceof PropertyIntegerDefinitionImpl) + { + PropertyIntegerDefinitionImpl propDefImpl = (PropertyIntegerDefinitionImpl) propDef; + propDefImpl.setDefaultValue(Collections.singletonList((BigInteger) convertValueFromString(value, + PropertyType.INTEGER))); + } else if (propDef instanceof PropertyStringDefinitionImpl) + { + PropertyStringDefinitionImpl propDefImpl = (PropertyStringDefinitionImpl) propDef; + propDefImpl.setDefaultValue(Collections.singletonList((String) convertValueFromString(value, + PropertyType.STRING))); + } else if (propDef instanceof PropertyUriDefinitionImpl) + { + PropertyUriDefinitionImpl propDefImpl = (PropertyUriDefinitionImpl) propDef; + propDefImpl.setDefaultValue(Collections.singletonList((String) convertValueFromString(value, + PropertyType.URI))); + } + } + + /** + * Adds choices to the property defintion. + */ + private void addChoiceList(ListOfValuesConstraint lovc, PropertyDefinition propDef) + { + if (propDef instanceof PropertyBooleanDefinitionImpl) + { + PropertyBooleanDefinitionImpl propDefImpl = (PropertyBooleanDefinitionImpl) propDef; + propDefImpl.setIsOpenChoice(false); + + List> choiceList = new ArrayList>(); + for (String allowed : lovc.getAllowedValues()) + { + ChoiceImpl choice = new ChoiceImpl(); + choice.setDisplayName(allowed); + choice.setValue(Collections.singletonList((Boolean) convertValueFromString(allowed, + PropertyType.BOOLEAN))); + + choiceList.add(choice); + } + + propDefImpl.setChoices(choiceList); + } else if (propDef instanceof PropertyDateTimeDefinitionImpl) + { + PropertyDateTimeDefinitionImpl propDefImpl = (PropertyDateTimeDefinitionImpl) propDef; + propDefImpl.setIsOpenChoice(false); + + List> choiceList = new ArrayList>(); + for (String allowed : lovc.getAllowedValues()) + { + ChoiceImpl choice = new ChoiceImpl(); + choice.setDisplayName(allowed); + choice.setValue(Collections.singletonList((GregorianCalendar) convertValueFromString(allowed, + PropertyType.DATETIME))); + + choiceList.add(choice); + } + + propDefImpl.setChoices(choiceList); + } else if (propDef instanceof PropertyDecimalDefinitionImpl) + { + PropertyDecimalDefinitionImpl propDefImpl = (PropertyDecimalDefinitionImpl) propDef; + propDefImpl.setIsOpenChoice(false); + + List> choiceList = new ArrayList>(); + for (String allowed : lovc.getAllowedValues()) + { + ChoiceImpl choice = new ChoiceImpl(); + choice.setDisplayName(allowed); + choice.setValue(Collections.singletonList((BigDecimal) convertValueFromString(allowed, + PropertyType.DECIMAL))); + + choiceList.add(choice); + } + + propDefImpl.setChoices(choiceList); + } else if (propDef instanceof PropertyHtmlDefinitionImpl) + { + PropertyHtmlDefinitionImpl propDefImpl = (PropertyHtmlDefinitionImpl) propDef; + propDefImpl.setIsOpenChoice(false); + + List> choiceList = new ArrayList>(); + for (String allowed : lovc.getAllowedValues()) + { + ChoiceImpl choice = new ChoiceImpl(); + choice.setDisplayName(allowed); + choice.setValue(Collections.singletonList((String) convertValueFromString(allowed, PropertyType.HTML))); + + choiceList.add(choice); + } + + propDefImpl.setChoices(choiceList); + } else if (propDef instanceof PropertyIdDefinitionImpl) + { + PropertyIdDefinitionImpl propDefImpl = (PropertyIdDefinitionImpl) propDef; + propDefImpl.setIsOpenChoice(false); + + List> choiceList = new ArrayList>(); + for (String allowed : lovc.getAllowedValues()) + { + ChoiceImpl choice = new ChoiceImpl(); + choice.setDisplayName(allowed); + choice.setValue(Collections.singletonList((String) convertValueFromString(allowed, PropertyType.ID))); + + choiceList.add(choice); + } + + propDefImpl.setChoices(choiceList); + } else if (propDef instanceof PropertyIntegerDefinitionImpl) + { + PropertyIntegerDefinitionImpl propDefImpl = (PropertyIntegerDefinitionImpl) propDef; + propDefImpl.setIsOpenChoice(false); + + List> choiceList = new ArrayList>(); + for (String allowed : lovc.getAllowedValues()) + { + ChoiceImpl choice = new ChoiceImpl(); + choice.setDisplayName(allowed); + choice.setValue(Collections.singletonList((BigInteger) convertValueFromString(allowed, + PropertyType.INTEGER))); + + choiceList.add(choice); + } + + propDefImpl.setChoices(choiceList); + } else if (propDef instanceof PropertyStringDefinitionImpl) + { + PropertyStringDefinitionImpl propDefImpl = (PropertyStringDefinitionImpl) propDef; + propDefImpl.setIsOpenChoice(false); + + List> choiceList = new ArrayList>(); + for (String allowed : lovc.getAllowedValues()) + { + ChoiceImpl choice = new ChoiceImpl(); + choice.setDisplayName(allowed); + choice.setValue(Collections + .singletonList((String) convertValueFromString(allowed, PropertyType.STRING))); + + choiceList.add(choice); + } + + propDefImpl.setChoices(choiceList); + } else if (propDef instanceof PropertyUriDefinitionImpl) + { + PropertyUriDefinitionImpl propDefImpl = (PropertyUriDefinitionImpl) propDef; + propDefImpl.setIsOpenChoice(false); + + List> choiceList = new ArrayList>(); + for (String allowed : lovc.getAllowedValues()) + { + ChoiceImpl choice = new ChoiceImpl(); + choice.setDisplayName(allowed); + choice.setValue(Collections.singletonList((String) convertValueFromString(allowed, PropertyType.URI))); + + choiceList.add(choice); + } + + propDefImpl.setChoices(choiceList); + } + } +} diff --git a/source/java/org/alfresco/opencmis/dictionary/BasePropertyDefintionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/BasePropertyDefintionWrapper.java new file mode 100644 index 0000000000..f47506f9ab --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/BasePropertyDefintionWrapper.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.io.Serializable; + +import org.alfresco.cmis.CMISPropertyAccessor; +import org.alfresco.cmis.CMISPropertyLuceneBuilder; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; + +public class BasePropertyDefintionWrapper implements PropertyDefintionWrapper, Serializable +{ + private static final long serialVersionUID = 1L; + + private PropertyDefinition propDef; + private QName alfrescoName; + private TypeDefinitionWrapper owningType; + private CMISPropertyAccessor accessor; + private CMISPropertyLuceneBuilder luceneBuilder; + + public BasePropertyDefintionWrapper(PropertyDefinition propDef, QName alfrescoName, + TypeDefinitionWrapper owningType, CMISPropertyAccessor accessor, CMISPropertyLuceneBuilder luceneBuilder) + { + this.propDef = propDef; + this.alfrescoName = alfrescoName; + this.owningType = owningType; + this.accessor = accessor; + this.luceneBuilder = luceneBuilder; + } + + @Override + public PropertyDefinition getPropertyDefinition() + { + return propDef; + } + + @Override + public String getPropertyId() + { + return propDef.getId(); + } + + @Override + public QName getAlfrescoName() + { + return alfrescoName; + } + + @Override + public TypeDefinitionWrapper getOwningType() + { + return owningType; + } + + @Override + public CMISPropertyAccessor getPropertyAccessor() + { + return accessor; + } + + @Override + public CMISPropertyLuceneBuilder getPropertyLuceneBuilder() + { + return luceneBuilder; + } + +} diff --git a/source/java/org/alfresco/opencmis/dictionary/CMISAbstractDictionaryService.java b/source/java/org/alfresco/opencmis/dictionary/CMISAbstractDictionaryService.java new file mode 100644 index 0000000000..dcea262168 --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/CMISAbstractDictionaryService.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.opencmis.mapping.CMISMapping; +import org.alfresco.repo.dictionary.DictionaryDAO; +import org.alfresco.repo.dictionary.DictionaryListener; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.extensions.surf.util.AbstractLifecycleBean; + +/** + * Common CMIS Dictionary Support including registry of Types. + * + * @author davidc + * @author florian.mueller + */ +public abstract class CMISAbstractDictionaryService extends AbstractLifecycleBean implements CMISDictionaryService, + DictionaryListener +{ + // Logger + protected static final Log logger = LogFactory.getLog(CMISAbstractDictionaryService.class); + + // service dependencies + private DictionaryDAO dictionaryDAO; + protected CMISMapping cmisMapping; + protected DictionaryService dictionaryService; + protected TenantService tenantService; + protected ServiceRegistry serviceRegistry; + + /** + * Set the mapping service + * + * @param cmisMapping + */ + public void setOpenCMISMapping(CMISMapping cmisMapping) + { + this.cmisMapping = cmisMapping; + } + + /** + * Set the dictionary Service + * + * @param dictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Set the dictionary DAO + * + * @param dictionaryDAO + */ + public void setDictionaryDAO(DictionaryDAO dictionaryDAO) + { + this.dictionaryDAO = dictionaryDAO; + } + + /** + * Set the tenant Service + * + * @param tenantService + */ + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + /** + * Set the service registry + * + * @param serviceRegistry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** CMIS Dictionary Registry (tenant-aware) */ + private Map registryMap = new ConcurrentHashMap(4); + + /** + * CMIS Dictionary registry + * + * Index of CMIS Type Definitions + */ + /* package */class DictionaryRegistry + { + // Type Definitions Index + Map typeDefsByQName = new HashMap(); + Map assocDefsByQName = new HashMap(); + + Map typeDefsByTypeId = new HashMap(); + Map typeDefsByQueryName = new HashMap(); + List baseTypes = new ArrayList(); + + Map propDefbyPropId = new HashMap(); + Map propDefbyQueryName = new HashMap(); + + /** + * Register type definition. + * + * @param typeDef + */ + public void registerTypeDefinition(AbstractTypeDefinitionWrapper typeDef) + { + AbstractTypeDefinitionWrapper existingTypeDef = typeDefsByTypeId.get(typeDef.getTypeId()); + if (existingTypeDef != null) + { + throw new AlfrescoRuntimeException("Type " + typeDef.getTypeId() + " already registered"); + } + + typeDefsByTypeId.put(typeDef.getTypeId(), typeDef); + QName typeQName = typeDef.getAlfrescoName(); + if (typeQName != null) + { + if ((typeDef instanceof RelationshipTypeDefintionWrapper) && !typeDef.isBaseType()) + { + assocDefsByQName.put(typeQName, typeDef); + } else + { + typeDefsByQName.put(typeQName, typeDef); + } + } + + typeDefsByQueryName.put(typeDef.getTypeDefinition(false).getQueryName(), typeDef); + + if (logger.isDebugEnabled()) + { + logger.debug("Registered type " + typeDef.getTypeId() + " (scope=" + typeDef.getBaseTypeId() + ")"); + logger.debug(" QName: " + typeDef.getAlfrescoName()); + logger.debug(" Table: " + typeDef.getTypeDefinition(false).getQueryName()); + logger.debug(" Action Evaluators: " + typeDef.getActionEvaluators().size()); + } + } + + /** + * Register property definitions. + * + * @param typeDef + */ + public void registerPropertyDefinitions(AbstractTypeDefinitionWrapper typeDef) + { + for (PropertyDefintionWrapper propDef : typeDef.getProperties()) + { + if (propDef.getPropertyDefinition().isInherited()) + { + continue; + } + + propDefbyPropId.put(propDef.getPropertyId(), propDef); + propDefbyQueryName.put(propDef.getPropertyDefinition().getQueryName(), propDef); + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("DictionaryRegistry["); + builder.append("Types=").append(typeDefsByTypeId.size()).append(", "); + builder.append("Base Types=").append(baseTypes.size()).append(", "); + builder.append("]"); + return builder.toString(); + } + } + + private DictionaryRegistry getRegistry() + { + String tenantDomain = tenantService.getCurrentUserDomain(); + DictionaryRegistry registry = registryMap.get(tenantDomain); + if (registry == null) + { + init(); + registry = registryMap.get(tenantDomain); + } + return registry; + } + + public TypeDefinitionWrapper findType(String typeId) + { + return getRegistry().typeDefsByTypeId.get(typeId); + } + + public TypeDefinitionWrapper findTypeForClass(QName clazz, BaseTypeId... matchingScopes) + { + // searching for relationship + boolean scopeByRelationship = false; + for (BaseTypeId scope : matchingScopes) + { + if (scope == BaseTypeId.CMIS_RELATIONSHIP) + { + scopeByRelationship = true; + break; + } + } + + // locate type in registry + clazz = cmisMapping.getCmisType(clazz); + TypeDefinitionWrapper typeDef = null; + if (scopeByRelationship) + { + typeDef = getRegistry().assocDefsByQName.get(clazz); + } else + { + typeDef = getRegistry().typeDefsByQName.get(clazz); + if (typeDef == null) + { + typeDef = getRegistry().assocDefsByQName.get(clazz); + } + } + + // ensure matches one of provided matching scopes + TypeDefinitionWrapper matchingTypeDef = (matchingScopes.length == 0) ? typeDef : null; + if (typeDef != null) + { + for (BaseTypeId scope : matchingScopes) + { + if (typeDef.getBaseTypeId() == scope) + { + matchingTypeDef = typeDef; + break; + } + } + } + + return matchingTypeDef; + } + + public TypeDefinitionWrapper findNodeType(QName clazz) + { + return getRegistry().typeDefsByQName.get(cmisMapping.getCmisType(clazz)); + } + + public TypeDefinitionWrapper findAssocType(QName clazz) + { + return getRegistry().assocDefsByQName.get(cmisMapping.getCmisType(clazz)); + } + + public TypeDefinitionWrapper findTypeByQueryName(String queryName) + { + return getRegistry().typeDefsByQueryName.get(queryName); + } + + public QName getAlfrescoClass(QName name) + { + return cmisMapping.getAlfrescoClass(name); + } + + public PropertyDefintionWrapper findProperty(String propId) + { + return getRegistry().propDefbyPropId.get(propId); + } + + @Override + public PropertyDefintionWrapper findPropertyByQueryName(String queryName) + { + return getRegistry().propDefbyQueryName.get(queryName); + } + + public List getBaseTypes() + { + return Collections.unmodifiableList(getRegistry().baseTypes); + } + + public List getAllTypes() + { + return Collections.unmodifiableList(new ArrayList(getRegistry().typeDefsByTypeId + .values())); + } + + public PropertyType findDataType(QName dataType) + { + return cmisMapping.getDataType(dataType); + } + + public QName findAlfrescoDataType(PropertyType propertyType) + { + return cmisMapping.getAlfrescoDataType(propertyType); + } + + /** + * Factory for creating CMIS Definitions + * + * @param registry + */ + abstract protected void createDefinitions(DictionaryRegistry registry); + + /** + * Dictionary Initialization - creates a new registry + */ + private void init() + { + DictionaryRegistry registry = new DictionaryRegistry(); + + if (logger.isDebugEnabled()) + logger.debug("Creating type definitions..."); + + // phase 1: construct type definitions and link them together + createDefinitions(registry); + for (AbstractTypeDefinitionWrapper objectTypeDef : registry.typeDefsByTypeId.values()) + { + objectTypeDef.connectParentAndSubTypes(cmisMapping, registry, dictionaryService); + } + + // phase 2: register base types and inherit property definitions + for (AbstractTypeDefinitionWrapper typeDef : registry.typeDefsByTypeId.values()) + { + if (typeDef.getTypeDefinition(false).getParentTypeId() == null) + { + registry.baseTypes.add(typeDef); + typeDef.resolveInheritance(cmisMapping, serviceRegistry, registry, dictionaryService); + } + } + + // phase 3: register properties + for (AbstractTypeDefinitionWrapper typeDef : registry.typeDefsByTypeId.values()) + { + registry.registerPropertyDefinitions(typeDef); + } + + // phase 4: assert valid + for (AbstractTypeDefinitionWrapper typeDef : registry.typeDefsByTypeId.values()) + { + typeDef.assertComplete(); + } + + // publish new registry + registryMap.put(tenantService.getCurrentUserDomain(), registry); + + if (logger.isInfoEnabled()) + logger.info("Initialized CMIS Dictionary. Types:" + registry.typeDefsByTypeId.size() + ", Base Types:" + + registry.baseTypes.size()); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.dictionary.DictionaryListener#onInit() + */ + public void onDictionaryInit() + { + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.dictionary.DictionaryListener#afterInit() + */ + public void afterDictionaryInit() + { + init(); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.dictionary.DictionaryListener#afterDictionaryDestroy() + */ + public void afterDictionaryDestroy() + { + registryMap.remove(tenantService.getCurrentUserDomain()); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.extensions.surf.util.AbstractLifecycleBean#onBootstrap + * (org.springframework.context.ApplicationEvent) + */ + protected void onBootstrap(ApplicationEvent event) + { + afterDictionaryInit(); + dictionaryDAO.register(this); + } + + /* + * (non-Javadoc) + * + * @see + * org.springframework.extensions.surf.util.AbstractLifecycleBean#onShutdown + * (org.springframework.context.ApplicationEvent) + */ + protected void onShutdown(ApplicationEvent event) + { + } + +} diff --git a/source/java/org/alfresco/opencmis/dictionary/CMISAllowedActionEnum.java b/source/java/org/alfresco/opencmis/dictionary/CMISAllowedActionEnum.java new file mode 100644 index 0000000000..1b457d4ff4 --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/CMISAllowedActionEnum.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.chemistry.opencmis.commons.BasicPermissions; + +/** + * CMIS Allowed Action Enum + * + * @author davidc + */ +public enum CMISAllowedActionEnum +{ + + // navigation services + CAN_GET_DESCENDANTS("canGetDescendants", "canGetDescendents.Folder", BasicPermissions.READ, "canGetDescendents.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), + //CAN_GET_FOLDER_TREE("canGetFolderTree", "canGetFolderTree.Folder", BasicPermissions.READ, "canGetFolderTree.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), + CAN_GET_CHILDREN("canGetChildren", "canGetChildren.Folder", BasicPermissions.READ, "canGetChildren.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadChildren"), + CAN_GET_FOLDER_PARENT("canGetFolderParent", "canGetParents.Folder", BasicPermissions.READ, "canGetParents.Folder", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), + CAN_GET_OBJECT_PARENTS("canGetObjectParents", "canGetFolderParent.Object", BasicPermissions.READ, "canGetFolderParent.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), + + // object services + CAN_CREATE_DOCUMENT("canCreateDocument", "canCreateDocument.Folder", BasicPermissions.ALL, "canCreateDocument.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), + CAN_CREATE_FOLDER("canCreateFolder", "canCreateFolder.Folder", BasicPermissions.ALL, "canCreateFolder.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), + CAN_CREATE_RELATIONSHIP("canCreateRelationship"), + CAN_GET_PROPERTIES("canGetProperties", "canGetProperties.Object", BasicPermissions.READ, "canGetProperties.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), + CAN_GET_RENDITIONS("canGetRenditions"/*, "canGetRenditions.Object", BasicPermissions.READ, "canGetRenditions.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"*/), + CAN_GET_CONTENT_STREAM("canGetContentStream", "canViewContent.Object", BasicPermissions.READ, "canViewContent.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadContent"), + CAN_UPDATE_PROPERTIES("canUpdateProperties", "canUpdateProperties.Object", BasicPermissions.WRITE, "canUpdateProperties.Object", "{http://www.alfresco.org/model/system/1.0}base.WriteProperties"), + CAN_MOVE_OBJECT("canMoveObject", "canMove.Object", BasicPermissions.ALL, "canMove.Target", BasicPermissions.ALL, "canMove.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode", "canMove.Target", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), + CAN_DELETE_OBJECT("canDeleteObject", "canDelete.Object", BasicPermissions.ALL, "canDelete.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), + CAN_SET_CONTENT_STREAM("canSetContentStream", "canSetContent.Document", BasicPermissions.WRITE, "canSetContent.Document", "{http://www.alfresco.org/model/system/1.0}base.WriteContent"), + CAN_DELETE_CONTENT_STREAM("canDeleteContentStream", "canDeleteContent.Document", BasicPermissions.WRITE, "canDeleteContent.Document", "{http://www.alfresco.org/model/system/1.0}base.WriteContent"), + CAN_DELETE_TREE("canDeleteTree", "canDeleteTree.Folder", BasicPermissions.ALL, "canDeleteTree.Folder", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), + + // multi-filing services + CAN_ADD_OBJECT_TO_FOLDER("canAddObjectToFolder", "canAddToFolder.Object", BasicPermissions.READ, "canAddToFolder.Folder", BasicPermissions.ALL, "canAddToFolder.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties", "canAddToFolder.Folder", "{http://www.alfresco.org/model/system/1.0}base.CreateChildren"), + CAN_REMOVE_OBJECT_FROM_FOLDER("canRemoveObjectFromFolder", "canRemoveFromFolder.Object", BasicPermissions.ALL, "canRemoveFromFolder.Object", "{http://www.alfresco.org/model/system/1.0}base.DeleteNode"), + + // versioning services + CAN_CHECKOUT("canCheckOut", "canCheckout.Document", BasicPermissions.ALL, "canCheckout.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CheckOut"), + CAN_CANCEL_CHECKOUT("canCancelCheckOut", "canCancelCheckout.Document", BasicPermissions.ALL, "canCancelCheckout.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CancelCheckOut"), + CAN_CHECKIN("canCheckIn", "canCheckin.Document", BasicPermissions.ALL, "canCheckin.Document", "{http://www.alfresco.org/model/content/1.0}lockable.CheckIn"), + CAN_GET_ALL_VERSIONS("canGetAllVersions", "canGetAllVersions.VersionSeries", BasicPermissions.READ, "canGetAllVersions.VersionSeries", "{http://www.alfresco.org/model/system/1.0}base.Read"), + + // relationship services + CAN_GET_OBJECT_RELATIONSHIPS("canGetObjectRelationships"), + + // policy services + CAN_APPLY_POLICY("canApplyPolicy", "canAddPolicy.Object", BasicPermissions.WRITE, "canAddPolicy.Policy", BasicPermissions.READ, "canAddPolicy.Object", "{http://www.alfresco.org/model/system/1.0}base.Write"), + CAN_REMOVE_POLICY("canRemovePolicy", "canRemovePolicy.Object", BasicPermissions.WRITE, "canRemovePolicy.Policy", BasicPermissions.READ, "canRemovePolicy.Object", "{http://www.alfresco.org/model/system/1.0}base.Write"), + CAN_GET_APPLIED_POLICIES("canGetAppliedPolicies", "canGetAppliedPolicies.Object", BasicPermissions.READ, "canGetAppliedPolicies.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadProperties"), + + // acl services + CAN_GET_ACL("canGetACL", "canGetACL.Object", BasicPermissions.ALL, "canGetACL.Object", "{http://www.alfresco.org/model/system/1.0}base.ReadPermissions"), + CAN_APPLY_ACL("canApplyACL", "canApplyACL.Object", BasicPermissions.ALL, "canApplyACL.Object", "{http://www.alfresco.org/model/system/1.0}base.ChangePermissions"); + + + private String label; + + private Map> mapping = new HashMap>(); + + /** + * Construct + * + * @param label + */ + CMISAllowedActionEnum(String label, String ... keysAndPermissions) + { + this.label = label; + assert(keysAndPermissions.length % 2 == 0); + for(int i = 0; i < keysAndPermissions.length; i++) + { + String key = keysAndPermissions[i]; + String permission = keysAndPermissions[++i]; + List permissions = mapping.get(key); + if(permissions == null) + { + permissions = new ArrayList(1); + mapping.put(key, permissions); + } + permissions.add(permission); + } + } + + + public String getLabel() + { + return label; + } + + public Map> getPermissionMapping() + { + return mapping; + } +} diff --git a/source/java/org/alfresco/opencmis/dictionary/CMISDictionaryService.java b/source/java/org/alfresco/opencmis/dictionary/CMISDictionaryService.java new file mode 100644 index 0000000000..715ad3292f --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/CMISDictionaryService.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General License for more details. + * + * You should have received a copy of the GNU Lesser General License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.util.List; + +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; + +/** + * Service to query the CMIS meta model + * + * @author davidc + */ +public interface CMISDictionaryService +{ + /** + * Find type for type id + * + * @param typeId + * @return + */ + TypeDefinitionWrapper findType(String typeId); + + /** + * Find type for Alfresco class name. Optionally, constrain match to one of + * specified CMIS scopes + * + * @param clazz + * @param matchingScopes + * @return + */ + TypeDefinitionWrapper findTypeForClass(QName clazz, BaseTypeId... matchingScopes); + + TypeDefinitionWrapper findNodeType(QName clazz); + + TypeDefinitionWrapper findAssocType(QName clazz); + + PropertyDefintionWrapper findProperty(String propId); + + PropertyDefintionWrapper findPropertyByQueryName(String queryName); + + /** + * Find a type by its query name + * + * @param queryName + * @return + */ + TypeDefinitionWrapper findTypeByQueryName(String queryName); + + /** + * Get Base Types + */ + List getBaseTypes(); + + /** + * Get all Types + * + * @return + */ + List getAllTypes(); + + /** + * Find data type + * + * @param dataType + * @return + */ + PropertyType findDataType(QName dataType); + + QName findAlfrescoDataType(PropertyType propertyType); +} diff --git a/source/java/org/alfresco/opencmis/dictionary/CMISStrictDictionaryService.java b/source/java/org/alfresco/opencmis/dictionary/CMISStrictDictionaryService.java new file mode 100644 index 0000000000..1060db530e --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/CMISStrictDictionaryService.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.util.Collection; + +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; + +/** + * CMIS Dictionary which provides Types that strictly conform to the CMIS + * specification. + * + * That is, only maps types to one of root Document, Folder, Relationship & + * Policy. + * + * @author davidc + */ +public class CMISStrictDictionaryService extends CMISAbstractDictionaryService +{ + + @Override + protected void createDefinitions(DictionaryRegistry registry) + { + createTypeDefs(registry, dictionaryService.getAllTypes()); + createAssocDefs(registry, dictionaryService.getAllAssociations()); + createTypeDefs(registry, dictionaryService.getAllAspects()); + } + + /** + * Create Type Definitions + * + * @param registry + * @param classQNames + */ + private void createTypeDefs(DictionaryRegistry registry, Collection classQNames) + { + for (QName classQName : classQNames) + { + // skip items that are remapped to CMIS model + if (cmisMapping.isRemappedType(classQName)) + continue; + + // create appropriate kind of type definition + ClassDefinition classDef = dictionaryService.getClass(classQName); + String typeId = null; + AbstractTypeDefinitionWrapper objectTypeDef = null; + if (cmisMapping.isValidCmisDocument(classQName)) + { + typeId = cmisMapping.getCmisTypeId(BaseTypeId.CMIS_DOCUMENT, classQName); + objectTypeDef = new DocumentTypeDefinitionWrapper(cmisMapping, serviceRegistry, typeId, classDef); + } else if (cmisMapping.isValidCmisFolder(classQName)) + { + typeId = cmisMapping.getCmisTypeId(BaseTypeId.CMIS_FOLDER, classQName); + objectTypeDef = new FolderTypeDefintionWrapper(cmisMapping, serviceRegistry, typeId, classDef); + } else if (cmisMapping.isValidCmisRelationship(classQName)) + { + typeId = cmisMapping.getCmisTypeId(BaseTypeId.CMIS_RELATIONSHIP, classQName); + objectTypeDef = new RelationshipTypeDefintionWrapper(cmisMapping, serviceRegistry, typeId, classDef); + } else if (cmisMapping.isValidCmisPolicy(classQName)) + { + typeId = cmisMapping.getCmisTypeId(BaseTypeId.CMIS_POLICY, classQName); + objectTypeDef = new PolicyTypeDefintionWrapper(cmisMapping, serviceRegistry, typeId, classDef); + } + + if (objectTypeDef != null) + { + registry.registerTypeDefinition(objectTypeDef); + } + } + } + + /** + * Create Relationship Definitions + * + * @param registry + * @param classQNames + */ + private void createAssocDefs(DictionaryRegistry registry, Collection classQNames) + { + for (QName classQName : classQNames) + { + if (!cmisMapping.isValidCmisRelationship(classQName)) + continue; + + // create appropriate kind of type definition + AssociationDefinition assocDef = dictionaryService.getAssociation(classQName); + String typeId = cmisMapping.getCmisTypeId(BaseTypeId.CMIS_RELATIONSHIP, classQName); + RelationshipTypeDefintionWrapper objectTypeDef = new RelationshipTypeDefintionWrapper(cmisMapping, typeId, + assocDef); + + registry.registerTypeDefinition(objectTypeDef); + } + } +} diff --git a/source/java/org/alfresco/opencmis/dictionary/DocumentTypeDefinitionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/DocumentTypeDefinitionWrapper.java new file mode 100644 index 0000000000..80d1b21b2e --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/DocumentTypeDefinitionWrapper.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import org.alfresco.opencmis.CMISUtils; +import org.alfresco.opencmis.mapping.CMISMapping; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.DocumentTypeDefinitionImpl; + +public class DocumentTypeDefinitionWrapper extends ShadowTypeDefinitionWrapper +{ + private static final long serialVersionUID = 1L; + + private DocumentTypeDefinitionImpl typeDef; + private DocumentTypeDefinitionImpl typeDefInclProperties; + + public DocumentTypeDefinitionWrapper(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, String typeId, + ClassDefinition cmisClassDef) + { + alfrescoName = cmisClassDef.getName(); + alfrescoClass = cmisMapping.getAlfrescoClass(alfrescoName); + + typeDef = new DocumentTypeDefinitionImpl(); + + typeDef.setBaseTypeId(BaseTypeId.CMIS_DOCUMENT); + typeDef.setId(typeId); + typeDef.setLocalName(alfrescoName.getLocalName()); + typeDef.setLocalNamespace(alfrescoName.getNamespaceURI()); + + if (BaseTypeId.CMIS_DOCUMENT.value().equals(typeId)) + { + typeDef.setQueryName(typeId); + typeDef.setParentTypeId(null); + } else + { + typeDef.setQueryName(cmisMapping.buildPrefixEncodedString(alfrescoName)); + QName parentQName = cmisMapping.getCmisType(cmisClassDef.getParentName()); + if (cmisMapping.isValidCmisDocument(parentQName)) + { + typeDef.setParentTypeId(cmisMapping.getCmisTypeId(BaseTypeId.CMIS_DOCUMENT, parentQName)); + } + } + + typeDef.setDisplayName((cmisClassDef.getTitle() != null) ? cmisClassDef.getTitle() : typeId); + typeDef.setDescription(cmisClassDef.getDescription() != null ? cmisClassDef.getDescription() : typeDef + .getDisplayName()); + + typeDef.setIsCreatable(true); + typeDef.setIsQueryable(true); + typeDef.setIsFulltextIndexed(true); + typeDef.setIsControllablePolicy(false); + typeDef.setIsControllableAcl(true); + typeDef.setIsIncludedInSupertypeQuery(cmisClassDef.getIncludedInSuperTypeQuery()); + typeDef.setIsFileable(true); + typeDef.setContentStreamAllowed(ContentStreamAllowed.ALLOWED); + typeDef.setIsVersionable(true); + + typeDefInclProperties = CMISUtils.copy(typeDef); + setTypeDefinition(typeDef, typeDefInclProperties); + + createOwningPropertyDefinitions(cmisMapping, serviceRegistry, cmisClassDef); + + actionEvaluators = cmisMapping.getActionEvaluators(BaseTypeId.CMIS_DOCUMENT); + } +} diff --git a/source/java/org/alfresco/opencmis/dictionary/FolderTypeDefintionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/FolderTypeDefintionWrapper.java new file mode 100644 index 0000000000..a45de14daf --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/FolderTypeDefintionWrapper.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISUtils; +import org.alfresco.opencmis.mapping.CMISMapping; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.FolderTypeDefinitionImpl; + +public class FolderTypeDefintionWrapper extends ShadowTypeDefinitionWrapper +{ + private static final long serialVersionUID = 1L; + + private FolderTypeDefinitionImpl typeDef; + private FolderTypeDefinitionImpl typeDefInclProperties; + + public FolderTypeDefintionWrapper(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, String typeId, + ClassDefinition cmisClassDef) + { + alfrescoName = cmisClassDef.getName(); + alfrescoClass = cmisMapping.getAlfrescoClass(alfrescoName); + + typeDef = new FolderTypeDefinitionImpl(); + + typeDef.setBaseTypeId(BaseTypeId.CMIS_FOLDER); + typeDef.setId(typeId); + typeDef.setLocalName(alfrescoName.getLocalName()); + typeDef.setLocalNamespace(alfrescoName.getNamespaceURI()); + + boolean isSystemFolder = false; + if (BaseTypeId.CMIS_FOLDER.value().equals(typeId)) + { + typeDef.setQueryName(typeId); + typeDef.setParentTypeId(null); + } else + { + typeDef.setQueryName(cmisMapping.buildPrefixEncodedString(alfrescoName)); + QName parentQName = cmisMapping.getCmisType(cmisClassDef.getParentName()); + if (cmisMapping.isValidCmisFolder(parentQName)) + { + typeDef.setParentTypeId(cmisMapping.getCmisTypeId(BaseTypeId.CMIS_FOLDER, parentQName)); + } + + if (alfrescoName.equals(ContentModel.TYPE_SYSTEM_FOLDER) + || serviceRegistry.getDictionaryService().isSubClass(alfrescoName, ContentModel.TYPE_SYSTEM_FOLDER)) + { + isSystemFolder = true; + } + } + + typeDef.setDisplayName((cmisClassDef.getTitle() != null) ? cmisClassDef.getTitle() : typeId); + typeDef.setDescription(cmisClassDef.getDescription() != null ? cmisClassDef.getDescription() : typeDef + .getDisplayName()); + + typeDef.setIsCreatable(!isSystemFolder); + typeDef.setIsQueryable(true); + typeDef.setIsFulltextIndexed(true); + typeDef.setIsControllablePolicy(false); + typeDef.setIsControllableAcl(true); + typeDef.setIsIncludedInSupertypeQuery(cmisClassDef.getIncludedInSuperTypeQuery()); + typeDef.setIsFileable(true); + + typeDefInclProperties = CMISUtils.copy(typeDef); + setTypeDefinition(typeDef, typeDefInclProperties); + + createOwningPropertyDefinitions(cmisMapping, serviceRegistry, cmisClassDef); + + actionEvaluators = cmisMapping.getActionEvaluators(BaseTypeId.CMIS_FOLDER); + } +} diff --git a/source/java/org/alfresco/opencmis/dictionary/PolicyTypeDefintionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/PolicyTypeDefintionWrapper.java new file mode 100644 index 0000000000..b7bf3b1c74 --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/PolicyTypeDefintionWrapper.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.util.ArrayList; +import java.util.Collection; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.opencmis.CMISUtils; +import org.alfresco.opencmis.dictionary.CMISAbstractDictionaryService.DictionaryRegistry; +import org.alfresco.opencmis.mapping.CMISMapping; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyTypeDefinitionImpl; + +public class PolicyTypeDefintionWrapper extends AbstractTypeDefinitionWrapper +{ + private static final long serialVersionUID = 1L; + + private PolicyTypeDefinitionImpl typeDef; + private PolicyTypeDefinitionImpl typeDefInclProperties; + + public PolicyTypeDefintionWrapper(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, String typeId, + ClassDefinition cmisClassDef) + { + alfrescoName = cmisClassDef.getName(); + alfrescoClass = cmisMapping.getAlfrescoClass(alfrescoName); + + typeDef = new PolicyTypeDefinitionImpl(); + + typeDef.setBaseTypeId(BaseTypeId.CMIS_POLICY); + typeDef.setId(typeId); + typeDef.setLocalName(alfrescoName.getLocalName()); + typeDef.setLocalNamespace(alfrescoName.getNamespaceURI()); + + if (BaseTypeId.CMIS_POLICY.value().equals(typeId)) + { + typeDef.setQueryName(typeId); + typeDef.setParentTypeId(null); + } else + { + typeDef.setQueryName(cmisMapping.buildPrefixEncodedString(alfrescoName)); + QName parentQName = cmisMapping.getCmisType(cmisClassDef.getParentName()); + if (parentQName == null) + { + typeDef.setParentTypeId(cmisMapping.getCmisTypeId(CMISMapping.ASPECTS_QNAME)); + } else if (cmisMapping.isValidCmisPolicy(parentQName)) + { + typeDef.setParentTypeId(cmisMapping.getCmisTypeId(BaseTypeId.CMIS_POLICY, parentQName)); + } + } + + typeDef.setDisplayName((cmisClassDef.getTitle() != null) ? cmisClassDef.getTitle() : typeId); + typeDef.setDescription(cmisClassDef.getDescription() != null ? cmisClassDef.getDescription() : typeDef + .getDisplayName()); + + typeDef.setIsCreatable(false); + typeDef.setIsQueryable(true); + typeDef.setIsFulltextIndexed(true); + typeDef.setIsControllablePolicy(false); + typeDef.setIsControllableAcl(false); + typeDef.setIsIncludedInSupertypeQuery(cmisClassDef.getIncludedInSuperTypeQuery()); + typeDef.setIsFileable(true); + + typeDefInclProperties = CMISUtils.copy(typeDef); + setTypeDefinition(typeDef, typeDefInclProperties); + + createOwningPropertyDefinitions(cmisMapping, serviceRegistry, cmisClassDef); + + actionEvaluators = cmisMapping.getActionEvaluators(BaseTypeId.CMIS_POLICY); + } + + public void connectParentAndSubTypes(CMISMapping cmisMapping, DictionaryRegistry registry, + DictionaryService dictionaryService) + { + // find parent + if (typeDef.getParentTypeId() != null) + { + parent = registry.typeDefsByTypeId.get(typeDef.getParentTypeId()); + } else + { + if (!isBaseType()) + { + throw new AlfrescoRuntimeException("Type " + typeDef.getId() + " has no parent!"); + } + + parent = null; + } + + // find children + children = new ArrayList(); + Collection childrenNames = null; + + if (isBaseType()) + { + // add the "Aspects" type to the CMIS Policy type + childrenNames = new ArrayList(); + childrenNames.add(CMISMapping.ASPECTS_QNAME); + } else if (getAlfrescoName().equals(CMISMapping.ASPECTS_QNAME)) + { + // add all root aspects to the "Aspects" type + childrenNames = new ArrayList(); + + String aspectsTypeId = cmisMapping.getCmisTypeId(CMISMapping.ASPECTS_QNAME); + for (AbstractTypeDefinitionWrapper tdw : registry.typeDefsByTypeId.values()) + { + String parentId = tdw.getTypeDefinition(false).getParentTypeId(); + if ((parentId != null) && parentId.equals(aspectsTypeId)) + { + childrenNames.add(tdw.getAlfrescoName()); + } + } + } else + { + // add all non-root aspects to their parent + childrenNames = dictionaryService.getSubAspects(cmisMapping.getAlfrescoClass(getAlfrescoName()), false); + } + + for (QName childName : childrenNames) + { + if (cmisMapping.isValidCmisPolicy(childName)) + { + TypeDefinitionWrapper child = registry.typeDefsByQName.get(childName); + + if (child == null) + { + throw new AlfrescoRuntimeException("Failed to retrieve sub type for type id " + childName + + " for parent type " + getAlfrescoName() + "!"); + } + children.add(child); + } else + { + System.out.println("Not a policy: " + childName); + } + } + } + + public void resolveInheritance(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, + DictionaryRegistry registry, DictionaryService dictionaryService) + { + PropertyDefinition propertyDefintion; + + if (parent != null) + { + for (PropertyDefintionWrapper propDef : parent.getProperties()) + { + if (propertiesById.containsKey(propDef.getPropertyId())) + { + continue; + } + + org.alfresco.service.cmr.dictionary.PropertyDefinition alfrescoPropDef = dictionaryService.getProperty( + propDef.getOwningType().getAlfrescoName(), propDef.getAlfrescoName()); + + propertyDefintion = createPropertyDefinition(cmisMapping, propDef.getPropertyId(), + alfrescoPropDef.getName(), alfrescoPropDef, true); + + if (propertyDefintion != null) + { + registerProperty(new BasePropertyDefintionWrapper(propertyDefintion, alfrescoPropDef.getName(), + propDef.getOwningType(), propDef.getPropertyAccessor(), propDef.getPropertyLuceneBuilder())); + } + } + } + + for (TypeDefinitionWrapper child : children) + { + if (child instanceof AbstractTypeDefinitionWrapper) + { + ((AbstractTypeDefinitionWrapper) child).resolveInheritance(cmisMapping, serviceRegistry, registry, + dictionaryService); + } + } + } +} diff --git a/source/java/org/alfresco/opencmis/dictionary/PropertyDefintionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/PropertyDefintionWrapper.java new file mode 100644 index 0000000000..e92384f991 --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/PropertyDefintionWrapper.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import org.alfresco.cmis.CMISPropertyAccessor; +import org.alfresco.cmis.CMISPropertyLuceneBuilder; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; + +public interface PropertyDefintionWrapper +{ + PropertyDefinition getPropertyDefinition(); + + String getPropertyId(); + + QName getAlfrescoName(); + + TypeDefinitionWrapper getOwningType(); + + CMISPropertyAccessor getPropertyAccessor(); + + CMISPropertyLuceneBuilder getPropertyLuceneBuilder(); +} diff --git a/source/java/org/alfresco/opencmis/dictionary/RelationshipTypeDefintionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/RelationshipTypeDefintionWrapper.java new file mode 100644 index 0000000000..4819728df3 --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/RelationshipTypeDefintionWrapper.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.util.ArrayList; +import java.util.Collections; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.opencmis.CMISUtils; +import org.alfresco.opencmis.dictionary.CMISAbstractDictionaryService.DictionaryRegistry; +import org.alfresco.opencmis.mapping.CMISMapping; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.ClassDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.impl.dataobjects.RelationshipTypeDefinitionImpl; + +public class RelationshipTypeDefintionWrapper extends AbstractTypeDefinitionWrapper +{ + private static final long serialVersionUID = 1L; + + private RelationshipTypeDefinitionImpl typeDef; + private RelationshipTypeDefinitionImpl typeDefInclProperties; + + public RelationshipTypeDefintionWrapper(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, String typeId, + ClassDefinition cmisClassDef) + { + alfrescoName = cmisClassDef.getName(); + alfrescoClass = cmisMapping.getAlfrescoClass(alfrescoName); + + typeDef = new RelationshipTypeDefinitionImpl(); + + typeDef.setBaseTypeId(BaseTypeId.CMIS_RELATIONSHIP); + typeDef.setId(typeId); + typeDef.setLocalName(alfrescoName.getLocalName()); + typeDef.setLocalNamespace(alfrescoName.getNamespaceURI()); + + if (BaseTypeId.CMIS_RELATIONSHIP.value().equals(typeId)) + { + typeDef.setQueryName(typeId); + typeDef.setParentTypeId(null); + } else + { + typeDef.setQueryName(cmisMapping.buildPrefixEncodedString(alfrescoName)); + typeDef.setParentTypeId(BaseTypeId.CMIS_RELATIONSHIP.value()); + } + + typeDef.setDisplayName(cmisClassDef.getTitle() != null ? cmisClassDef.getTitle() : typeId); + typeDef.setDescription(cmisClassDef.getDescription() != null ? cmisClassDef.getDescription() : typeDef + .getDisplayName()); + + typeDef.setIsCreatable(true); + typeDef.setIsQueryable(false); + typeDef.setIsFulltextIndexed(false); + typeDef.setIsControllablePolicy(false); + typeDef.setIsControllableAcl(false); + typeDef.setIsIncludedInSupertypeQuery(true); + typeDef.setIsFileable(false); + + typeDefInclProperties = CMISUtils.copy(typeDef); + setTypeDefinition(typeDef, typeDefInclProperties); + + createOwningPropertyDefinitions(cmisMapping, serviceRegistry, cmisClassDef); + + actionEvaluators = cmisMapping.getActionEvaluators(BaseTypeId.CMIS_RELATIONSHIP); + } + + public RelationshipTypeDefintionWrapper(CMISMapping cmisMapping, String typeId, AssociationDefinition cmisAssocDef) + { + alfrescoName = cmisAssocDef.getName(); + alfrescoClass = cmisMapping.getAlfrescoClass(alfrescoName); + + typeDef = new RelationshipTypeDefinitionImpl(); + + typeDef.setBaseTypeId(BaseTypeId.CMIS_RELATIONSHIP); + typeDef.setId(typeId); + typeDef.setLocalName(alfrescoName.getLocalName()); + typeDef.setLocalNamespace(alfrescoName.getNamespaceURI()); + + typeDef.setQueryName(cmisMapping.buildPrefixEncodedString(alfrescoName)); + typeDef.setParentTypeId(BaseTypeId.CMIS_RELATIONSHIP.value()); + + typeDef.setDisplayName(cmisAssocDef.getTitle() != null ? cmisAssocDef.getTitle() : typeId); + typeDef.setDescription(cmisAssocDef.getDescription() != null ? cmisAssocDef.getDescription() : typeDef + .getDisplayName()); + + typeDef.setIsCreatable(true); + typeDef.setIsQueryable(false); + typeDef.setIsFulltextIndexed(false); + typeDef.setIsControllablePolicy(false); + typeDef.setIsControllableAcl(false); + typeDef.setIsIncludedInSupertypeQuery(true); + typeDef.setIsFileable(false); + + String sourceTypeId = cmisMapping.getCmisTypeId(cmisMapping + .getCmisType(cmisAssocDef.getSourceClass().getName())); + if (sourceTypeId != null) + { + typeDef.setAllowedSourceTypes(Collections.singletonList(sourceTypeId)); + } + + String targetTypeId = cmisMapping.getCmisTypeId(cmisMapping + .getCmisType(cmisAssocDef.getTargetClass().getName())); + if (targetTypeId != null) + { + typeDef.setAllowedTargetTypes(Collections.singletonList(targetTypeId)); + } + + typeDefInclProperties = CMISUtils.copy(typeDef); + setTypeDefinition(typeDef, typeDefInclProperties); + + actionEvaluators = cmisMapping.getActionEvaluators(BaseTypeId.CMIS_RELATIONSHIP); + } + + public void connectParentAndSubTypes(CMISMapping cmisMapping, DictionaryRegistry registry, + DictionaryService dictionaryService) + { + // find parent + if (typeDef.getParentTypeId() != null) + { + parent = registry.typeDefsByTypeId.get(typeDef.getParentTypeId()); + } else + { + if (!isBaseType()) + { + throw new AlfrescoRuntimeException("Type " + typeDef.getId() + " has no parent!"); + } + + parent = null; + } + + // find children + children = new ArrayList(); + if (isBaseType()) + { + for (TypeDefinitionWrapper child : registry.assocDefsByQName.values()) + { + children.add(child); + } + } + } + + public void resolveInheritance(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, + DictionaryRegistry registry, DictionaryService dictionaryService) + { + PropertyDefinition propertyDefintion; + + if (parent != null) + { + for (PropertyDefintionWrapper propDef : parent.getProperties()) + { + org.alfresco.service.cmr.dictionary.PropertyDefinition alfrescoPropDef = dictionaryService.getProperty( + propDef.getOwningType().getAlfrescoName(), propDef.getAlfrescoName()); + + propertyDefintion = createPropertyDefinition(cmisMapping, propDef.getPropertyId(), + alfrescoPropDef.getName(), alfrescoPropDef, true); + + if (propertyDefintion != null) + { + registerProperty(new BasePropertyDefintionWrapper(propertyDefintion, alfrescoPropDef.getName(), + propDef.getOwningType(), propDef.getPropertyAccessor(), propDef.getPropertyLuceneBuilder())); + } + } + } + + for (TypeDefinitionWrapper child : children) + { + if (child instanceof AbstractTypeDefinitionWrapper) + { + ((AbstractTypeDefinitionWrapper) child).resolveInheritance(cmisMapping, serviceRegistry, registry, + dictionaryService); + } + } + } +} diff --git a/source/java/org/alfresco/opencmis/dictionary/ShadowTypeDefinitionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/ShadowTypeDefinitionWrapper.java new file mode 100644 index 0000000000..8b7bd70bc4 --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/ShadowTypeDefinitionWrapper.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.util.ArrayList; +import java.util.Collection; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.opencmis.dictionary.CMISAbstractDictionaryService.DictionaryRegistry; +import org.alfresco.opencmis.mapping.CMISMapping; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; + +public abstract class ShadowTypeDefinitionWrapper extends AbstractTypeDefinitionWrapper +{ + + private static final long serialVersionUID = 1L; + + public void connectParentAndSubTypes(CMISMapping cmisMapping, DictionaryRegistry registry, + DictionaryService dictionaryService) + { + // find parent + if (typeDef.getParentTypeId() != null) + { + parent = registry.typeDefsByTypeId.get(typeDef.getParentTypeId()); + } else + { + if (!isBaseType()) + { + throw new AlfrescoRuntimeException("Type " + typeDef.getId() + " has no parent!"); + } + + parent = null; + } + + // find children + children = new ArrayList(); + Collection childrenNames = dictionaryService.getSubTypes(cmisMapping.getAlfrescoClass(getAlfrescoName()), + false); + for (QName childName : childrenNames) + { + if (cmisMapping.isValidCmisObject(getBaseTypeId(), childName)) + { + TypeDefinitionWrapper child = registry.typeDefsByQName.get(childName); + + if (child == null) + { + throw new AlfrescoRuntimeException("Failed to retrieve sub type for type id " + childName + + " for parent type " + getAlfrescoName() + "!"); + } + children.add(child); + } + } + } + + public void resolveInheritance(CMISMapping cmisMapping, ServiceRegistry serviceRegistry, + DictionaryRegistry registry, DictionaryService dictionaryService) + { + PropertyDefinition propertyDefintion; + + if (parent != null) + { + for (PropertyDefintionWrapper propDef : parent.getProperties()) + { + if (propertiesById.containsKey(propDef.getPropertyId())) + { + continue; + } + + org.alfresco.service.cmr.dictionary.PropertyDefinition alfrescoPropDef = dictionaryService.getProperty( + propDef.getOwningType().getAlfrescoName(), propDef.getAlfrescoName()); + + propertyDefintion = createPropertyDefinition(cmisMapping, propDef.getPropertyId(), + alfrescoPropDef.getName(), alfrescoPropDef, true); + + if (propertyDefintion != null) + { + registerProperty(new BasePropertyDefintionWrapper(propertyDefintion, alfrescoPropDef.getName(), + propDef.getOwningType(), propDef.getPropertyAccessor(), propDef.getPropertyLuceneBuilder())); + } + } + } + + for (TypeDefinitionWrapper child : children) + { + if (child instanceof AbstractTypeDefinitionWrapper) + { + ((AbstractTypeDefinitionWrapper) child).resolveInheritance(cmisMapping, serviceRegistry, registry, + dictionaryService); + } + } + } +} diff --git a/source/java/org/alfresco/opencmis/dictionary/TypeDefinitionWrapper.java b/source/java/org/alfresco/opencmis/dictionary/TypeDefinitionWrapper.java new file mode 100644 index 0000000000..c026e72f96 --- /dev/null +++ b/source/java/org/alfresco/opencmis/dictionary/TypeDefinitionWrapper.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.dictionary; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.alfresco.opencmis.CMISActionEvaluator; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; +import org.apache.chemistry.opencmis.commons.enums.Action; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; + +public interface TypeDefinitionWrapper +{ + TypeDefinition getTypeDefinition(boolean includePropertyDefinitions); + + String getTypeId(); + + BaseTypeId getBaseTypeId(); + + boolean isBaseType(); + + QName getAlfrescoName(); + + QName getAlfrescoClass(); + + TypeDefinitionWrapper getParent(); + + List getChildren(); + + Collection getProperties(); + + PropertyDefintionWrapper getPropertyById(String propertyId); + + PropertyDefintionWrapper getPropertyByQueryName(String queryName); + + PropertyDefintionWrapper getPropertyByQName(QName name); + + Map> getActionEvaluators(); +} diff --git a/source/java/org/alfresco/opencmis/mapping/AbstractActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/AbstractActionEvaluator.java new file mode 100644 index 0000000000..e38e382028 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/AbstractActionEvaluator.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.CMISActionEvaluator; +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Base class for all action evaluators + * + * @author davidc + * + */ +public abstract class AbstractActionEvaluator implements CMISActionEvaluator +{ + private ServiceRegistry serviceRegistry; + private Action action; + + /** + * Construct + * + * @param serviceRegistry + * @param action + */ + protected AbstractActionEvaluator(ServiceRegistry serviceRegistry, Action action) + { + this.serviceRegistry = serviceRegistry; + this.action = action; + } + + /** + * @return service registry + */ + protected ServiceRegistry getServiceRegistry() + { + return serviceRegistry; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.opencmis.CMISActionEvaluator#getAction() + */ + public Action getAction() + { + return action; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java b/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java new file mode 100644 index 0000000000..cdaa2e217a --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/AbstractProperty.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Collection; + +import org.alfresco.cmis.CMISPropertyAccessor; +import org.alfresco.cmis.CMISPropertyLuceneBuilder; +import org.alfresco.repo.search.impl.lucene.LuceneFunction; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.Query; + +/** + * Base class for all property accessors + * + * @author andyh + * + */ +public abstract class AbstractProperty implements CMISPropertyAccessor, CMISPropertyLuceneBuilder +{ + private ServiceRegistry serviceRegistry; + private String propertyName; + + /** + * Construct + * + * @param serviceRegistry + * @param propertyName + */ + protected AbstractProperty(ServiceRegistry serviceRegistry, String propertyName) + { + this.serviceRegistry = serviceRegistry; + this.propertyName = propertyName; + } + + /** + * @return service registry + */ + protected ServiceRegistry getServiceRegistry() + { + return serviceRegistry; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyAccessor#getName() + */ + public String getName() + { + return propertyName; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyAccessor#getMappedProperty() + */ + public QName getMappedProperty() + { + return null; + } + + + public Query buildLuceneEquality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return null; + } + + public Query buildLuceneExists(LuceneQueryParser lqp, Boolean not) throws ParseException + { + return null; + } + + public Query buildLuceneGreaterThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return null; + } + + public Query buildLuceneGreaterThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return null; + } + + public Query buildLuceneIn(LuceneQueryParser lqp, Collection values, Boolean not, PredicateMode mode) throws ParseException + { + return null; + } + + public Query buildLuceneInequality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return null; + } + + public Query buildLuceneLessThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return null; + } + + public Query buildLuceneLessThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return null; + } + + public Query buildLuceneLike(LuceneQueryParser lqp, Serializable value, Boolean not) throws ParseException + { + return null; + } + + public String getLuceneFieldName() + { + throw new UnsupportedOperationException(); + } + + public String getLuceneSortField(LuceneQueryParser lqp) + { + throw new UnsupportedOperationException(); + } + + public Serializable getValue(NodeRef nodeRef) + { + throw new UnsupportedOperationException(); + } + + public void setValue(NodeRef nodeRef, Serializable value) + { + throw new UnsupportedOperationException(); + } + + public Serializable getValue(AssociationRef assocRef) + { + throw new UnsupportedOperationException(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/AbstractSimpleProperty.java b/source/java/org/alfresco/opencmis/mapping/AbstractSimpleProperty.java new file mode 100644 index 0000000000..d5532b4075 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/AbstractSimpleProperty.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Collection; + +import org.alfresco.repo.search.impl.lucene.AnalysisMode; +import org.alfresco.repo.search.impl.lucene.LuceneFunction; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.BooleanClause.Occur; + +/** + * Common support for lucene query building. + * + * @author andyh + * + */ +public abstract class AbstractSimpleProperty extends AbstractProperty +{ + + protected AbstractSimpleProperty(ServiceRegistry serviceRegistry, String propertyName) + { + super(serviceRegistry, propertyName); + } + + protected abstract String getValueAsString(Serializable value); + + protected String getRangeMax() + { + return "\uFFFF"; + } + + protected String getRangeMin() + { + return "\u0000"; + } + + protected abstract DataTypeDefinition getInDataType(); + + protected abstract QName getQNameForExists(); + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneEquality(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneEquality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return lqp.getFieldQuery(getLuceneFieldName(), getValueAsString(value), AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneExists(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.lang.Boolean) + */ + public Query buildLuceneExists(LuceneQueryParser lqp, Boolean not) throws ParseException + { + if (not) + { + return lqp.getFieldQuery("ISNULL", getQNameForExists().toString(), AnalysisMode.DEFAULT, LuceneFunction.FIELD); + } + else + { + return lqp.getFieldQuery("ISNOTNULL", getQNameForExists().toString(), AnalysisMode.DEFAULT, LuceneFunction.FIELD); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneGreaterThan(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneGreaterThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getRangeQuery(field, stringValue, getRangeMax(), false, true, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneGreaterThanOrEquals(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneGreaterThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getRangeQuery(field, stringValue, getRangeMax(), true, true, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneIn(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.util.Collection, java.lang.Boolean, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneIn(LuceneQueryParser lqp, Collection values, Boolean not, PredicateMode mode) throws ParseException + { + String field = getLuceneFieldName(); + + // Check type conversion + + @SuppressWarnings("unused") + Object converted = DefaultTypeConverter.INSTANCE.convert(getInDataType(), values); + Collection asStrings = DefaultTypeConverter.INSTANCE.convert(String.class, values); + + if (asStrings.size() == 0) + { + if (not) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + else if (asStrings.size() == 1) + { + String value = asStrings.iterator().next(); + if (not) + { + return lqp.getDoesNotMatchFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } + else + { + return lqp.getFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } + } + else + { + BooleanQuery booleanQuery = new BooleanQuery(); + if (not) + { + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + } + for (String value : asStrings) + { + Query any = lqp.getFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + if (not) + { + booleanQuery.add(any, Occur.MUST_NOT); + } + else + { + booleanQuery.add(any, Occur.SHOULD); + } + } + return booleanQuery; + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneInequality(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneInequality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getDoesNotMatchFieldQuery(field, stringValue, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLessThan(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneLessThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getRangeQuery(field, getRangeMin(), stringValue, true, false, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLessThanOrEquals(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneLessThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getRangeQuery(field, getRangeMin(), stringValue, true, true, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLike(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, java.lang.Boolean) + */ + public Query buildLuceneLike(LuceneQueryParser lqp, Serializable value, Boolean not) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + + if (not) + { + BooleanQuery booleanQuery = new BooleanQuery(); + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + booleanQuery.add(lqp.getLikeQuery(field, stringValue, AnalysisMode.IDENTIFIER), Occur.MUST_NOT); + return booleanQuery; + } + else + { + return lqp.getLikeQuery(field, stringValue, AnalysisMode.IDENTIFIER); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#getLuceneSortField() + */ + public String getLuceneSortField(LuceneQueryParser lqp) + { + return getLuceneFieldName(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/AbstractVersioningProperty.java b/source/java/org/alfresco/opencmis/mapping/AbstractVersioningProperty.java new file mode 100644 index 0000000000..4354aac498 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/AbstractVersioningProperty.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.version.VersionBaseModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.namespace.QName; + +/** + * Base class for versioning property accessors. + * + * @author dward + * + */ +public abstract class AbstractVersioningProperty extends AbstractProperty +{ + + /** + * Construct + * + * @param serviceRegistry + * @param propertyName + */ + protected AbstractVersioningProperty(ServiceRegistry serviceRegistry, String propertyName) + { + super(serviceRegistry, propertyName); + } + + public NodeRef getVersionSeries(NodeRef nodeRef) + { + if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + // Due to the remapping done for us by the versioned node services, + // we can simply look up the properties + // containing the component parts of the node ref to map back to the + // original node + Map properties = getServiceRegistry().getNodeService().getProperties(nodeRef); + return new NodeRef((String) properties.get(ContentModel.PROP_STORE_PROTOCOL), + (String) properties.get(ContentModel.PROP_STORE_IDENTIFIER), + (String) properties.get(ContentModel.PROP_NODE_UUID)); + } else if (isWorkingCopy(nodeRef)) + { + return (NodeRef) getServiceRegistry().getNodeService().getProperty(nodeRef, + ContentModel.PROP_COPY_REFERENCE); + } + return nodeRef; + } + + public boolean isWorkingCopy(NodeRef nodeRef) + { + return getServiceRegistry().getNodeService().hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY); + } + + public boolean hasWorkingCopy(NodeRef nodeRef) + { + return getServiceRegistry().getLockService().getLockType(nodeRef) == LockType.READ_ONLY_LOCK; + } + + public NodeRef getLiveNodeRef(NodeRef nodeRef) + { + if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + VersionHistory versionHistory = getServiceRegistry().getVersionService().getVersionHistory(nodeRef); + if (versionHistory == null) + { + return nodeRef; + } + + Version currentVersion = versionHistory.getHeadVersion(); + Serializable versionLabel = getServiceRegistry().getNodeService().getProperty(nodeRef, + ContentModel.PROP_VERSION_LABEL); + + if (currentVersion.getVersionLabel().equals(versionLabel)) + { + return currentVersion.getVersionedNodeRef(); + } + } + + return nodeRef; + } + + public boolean isCurrentVersion(NodeRef nodeRef) + { + if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + VersionHistory versionHistory = getServiceRegistry().getVersionService().getVersionHistory(nodeRef); + if (versionHistory == null) + { + return true; + } + + Version currentVersion = versionHistory.getHeadVersion(); + Serializable versionLabel = getServiceRegistry().getNodeService().getProperty(nodeRef, + ContentModel.PROP_VERSION_LABEL); + + return currentVersion.getVersionLabel().equals(versionLabel); + } + + return true; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java b/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java new file mode 100644 index 0000000000..eca86a4c7c --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/AllowedChildObjectTypeIdsProperty.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.ChildAssociationDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS allowedChildObjectTypeIds property. + * + * @author florian.mueller + */ +public class AllowedChildObjectTypeIdsProperty extends AbstractProperty +{ + private CMISMapping cmisMapping; + + /** + * Construct + * + * @param serviceRegistry + */ + public AllowedChildObjectTypeIdsProperty(ServiceRegistry serviceRegistry, CMISMapping cmisMapping) + { + super(serviceRegistry, PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS); + this.cmisMapping = cmisMapping; + } + + @Override + public Serializable getValue(NodeRef nodeRef) + { + QName typeQName = getServiceRegistry().getNodeService().getType(nodeRef); + TypeDefinition type = getServiceRegistry().getDictionaryService().getType(typeQName); + if ((type != null) && (type.getChildAssociations() != null) && (!type.getChildAssociations().isEmpty())) + { + ArrayList result = new ArrayList(); + + for (ChildAssociationDefinition cad : type.getChildAssociations().values()) + { + String typeId = cmisMapping.getCmisTypeId(cad.getTargetClass().getName()); + if (typeId != null) + { + result.add(typeId); + } + } + + return result; + } + + return (Serializable) Collections.emptyList(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/BaseTypeIdProperty.java b/source/java/org/alfresco/opencmis/mapping/BaseTypeIdProperty.java new file mode 100644 index 0000000000..659182e3a5 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/BaseTypeIdProperty.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Collection; + +import org.alfresco.cmis.CMISQueryException; +import org.alfresco.cmis.CMISScope; +import org.alfresco.cmis.CMISTypeDefinition; +import org.alfresco.repo.search.impl.lucene.AnalysisMode; +import org.alfresco.repo.search.impl.lucene.LuceneFunction; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; + +/** + * Get the CMIS object type id property + * + * @author andyh + */ +public class BaseTypeIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public BaseTypeIdProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.BASE_TYPE_ID); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service.cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + QName type = getServiceRegistry().getNodeService().getType(nodeRef); + return getServiceRegistry().getCMISDictionaryService().findTypeForClass(type).getBaseType().getTypeId().getId(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service.cmr.repository.AssociationRef) + */ + public Serializable getValue(AssociationRef assocRef) + { + QName type = assocRef.getTypeQName(); + return getServiceRegistry().getCMISDictionaryService().findTypeForClass(type, CMISScope.RELATIONSHIP).getBaseType().getTypeId().getId(); + } + + @Override + public Query buildLuceneEquality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return lqp.getFieldQuery("TYPE", getBaseType(getValueAsString(value)), AnalysisMode.IDENTIFIER, luceneFunction); + } + + @Override + public Query buildLuceneInequality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + return lqp.getDoesNotMatchFieldQuery("TYPE", getBaseType(getValueAsString(value)), AnalysisMode.IDENTIFIER, luceneFunction); + } + + @Override + public Query buildLuceneIn(LuceneQueryParser lqp, Collection values, Boolean not, PredicateMode mode) throws ParseException + { + String field = "TYPE"; + + // Check type conversion + + + Collection asStrings = DefaultTypeConverter.INSTANCE.convert(String.class, values); + + if (asStrings.size() == 0) + { + if (not) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + else if (asStrings.size() == 1) + { + String value = asStrings.iterator().next(); + if (not) + { + return lqp.getDoesNotMatchFieldQuery(field, getBaseType(value), AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } + else + { + return lqp.getFieldQuery(field, getBaseType(value), AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } + } + else + { + BooleanQuery booleanQuery = new BooleanQuery(); + if (not) + { + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + } + for (String value : asStrings) + { + Query any = lqp.getFieldQuery(field, getBaseType(value), AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + if (not) + { + booleanQuery.add(any, Occur.MUST_NOT); + } + else + { + booleanQuery.add(any, Occur.SHOULD); + } + } + return booleanQuery; + } + } + + @Override + public Query buildLuceneExists(LuceneQueryParser lqp, Boolean not) throws ParseException + { + if (not) + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + else + { + return new MatchAllDocsQuery(); + } + } + + + private String getBaseType(String tableName) + { + CMISTypeDefinition typeDef = getServiceRegistry().getCMISDictionaryService().findTypeByQueryName(tableName); + if (typeDef == null) + { + throw new CMISQueryException("Unknwon base type: " + tableName); + } + if(!typeDef.getBaseType().equals(typeDef)) + { + throw new CMISQueryException("Not a base type: " + tableName); + } + if(!typeDef.isQueryable()) + { + throw new CMISQueryException("Base type is not queryable: " + tableName); + } + return typeDef.getTypeId().getQName().toString(); + } + + private String getValueAsString(Serializable value) + { + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, value); + return asString; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/CMISMapping.java b/source/java/org/alfresco/opencmis/mapping/CMISMapping.java new file mode 100644 index 0000000000..76a866ad35 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/CMISMapping.java @@ -0,0 +1,879 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.alfresco.cmis.CMISAccessControlFormatEnum; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISActionEvaluator; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.AssociationDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.chemistry.opencmis.commons.enums.Action; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * CMIS <-> Alfresco mappings + * + * @author andyh + */ +public class CMISMapping implements InitializingBean +{ + // Logger + protected static final Log logger = LogFactory.getLog(CMISMapping.class); + + /** + * The Alfresco CMIS Namespace + */ + public static String CMIS_MODEL_NS = "cmis"; + public static String CMIS_MODEL_URI = "http://www.alfresco.org/model/cmis/1.0/cs01"; + + public static String CMIS_EXT_NS = "cmisext"; + public static String CMIS_EXT_URI = "http://www.alfresco.org/model/cmis/1.0/cs01ext"; + + /** + * The Alfresco CMIS Model name. + */ + public static String CMIS_MODEL_NAME = "cmismodel"; + + /** + * The QName for the Alfresco CMIS Model. + */ + public static QName CMIS_MODEL_QNAME = QName.createQName(CMIS_MODEL_URI, CMIS_MODEL_NAME); + + // CMIS Data Types + public static QName CMIS_DATATYPE_ID = QName.createQName(CMIS_MODEL_URI, "id"); + public static QName CMIS_DATATYPE_URI = QName.createQName(CMIS_MODEL_URI, "uri"); + public static QName CMIS_DATATYPE_XML = QName.createQName(CMIS_MODEL_URI, "xml"); + public static QName CMIS_DATATYPE_HTML = QName.createQName(CMIS_MODEL_URI, "html"); + + // CMIS Types + public static QName OBJECT_QNAME = QName.createQName(CMIS_EXT_URI, "object"); + public static QName DOCUMENT_QNAME = QName.createQName(CMIS_MODEL_URI, "document"); + public static QName FOLDER_QNAME = QName.createQName(CMIS_MODEL_URI, "folder"); + public static QName RELATIONSHIP_QNAME = QName.createQName(CMIS_MODEL_URI, "relationship"); + public static QName POLICY_QNAME = QName.createQName(CMIS_MODEL_URI, "policy"); + public static QName ASPECTS_QNAME = QName.createQName(CMIS_EXT_URI, "aspects"); + + // CMIS Internal Type Ids + public static String OBJECT_TYPE_ID = "cmisext:object"; + + /** + * Basic permissions. + */ + public static final String CMIS_READ = "cmis:read"; + public static final String CMIS_WRITE = "cmis:write"; + public static final String CMIS_ALL = "cmis:all"; + + // Service Dependencies + private ServiceRegistry serviceRegistry; + private CMISConnector cmisConnector; + + // Mappings + private Map mapAlfrescoQNameToTypeId = new HashMap(); + private Map mapCmisQNameToAlfrescoQName = new HashMap(); + private Map mapAlfrescoQNameToCmisQName = new HashMap(); + private Map mapAlfrescoToCmisDataType = new HashMap(); + private Map mapCmisDataTypeToAlfresco = new HashMap(); + private Map propertyAccessors = new HashMap(); + private Map>> actionEvaluators = new HashMap>>(); + + /* + * (non-Javadoc) + * + * @see + * org.springframework.beans.factory.InitializingBean#afterPropertiesSet() + */ + public void afterPropertiesSet() throws Exception + { + // + // Type Mappings + // + + mapAlfrescoQNameToTypeId.put(OBJECT_QNAME, OBJECT_TYPE_ID); + mapAlfrescoQNameToTypeId.put(DOCUMENT_QNAME, BaseTypeId.CMIS_DOCUMENT.value()); + mapAlfrescoQNameToTypeId.put(FOLDER_QNAME, BaseTypeId.CMIS_FOLDER.value()); + mapAlfrescoQNameToTypeId.put(RELATIONSHIP_QNAME, BaseTypeId.CMIS_RELATIONSHIP.value()); + mapAlfrescoQNameToTypeId.put(POLICY_QNAME, BaseTypeId.CMIS_POLICY.value()); + + mapAlfrescoQNameToCmisQName.put(ContentModel.TYPE_CONTENT, DOCUMENT_QNAME); + mapAlfrescoQNameToCmisQName.put(ContentModel.TYPE_FOLDER, FOLDER_QNAME); + + mapCmisQNameToAlfrescoQName.put(DOCUMENT_QNAME, ContentModel.TYPE_CONTENT); + mapCmisQNameToAlfrescoQName.put(FOLDER_QNAME, ContentModel.TYPE_FOLDER); + mapCmisQNameToAlfrescoQName.put(RELATIONSHIP_QNAME, null); + mapCmisQNameToAlfrescoQName.put(POLICY_QNAME, null); + + // + // Data Type Mappings + // + + mapAlfrescoToCmisDataType.put(DataTypeDefinition.ANY, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.ASSOC_REF, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.BOOLEAN, PropertyType.BOOLEAN); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.CATEGORY, PropertyType.ID); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.CHILD_ASSOC_REF, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.CONTENT, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.DATE, PropertyType.DATETIME); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.DATETIME, PropertyType.DATETIME); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.DOUBLE, PropertyType.DECIMAL); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.FLOAT, PropertyType.DECIMAL); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.INT, PropertyType.INTEGER); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.LOCALE, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.PERIOD, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.LONG, PropertyType.INTEGER); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.MLTEXT, PropertyType.STRING); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.NODE_REF, PropertyType.ID); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.PATH, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.QNAME, null); + mapAlfrescoToCmisDataType.put(DataTypeDefinition.TEXT, PropertyType.STRING); + mapAlfrescoToCmisDataType.put(CMIS_DATATYPE_ID, PropertyType.ID); + mapAlfrescoToCmisDataType.put(CMIS_DATATYPE_URI, PropertyType.URI); + mapAlfrescoToCmisDataType.put(CMIS_DATATYPE_HTML, PropertyType.HTML); + + mapCmisDataTypeToAlfresco.put(PropertyType.ID, DataTypeDefinition.TEXT); + mapCmisDataTypeToAlfresco.put(PropertyType.INTEGER, DataTypeDefinition.LONG); + mapCmisDataTypeToAlfresco.put(PropertyType.STRING, DataTypeDefinition.TEXT); + mapCmisDataTypeToAlfresco.put(PropertyType.DECIMAL, DataTypeDefinition.DOUBLE); + mapCmisDataTypeToAlfresco.put(PropertyType.BOOLEAN, DataTypeDefinition.BOOLEAN); + mapCmisDataTypeToAlfresco.put(PropertyType.DATETIME, DataTypeDefinition.DATETIME); + mapCmisDataTypeToAlfresco.put(PropertyType.URI, DataTypeDefinition.TEXT); + mapCmisDataTypeToAlfresco.put(PropertyType.HTML, DataTypeDefinition.TEXT); + + // + // Property Mappings + // + + registerPropertyAccessor(new ObjectIdProperty(serviceRegistry)); + registerPropertyAccessor(new NodeRefProperty(serviceRegistry)); + registerPropertyAccessor(new ObjectTypeIdProperty(serviceRegistry)); + registerPropertyAccessor(new BaseTypeIdProperty(serviceRegistry)); + registerPropertyAccessor(new DirectProperty(serviceRegistry, PropertyIds.CREATED_BY, ContentModel.PROP_CREATOR)); + registerPropertyAccessor(new DirectProperty(serviceRegistry, PropertyIds.CREATION_DATE, + ContentModel.PROP_CREATED)); + registerPropertyAccessor(new DirectProperty(serviceRegistry, PropertyIds.LAST_MODIFIED_BY, + ContentModel.PROP_MODIFIER)); + registerPropertyAccessor(new DirectProperty(serviceRegistry, PropertyIds.LAST_MODIFICATION_DATE, + ContentModel.PROP_MODIFIED)); + registerPropertyAccessor(new FixedValueProperty(serviceRegistry, PropertyIds.CHANGE_TOKEN, null)); + registerPropertyAccessor(new DirectProperty(serviceRegistry, PropertyIds.NAME, ContentModel.PROP_NAME) + { + @Override + public Serializable getValue(AssociationRef assocRef) + { + // Let's use the association ref as the name + return assocRef.toString(); + } + }); + registerPropertyAccessor(new IsImmutableProperty(serviceRegistry)); + registerPropertyAccessor(new IsLatestVersionProperty(serviceRegistry)); + registerPropertyAccessor(new IsMajorVersionProperty(serviceRegistry)); + registerPropertyAccessor(new IsLatestMajorVersionProperty(serviceRegistry)); + registerPropertyAccessor(new VersionLabelProperty(serviceRegistry)); + registerPropertyAccessor(new VersionSeriesIdProperty(serviceRegistry)); + registerPropertyAccessor(new IsVersionSeriesCheckedOutProperty(serviceRegistry)); + registerPropertyAccessor(new VersionSeriesCheckedOutByProperty(serviceRegistry)); + registerPropertyAccessor(new VersionSeriesCheckedOutIdProperty(serviceRegistry)); + registerPropertyAccessor(new CheckinCommentProperty(serviceRegistry)); + registerPropertyAccessor(new ContentStreamLengthProperty(serviceRegistry)); + registerPropertyAccessor(new ContentStreamMimetypeProperty(serviceRegistry)); + registerPropertyAccessor(new ContentStreamIdProperty(serviceRegistry)); + registerPropertyAccessor(new DirectProperty(serviceRegistry, PropertyIds.CONTENT_STREAM_FILE_NAME, + ContentModel.PROP_NAME)); + registerPropertyAccessor(new ParentProperty(serviceRegistry)); + registerPropertyAccessor(new PathProperty(serviceRegistry, cmisConnector)); + registerPropertyAccessor(new AllowedChildObjectTypeIdsProperty(serviceRegistry, this)); + registerPropertyAccessor(new SourceIdProperty(serviceRegistry)); + registerPropertyAccessor(new TargetIdProperty(serviceRegistry)); + + // + // Action Evaluator Mappings + // + + // NOTE: The order of evaluators is important - they must be in the + // order as specified in CMIS-Core.xsd + // so that schema validation passes + + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, + new CurrentVersionEvaluator(serviceRegistry, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_DELETE_OBJECT, PermissionService.DELETE_NODE), false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new PermissionActionEvaluator(serviceRegistry, Action.CAN_UPDATE_PROPERTIES, + PermissionService.WRITE_PROPERTIES), false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_GET_PROPERTIES, PermissionService.READ_PROPERTIES)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_OBJECT_RELATIONSHIPS, true)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new ParentActionEvaluator(new PermissionActionEvaluator( + serviceRegistry, Action.CAN_GET_OBJECT_PARENTS, PermissionService.READ_PERMISSIONS))); + // Is CAN_MOVE correct mapping? + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new PermissionActionEvaluator(serviceRegistry, Action.CAN_MOVE_OBJECT, PermissionService.DELETE_NODE), + false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new PermissionActionEvaluator(serviceRegistry, Action.CAN_DELETE_CONTENT_STREAM, + PermissionService.WRITE_PROPERTIES, PermissionService.WRITE_CONTENT), false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new CanCheckOutActionEvaluator(serviceRegistry), false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new PermissionActionEvaluator(serviceRegistry, Action.CAN_CANCEL_CHECK_OUT, + PermissionService.CANCEL_CHECK_OUT), false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new PermissionActionEvaluator(serviceRegistry, Action.CAN_CHECK_IN, + PermissionService.CHECK_IN)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new PermissionActionEvaluator(serviceRegistry, Action.CAN_SET_CONTENT_STREAM, + PermissionService.WRITE_CONTENT), false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_ALL_VERSIONS, true)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new ParentActionEvaluator(new PermissionActionEvaluator(serviceRegistry, + Action.CAN_ADD_OBJECT_TO_FOLDER, PermissionService.LINK_CHILDREN)), false)); + // Is CAN_REMOVE_FROM_FOLDER correct mapping? + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new ParentActionEvaluator(new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_REMOVE_OBJECT_FROM_FOLDER, true)), false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_GET_CONTENT_STREAM, PermissionService.READ_CONTENT)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_APPLY_POLICY, false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_APPLIED_POLICIES, true)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_REMOVE_POLICY, false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new FixedValueActionEvaluator(serviceRegistry, Action.CAN_CREATE_RELATIONSHIP, true), false)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_RENDITIONS, true)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new PermissionActionEvaluator(serviceRegistry, Action.CAN_GET_ACL, + PermissionService.READ_PERMISSIONS)); + registerEvaluator(BaseTypeId.CMIS_DOCUMENT, new CurrentVersionEvaluator(serviceRegistry, + new PermissionActionEvaluator(serviceRegistry, Action.CAN_APPLY_ACL, + PermissionService.CHANGE_PERMISSIONS), false)); + + registerEvaluator(BaseTypeId.CMIS_FOLDER, + new RootFolderEvaluator(serviceRegistry, cmisConnector, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_DELETE_OBJECT, PermissionService.DELETE_NODE), false)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_UPDATE_PROPERTIES, PermissionService.WRITE_PROPERTIES)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_GET_FOLDER_TREE, PermissionService.READ_CHILDREN)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_GET_PROPERTIES, PermissionService.READ_PROPERTIES)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_OBJECT_RELATIONSHIPS, true)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new ParentActionEvaluator(new PermissionActionEvaluator( + serviceRegistry, Action.CAN_GET_OBJECT_PARENTS, PermissionService.READ_PERMISSIONS))); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new RootFolderEvaluator(serviceRegistry, cmisConnector, + new ParentActionEvaluator(new PermissionActionEvaluator(serviceRegistry, Action.CAN_GET_FOLDER_PARENT, + PermissionService.READ_PERMISSIONS)), false)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_GET_DESCENDANTS, PermissionService.READ_CHILDREN)); + // Is CAN_MOVE_OBJECT correct mapping? + registerEvaluator(BaseTypeId.CMIS_FOLDER, new RootFolderEvaluator(serviceRegistry, cmisConnector, + new PermissionActionEvaluator(serviceRegistry, Action.CAN_MOVE_OBJECT, PermissionService.DELETE_NODE), + false)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_APPLY_POLICY, false)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_APPLIED_POLICIES, true)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_REMOVE_POLICY, false)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_GET_CHILDREN, PermissionService.READ_CHILDREN)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_CREATE_DOCUMENT, PermissionService.CREATE_CHILDREN)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_CREATE_FOLDER, PermissionService.CREATE_CHILDREN)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, + Action.CAN_CREATE_RELATIONSHIP, PermissionService.CREATE_ASSOCIATIONS)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new RootFolderEvaluator(serviceRegistry, cmisConnector, + new PermissionActionEvaluator(serviceRegistry, Action.CAN_DELETE_TREE, PermissionService.DELETE_NODE), + false)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, Action.CAN_GET_ACL, + PermissionService.READ_PERMISSIONS)); + registerEvaluator(BaseTypeId.CMIS_FOLDER, new PermissionActionEvaluator(serviceRegistry, Action.CAN_APPLY_ACL, + PermissionService.CHANGE_PERMISSIONS)); + + registerEvaluator(BaseTypeId.CMIS_RELATIONSHIP, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_DELETE_OBJECT, true)); + registerEvaluator(BaseTypeId.CMIS_RELATIONSHIP, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_UPDATE_PROPERTIES, false)); + registerEvaluator(BaseTypeId.CMIS_RELATIONSHIP, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_PROPERTIES, true)); + registerEvaluator(BaseTypeId.CMIS_RELATIONSHIP, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_ACL, false)); + registerEvaluator(BaseTypeId.CMIS_RELATIONSHIP, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_APPLY_ACL, false)); + + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_DELETE_OBJECT, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_UPDATE_PROPERTIES, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_PROPERTIES, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_OBJECT_PARENTS, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_MOVE_OBJECT, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_ADD_OBJECT_TO_FOLDER, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_REMOVE_OBJECT_FROM_FOLDER, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_OBJECT_RELATIONSHIPS, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_GET_ACL, false)); + registerEvaluator(BaseTypeId.CMIS_POLICY, new FixedValueActionEvaluator(serviceRegistry, + Action.CAN_APPLY_ACL, false)); + + } + + /** + * @param serviceRegistry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public void setCmisConnector(CMISConnector cmisConnector) + { + this.cmisConnector = cmisConnector; + } + + /** + * @return namespaceService + */ + /* package */NamespaceService getNamespaceService() + { + return serviceRegistry.getNamespaceService(); + } + + /** + * Gets the CMIS Type Id given the Alfresco QName for the type in any + * Alfresco model + * + * @param typeQName + * @return + */ + public String getCmisTypeId(BaseTypeId scope, QName typeQName) + { + String typeId = mapAlfrescoQNameToTypeId.get(typeQName); + if (typeId == null) + { + String p = null; + switch (scope) + { + case CMIS_DOCUMENT: + p = "D"; + break; + case CMIS_FOLDER: + p = "F"; + break; + case CMIS_RELATIONSHIP: + p = "R"; + break; + case CMIS_POLICY: + p = "P"; + break; + default: + throw new CmisRuntimeException("Invalid base type!"); + } + + return p + ":" + typeQName.toPrefixString(serviceRegistry.getNamespaceService()); + } else + { + return typeId; + } + } + + public String getCmisTypeId(QName classQName) + { + if (classQName.equals(ContentModel.TYPE_CONTENT)) + { + return getCmisTypeId(BaseTypeId.CMIS_DOCUMENT, classQName); + } + if (classQName.equals(ContentModel.TYPE_FOLDER)) + { + return getCmisTypeId(BaseTypeId.CMIS_FOLDER, classQName); + } + if (classQName.equals(CMISMapping.RELATIONSHIP_QNAME)) + { + return getCmisTypeId(BaseTypeId.CMIS_RELATIONSHIP, classQName); + } + if (classQName.equals(CMISMapping.POLICY_QNAME)) + { + return getCmisTypeId(BaseTypeId.CMIS_POLICY, classQName); + } + if (classQName.equals(CMISMapping.ASPECTS_QNAME)) + { + return getCmisTypeId(BaseTypeId.CMIS_POLICY, classQName); + } + if (isValidCmisDocument(classQName)) + { + return getCmisTypeId(BaseTypeId.CMIS_DOCUMENT, classQName); + } + if (isValidCmisFolder(classQName)) + { + return getCmisTypeId(BaseTypeId.CMIS_FOLDER, classQName); + } + if (isValidCmisRelationship(classQName)) + { + return getCmisTypeId(BaseTypeId.CMIS_RELATIONSHIP, classQName); + } + if (isValidCmisPolicy(classQName)) + { + return getCmisTypeId(BaseTypeId.CMIS_POLICY, classQName); + } + + return null; + } + + public String buildPrefixEncodedString(QName qname) + { + return qname.toPrefixString(serviceRegistry.getNamespaceService()); + } + + public QName getAlfrescoName(String typeId) + { + // Is it an Alfresco type id? + if (typeId.length() < 4 || typeId.charAt(1) != ':') + { + throw new CmisInvalidArgumentException("Malformed type id '" + typeId + "'"); + } + + return QName.createQName(typeId.substring(2), serviceRegistry.getNamespaceService()); + } + + /** + * Is this a valid cmis document or folder type (not a relationship) + * + * @param dictionaryService + * @param typeQName + * @return + */ + public boolean isValidCmisDocumentOrFolder(QName typeQName) + { + return isValidCmisFolder(typeQName) || isValidCmisDocument(typeQName); + } + + public boolean isValidCmisObject(BaseTypeId scope, QName qname) + { + switch (scope) + { + case CMIS_DOCUMENT: + return isValidCmisDocument(qname); + case CMIS_FOLDER: + return isValidCmisFolder(qname); + case CMIS_POLICY: + return isValidCmisPolicy(qname); + case CMIS_RELATIONSHIP: + return isValidCmisRelationship(qname); + } + + return false; + } + + /** + * Is this a valid CMIS folder type? + * + * @param dictionaryService + * @param typeQName + * @return + */ + public boolean isValidCmisFolder(QName typeQName) + { + if (typeQName == null) + { + return false; + } + if (typeQName.equals(FOLDER_QNAME)) + { + return true; + } + + if (serviceRegistry.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_FOLDER)) + { + if (typeQName.equals(ContentModel.TYPE_FOLDER)) + { + return false; + } else + { + return true; + } + } + + return false; + } + + /** + * Is this a valid CMIS document type? + * + * @param dictionaryService + * @param typeQName + * @return + */ + public boolean isValidCmisDocument(QName typeQName) + { + if (typeQName == null) + { + return false; + } + if (typeQName.equals(DOCUMENT_QNAME)) + { + return true; + } + + if (serviceRegistry.getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_CONTENT)) + { + if (typeQName.equals(ContentModel.TYPE_CONTENT)) + { + return false; + } else + { + return true; + } + } + return false; + } + + /** + * Is this a valid CMIS policy type? + * + * @param dictionaryService + * @param typeQName + * @return + */ + public boolean isValidCmisPolicy(QName typeQName) + { + if (typeQName == null) + { + return false; + } + if (typeQName.equals(POLICY_QNAME)) + { + return true; + } + if (typeQName.equals(ASPECTS_QNAME)) + { + return true; + } + + AspectDefinition aspectDef = serviceRegistry.getDictionaryService().getAspect(typeQName); + if (aspectDef == null) + { + return false; + } + + if (aspectDef.getName().equals(ContentModel.ASPECT_VERSIONABLE) + || aspectDef.getName().equals(ContentModel.ASPECT_AUDITABLE) + || aspectDef.getName().equals(ContentModel.ASPECT_REFERENCEABLE)) + { + return false; + } + return true; + } + + /** + * Is an association valid in CMIS? It must be a non-child relationship and + * the source and target must both be valid CMIS types. + * + * @param dictionaryService + * @param associationQName + * @return + */ + public boolean isValidCmisRelationship(QName associationQName) + { + if (associationQName == null) + { + return false; + } + if (associationQName.equals(RELATIONSHIP_QNAME)) + { + return true; + } + AssociationDefinition associationDefinition = serviceRegistry.getDictionaryService().getAssociation( + associationQName); + if (associationDefinition == null) + { + return false; + } + if (associationDefinition.isChild()) + { + return false; + } + if (!isValidCmisDocumentOrFolder(getCmisType(associationDefinition.getSourceClass().getName()))) + { + return false; + } + if (!isValidCmisDocumentOrFolder(getCmisType(associationDefinition.getTargetClass().getName()))) + { + return false; + } + return true; + } + + /** + * Given an Alfresco model type map it to the appropriate type. Maps + * cm:folder and cm:content to the CMIS definitions + */ + public QName getCmisType(QName typeQName) + { + QName mapped = mapAlfrescoQNameToCmisQName.get(typeQName); + if (mapped != null) + { + return mapped; + } + return typeQName; + } + + /** + * Is Alfresco Type mapped to an alternative CMIS Type? + */ + public boolean isRemappedType(QName typeQName) + { + return mapAlfrescoQNameToCmisQName.containsKey(typeQName); + } + + /** + * Given a CMIS model type map it to the appropriate Alfresco type. + * + * @param cmisTypeQName + * @return + */ + public QName getAlfrescoClass(QName cmisTypeQName) + { + QName mapped = mapCmisQNameToAlfrescoQName.get(cmisTypeQName); + if (mapped != null) + { + return mapped; + } + return cmisTypeQName; + } + + /** + * Get the CMIS property type for a property + * + * @param dictionaryService + * @param propertyQName + * @return + */ + public PropertyType getDataType(DataTypeDefinition datatype) + { + return getDataType(datatype.getName()); + } + + public PropertyType getDataType(QName dataType) + { + return mapAlfrescoToCmisDataType.get(dataType); + } + + public QName getAlfrescoDataType(PropertyType propertyType) + { + return mapCmisDataTypeToAlfresco.get(propertyType); + } + + /** + * @param namespaceService + * @param propertyQName + * @return + */ + public String getCmisPropertyId(QName propertyQName) + { + return propertyQName.toPrefixString(serviceRegistry.getNamespaceService()); + } + + /** + * Get a Property Accessor + */ + public AbstractProperty getPropertyAccessor(String propertyId) + { + return propertyAccessors.get(propertyId); + } + + /** + * Register pre-defined Property Accessor + * + * @param propertyAccessor + */ + private void registerPropertyAccessor(AbstractProperty propertyAccessor) + { + propertyAccessors.put(propertyAccessor.getName(), propertyAccessor); + } + + /** + * Gets the Action Evaluators applicable for the given CMIS Scope + */ + public Map> getActionEvaluators(BaseTypeId scope) + { + Map> evaluators = actionEvaluators.get(scope); + if (evaluators == null) + { + evaluators = Collections.emptyMap(); + } + return evaluators; + } + + /** + * Register an Action Evaluator + * + * @param scope + * @param evaluator + */ + private void registerEvaluator(BaseTypeId scope, CMISActionEvaluator evaluator) + { + Map> evaluators = actionEvaluators.get(scope); + if (evaluators == null) + { + evaluators = new LinkedHashMap>(); + actionEvaluators.put(scope, evaluators); + } + if (evaluators.get(evaluator.getAction()) != null) + { + throw new AlfrescoRuntimeException("Already registered Action Evaluator " + evaluator.getAction() + + " for scope " + scope); + } + evaluators.put(evaluator.getAction(), evaluator); + + if (logger.isDebugEnabled()) + logger.debug("Registered Action Evaluator: scope=" + scope + ", evaluator=" + evaluator); + } + + public Collection> getReportedPermissions(String permission, Set permissions, + boolean hasFull, boolean isDirect, CMISAccessControlFormatEnum format) + { + ArrayList> answer = new ArrayList>(20); + // indirect + + if (hasFull) + { + answer.add(new Pair(CMIS_READ, false)); + answer.add(new Pair(CMIS_WRITE, false)); + answer.add(new Pair(CMIS_ALL, false)); + } + + for (String perm : permissions) + { + if (PermissionService.READ.equals(perm)) + { + answer.add(new Pair(CMIS_READ, false)); + } else if (PermissionService.WRITE.equals(perm)) + { + answer.add(new Pair(CMIS_WRITE, false)); + } else if (PermissionService.ALL_PERMISSIONS.equals(perm)) + { + answer.add(new Pair(CMIS_READ, false)); + answer.add(new Pair(CMIS_WRITE, false)); + answer.add(new Pair(CMIS_ALL, false)); + } + + if (hasFull) + { + answer.add(new Pair(CMIS_READ, false)); + answer.add(new Pair(CMIS_WRITE, false)); + answer.add(new Pair(CMIS_ALL, false)); + } + } + + // permission + + if (format == CMISAccessControlFormatEnum.REPOSITORY_SPECIFIC_PERMISSIONS) + { + if (PermissionService.READ.equals(permission)) + { + answer.add(new Pair(CMIS_READ, false)); + answer.add(new Pair(permission, isDirect)); + } else if (PermissionService.WRITE.equals(permission)) + { + answer.add(new Pair(CMIS_WRITE, false)); + answer.add(new Pair(permission, isDirect)); + } else if (PermissionService.ALL_PERMISSIONS.equals(permission)) + { + answer.add(new Pair(CMIS_ALL, false)); + answer.add(new Pair(permission, isDirect)); + } else + { + answer.add(new Pair(permission, isDirect)); + } + } else if (format == CMISAccessControlFormatEnum.CMIS_BASIC_PERMISSIONS) + { + if (PermissionService.READ.equals(permission)) + { + answer.add(new Pair(CMIS_READ, isDirect)); + } else if (PermissionService.WRITE.equals(permission)) + { + answer.add(new Pair(CMIS_WRITE, isDirect)); + } else if (PermissionService.ALL_PERMISSIONS.equals(permission)) + { + answer.add(new Pair(CMIS_ALL, isDirect)); + } else + { + // else nothing + } + } + + return answer; + } + + /** + * @param permission + * @return permission to set + */ + public String getSetPermission(String permission) + { + if (permission.equals(CMIS_READ)) + { + return PermissionService.READ; + } else if (permission.equals(CMIS_WRITE)) + { + return PermissionService.WRITE; + } else if (permission.equals(CMIS_ALL)) + { + return PermissionService.ALL_PERMISSIONS; + } else + { + return permission; + } + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/CanCheckOutActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/CanCheckOutActionEvaluator.java new file mode 100644 index 0000000000..935b27e5af --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/CanCheckOutActionEvaluator.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Alfresco Permission based Action Evaluator + * + * @author davidc + */ +public class CanCheckOutActionEvaluator extends AbstractActionEvaluator +{ + private PermissionActionEvaluator permissionEvaluator; + private NodeService nodeService; + private LockService lockService; + + /** + * Construct + * + * @param serviceRegistry + * @param permission + */ + protected CanCheckOutActionEvaluator(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, Action.CAN_CHECK_OUT); + permissionEvaluator = new PermissionActionEvaluator(serviceRegistry, Action.CAN_CHECK_OUT, + PermissionService.CHECK_OUT); + nodeService = serviceRegistry.getNodeService(); + lockService = serviceRegistry.getLockService(); + } + + public boolean isAllowed(NodeRef nodeRef) + { + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) + || lockService.getLockType(nodeRef) == LockType.READ_ONLY_LOCK) + { + return false; + } + return permissionEvaluator.isAllowed(nodeRef); + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/CheckinCommentProperty.java b/source/java/org/alfresco/opencmis/mapping/CheckinCommentProperty.java new file mode 100644 index 0000000000..95b08a41a7 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/CheckinCommentProperty.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.cmis.CMISDictionaryModel; +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; + +/** + * Accessor for the CMIS Checkin Comment + * + * @author dward + */ +public class CheckinCommentProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public CheckinCommentProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, CMISDictionaryModel.PROP_CHECKIN_COMMENT); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service.cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + if (isWorkingCopy(nodeRef)) + { + return null; + } + ServiceRegistry serviceRegistry = getServiceRegistry(); + String versionLabel = (String)serviceRegistry.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + if (versionLabel == null) + { + return null; + } + NodeRef versionSeries = getVersionSeries(nodeRef); + VersionHistory versionHistory = serviceRegistry.getVersionService().getVersionHistory(versionSeries); + if (versionHistory == null) + { + return null; + } + Version version = versionHistory.getVersion(versionLabel); + if (version == null) + { + return null; + } + return version.getDescription(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ContentStreamIdProperty.java b/source/java/org/alfresco/opencmis/mapping/ContentStreamIdProperty.java new file mode 100644 index 0000000000..d98411907c --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/ContentStreamIdProperty.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS content stream property id + * + * @author andyh + */ +public class ContentStreamIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public ContentStreamIdProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.CONTENT_STREAM_ID); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + Serializable sValue = getServiceRegistry().getNodeService().getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (sValue != null) + { + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, sValue); + return contentData.getContentUrl(); + } + return null; + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/ContentStreamLengthProperty.java b/source/java/org/alfresco/opencmis/mapping/ContentStreamLengthProperty.java new file mode 100644 index 0000000000..65f0c69b76 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/ContentStreamLengthProperty.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS content stream length property + * + * @author andyh + */ +public class ContentStreamLengthProperty extends AbstractSimpleProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public ContentStreamLengthProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.CONTENT_STREAM_LENGTH); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + Serializable value = getServiceRegistry().getNodeService().getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (value != null) + { + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); + return contentData.getSize(); + } else + { + return 0L; + } + } + + public String getLuceneFieldName() + { + StringBuilder field = new StringBuilder(128); + field.append("@"); + field.append(ContentModel.PROP_CONTENT); + field.append(".size"); + return field.toString(); + } + + protected String getValueAsString(Serializable value) + { + Object converted = DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService() + .getDataType(DataTypeDefinition.LONG), value); + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); + return asString; + } + + protected QName getQNameForExists() + { + return ContentModel.PROP_CONTENT; + } + + protected DataTypeDefinition getInDataType() + { + return getServiceRegistry().getDictionaryService().getDataType(DataTypeDefinition.LONG); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ContentStreamMimetypeProperty.java b/source/java/org/alfresco/opencmis/mapping/ContentStreamMimetypeProperty.java new file mode 100644 index 0000000000..d7106c7e42 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/ContentStreamMimetypeProperty.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS content stream mimetype property + * + * @author andyh + */ +public class ContentStreamMimetypeProperty extends AbstractSimpleProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public ContentStreamMimetypeProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.CONTENT_STREAM_MIME_TYPE); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + Serializable value = getServiceRegistry().getNodeService().getProperty(nodeRef, ContentModel.PROP_CONTENT); + if (value != null) + { + ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); + return contentData.getMimetype(); + } else + { + return ""; + } + } + + public String getLuceneFieldName() + { + StringBuilder field = new StringBuilder(128); + field.append("@"); + field.append(ContentModel.PROP_CONTENT); + field.append(".mimetype"); + return field.toString(); + } + + protected String getValueAsString(Serializable value) + { + Object converted = DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService() + .getDataType(DataTypeDefinition.TEXT), value); + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); + return asString; + } + + protected QName getQNameForExists() + { + return ContentModel.PROP_CONTENT; + } + + protected DataTypeDefinition getInDataType() + { + return getServiceRegistry().getDictionaryService().getDataType(DataTypeDefinition.TEXT); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/CurrentVersionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/CurrentVersionEvaluator.java new file mode 100644 index 0000000000..7c4770e38b --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/CurrentVersionEvaluator.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISActionEvaluator; +import org.alfresco.repo.version.VersionBaseModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.apache.chemistry.opencmis.commons.enums.Action; + +public class CurrentVersionEvaluator extends AbstractActionEvaluator +{ + private CMISActionEvaluator currentVersionEvaluator; + private boolean currentVersionValue; + private boolean nonCurrentVersionValue; + + /** + * Construct + * + * @param serviceRegistry + */ + protected CurrentVersionEvaluator(ServiceRegistry serviceRegistry, Action action, boolean currentVersionValue, + boolean nonCurrentVersionValue) + { + super(serviceRegistry, action); + this.currentVersionValue = currentVersionValue; + this.nonCurrentVersionValue = nonCurrentVersionValue; + } + + /** + * Construct + * + * @param serviceRegistry + */ + protected CurrentVersionEvaluator(ServiceRegistry serviceRegistry, + CMISActionEvaluator currentVersionEvaluator, boolean nonCurrentVersionValue) + { + super(serviceRegistry, currentVersionEvaluator.getAction()); + this.currentVersionEvaluator = currentVersionEvaluator; + this.nonCurrentVersionValue = nonCurrentVersionValue; + } + + public boolean isAllowed(NodeRef nodeRef) + { + if (nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + VersionHistory versionHistory = getServiceRegistry().getVersionService().getVersionHistory(nodeRef); + if (versionHistory != null) + { + Version currentVersion = versionHistory.getHeadVersion(); + Serializable versionLabel = getServiceRegistry().getNodeService().getProperty(nodeRef, + ContentModel.PROP_VERSION_LABEL); + + if (!currentVersion.getVersionLabel().equals(versionLabel)) + { + return nonCurrentVersionValue; + } + } + } + + return currentVersionEvaluator == null ? currentVersionValue : currentVersionEvaluator.isAllowed(nodeRef); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/DirectProperty.java b/source/java/org/alfresco/opencmis/mapping/DirectProperty.java new file mode 100644 index 0000000000..ddac3b618f --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/DirectProperty.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; + +import org.alfresco.repo.search.MLAnalysisMode; +import org.alfresco.repo.search.SearcherException; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.lucene.analysis.DateTimeAnalyser; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.lucene.index.IndexReader.FieldOption; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * A simple 1-1 property mapping from a CMIS property name to an alfresco property + * + * @author andyh + */ +public class DirectProperty extends AbstractSimpleProperty +{ + /* + * (non-Javadoc) + * @see org.alfresco.cmis.mapping.AbstractSimpleProperty#getLuceneSortField() + */ + @Override + public String getLuceneSortField(LuceneQueryParser lqp) + { + + String field = getLuceneFieldName(); + // need to find the real field to use + Locale sortLocale = null; + + PropertyDefinition propertyDef = getServiceRegistry().getDictionaryService().getProperty(QName.createQName(field.substring(1))); + + if (propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) + { + throw new SearcherException("Order on content properties is not curently supported"); + } + else if ((propertyDef.getDataType().getName().equals(DataTypeDefinition.MLTEXT)) || (propertyDef.getDataType().getName().equals(DataTypeDefinition.TEXT))) + { + List locales = lqp.getSearchParameters().getLocales(); + if (((locales == null) || (locales.size() == 0))) + { + locales = Collections.singletonList(I18NUtil.getLocale()); + } + + if (locales.size() > 1) + { + throw new SearcherException("Order on text/mltext properties with more than one locale is not curently supported"); + } + + sortLocale = locales.get(0); + // find best field match + + HashSet allowableLocales = new HashSet(); + MLAnalysisMode analysisMode = lqp.getDefaultSearchMLAnalysisMode(); + for (Locale l : MLAnalysisMode.getLocales(analysisMode, sortLocale, false)) + { + allowableLocales.add(l.toString()); + } + + String sortField = field; + + for (Object current : lqp.getIndexReader().getFieldNames(FieldOption.INDEXED)) + { + String currentString = (String) current; + if (currentString.startsWith(field) && currentString.endsWith(".sort")) + { + String fieldLocale = currentString.substring(field.length() + 1, currentString.length() - 5); + if (allowableLocales.contains(fieldLocale)) + { + if (fieldLocale.equals(sortLocale.toString())) + { + sortField = currentString; + break; + } + else if (sortLocale.toString().startsWith(fieldLocale)) + { + if (sortField.equals(field) || (currentString.length() < sortField.length())) + { + sortField = currentString; + } + } + else if (fieldLocale.startsWith(sortLocale.toString())) + { + if (sortField.equals(field) || (currentString.length() < sortField.length())) + { + sortField = currentString; + } + } + } + } + } + + field = sortField; + + } + else if (propertyDef.getDataType().getName().equals(DataTypeDefinition.DATETIME)) + { + DataTypeDefinition dataType = propertyDef.getDataType(); + String analyserClassName = dataType.getAnalyserClassName(); + if (analyserClassName.equals(DateTimeAnalyser.class.getCanonicalName())) + { + field = field + ".sort"; + } + } + + return field; + } + + private QName alfrescoName; + + /** + * Construct + * + * @param serviceRegistry + * @param propertyName + * @param alfrescoName + */ + public DirectProperty(ServiceRegistry serviceRegistry, String propertyName, QName alfrescoName) + { + super(serviceRegistry, propertyName); + this.alfrescoName = alfrescoName; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.AbstractPropertyAccessor#getMappedProperty() + */ + public QName getMappedProperty() + { + return alfrescoName; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service.cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + return getServiceRegistry().getNodeService().getProperty(nodeRef, alfrescoName); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service.cmr.repository.AssociationRef) + */ + public Serializable getValue(AssociationRef assocRef) + { + return null; + } + + public String getLuceneFieldName() + { + StringBuilder field = new StringBuilder(64); + field.append("@"); + field.append(alfrescoName); + return field.toString(); + } + + protected String getValueAsString(Serializable value) + { + PropertyDefinition pd = getServiceRegistry().getDictionaryService().getProperty(alfrescoName); + Object converted = DefaultTypeConverter.INSTANCE.convert(pd.getDataType(), value); + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); + return asString; + } + + protected QName getQNameForExists() + { + return alfrescoName; + } + + protected DataTypeDefinition getInDataType() + { + PropertyDefinition pd = getServiceRegistry().getDictionaryService().getProperty(alfrescoName); + return pd.getDataType(); + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/FixedValueActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/FixedValueActionEvaluator.java new file mode 100644 index 0000000000..180e92c56a --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/FixedValueActionEvaluator.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import org.alfresco.service.ServiceRegistry; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Action Evaluator whose evaluation is fixed + * + * @author davidc + * + */ +public class FixedValueActionEvaluator extends AbstractActionEvaluator +{ + private boolean allowed; + + /** + * Construct + * + * @param serviceRegistry + * @param action + */ + protected FixedValueActionEvaluator(ServiceRegistry serviceRegistry, Action action, boolean allowed) + { + super(serviceRegistry, action); + this.allowed = allowed; + } + + public boolean isAllowed(T object) + { + return allowed; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("FixedValueActionEvaluator[action=").append(getAction()); + builder.append(", allowed=").append(allowed).append("]"); + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/FixedValueProperty.java b/source/java/org/alfresco/opencmis/mapping/FixedValueProperty.java new file mode 100644 index 0000000000..b6fa336fb4 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/FixedValueProperty.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.repo.search.impl.lucene.LuceneFunction; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.util.EqualsHelper; +import org.alfresco.util.SearchLanguageConversion; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; + +/** + * Property accessor for fixed value mapping (eg to null, true, etc) + * + * @author andyh + */ +public class FixedValueProperty extends AbstractProperty +{ + private Serializable value; + + /** + * Construct + * + * @param serviceRegistry + * @param propertyName + * @param value + */ + public FixedValueProperty(ServiceRegistry serviceRegistry, String propertyName, Serializable value) + { + super(serviceRegistry, propertyName); + this.value = value; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service.cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + return value; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service.cmr.repository.AssociationRef) + */ + public Serializable getValue(AssociationRef assocRef) + { + return value; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneEquality(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneEquality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + if (EqualsHelper.nullSafeEquals(value, value)) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneExists(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.lang.Boolean) + */ + public Query buildLuceneExists(LuceneQueryParser lqp, Boolean not) throws ParseException + { + if (not) + { + if (value == null) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + else + { + if (value == null) + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + else + { + return new MatchAllDocsQuery(); + } + } + + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneGreaterThan(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + @SuppressWarnings("unchecked") + public Query buildLuceneGreaterThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + if (value instanceof Comparable) + { + Comparable comparable = (Comparable) value; + if (comparable.compareTo(value) > 0) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneGreaterThanOrEquals(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + @SuppressWarnings("unchecked") + public Query buildLuceneGreaterThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + if (value instanceof Comparable) + { + Comparable comparable = (Comparable) value; + if (comparable.compareTo(value) >= 0) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneIn(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.util.Collection, java.lang.Boolean, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneIn(LuceneQueryParser lqp, Collection values, Boolean not, PredicateMode mode) throws ParseException + { + boolean in = false; + for (Serializable value : values) + { + if (EqualsHelper.nullSafeEquals(value, value)) + { + in = true; + break; + } + } + + if (in == !not) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneInequality(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneInequality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + if (!EqualsHelper.nullSafeEquals(value, value)) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLessThan(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + @SuppressWarnings("unchecked") + public Query buildLuceneLessThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + if (value instanceof Comparable) + { + Comparable comparable = (Comparable) value; + if (comparable.compareTo(value) < 0) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLessThanOrEquals(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + @SuppressWarnings("unchecked") + public Query buildLuceneLessThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + if (value instanceof Comparable) + { + Comparable comparable = (Comparable) value; + if (comparable.compareTo(value) <= 0) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLike(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, java.lang.Boolean) + */ + public Query buildLuceneLike(LuceneQueryParser lqp, Serializable value, Boolean not) throws ParseException + { + if (value != null) + { + boolean matches = false; + + Object converted = DefaultTypeConverter.INSTANCE.convert(value.getClass(), value); + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); + String regExpression = SearchLanguageConversion.convertSQLLikeToRegex(asString); + Pattern pattern = Pattern.compile(regExpression); + String target = DefaultTypeConverter.INSTANCE.convert(String.class, value); + Matcher matcher = pattern.matcher(target); + if (matcher.matches()) + { + matches = true; + } + + if (matches == !not) + { + return new MatchAllDocsQuery(); + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.property.PropertyLuceneBuilder#getLuceneSortField() + */ + public String getLuceneSortField(LuceneQueryParser lqp) + { + throw new UnsupportedOperationException(); + } + + public String getLuceneFieldName() + { + throw new UnsupportedOperationException(); + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsImmutableProperty.java b/source/java/org/alfresco/opencmis/mapping/IsImmutableProperty.java new file mode 100644 index 0000000000..20402bd908 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/IsImmutableProperty.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Property accessor for CMIS is immutable property + * + * @author dward + */ +public class IsImmutableProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public IsImmutableProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.IS_IMMUTABLE); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + if (!isCurrentVersion(nodeRef)) + { + return true; + } + if (isWorkingCopy(nodeRef)) + { + return false; + } + if (getVersionSeries(nodeRef).equals(nodeRef)) + { + return hasWorkingCopy(nodeRef); + } + return true; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsLatestMajorVersionProperty.java b/source/java/org/alfresco/opencmis/mapping/IsLatestMajorVersionProperty.java new file mode 100644 index 0000000000..0f3bd47534 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/IsLatestMajorVersionProperty.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.version.VersionBaseModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.version.VersionType; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS is latest major version property + * + * @author dward + */ +public class IsLatestMajorVersionProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public IsLatestMajorVersionProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.IS_LATEST_MAJOR_VERSION); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + if (isWorkingCopy(nodeRef)) + { + return false; + } + NodeRef versionSeries = getVersionSeries(nodeRef); + ServiceRegistry serviceRegistry = getServiceRegistry(); + VersionService versionService = serviceRegistry.getVersionService(); + VersionHistory versionHistory = versionService.getVersionHistory(versionSeries); + if (versionHistory == null) + { + return false; + } + + NodeRef versionNodeRef = nodeRef; + if (!nodeRef.getStoreRef().getProtocol().equals(VersionBaseModel.STORE_PROTOCOL)) + { + String versionLabel = (String) serviceRegistry.getNodeService().getProperty(nodeRef, + ContentModel.PROP_VERSION_LABEL); + if (versionLabel == null) + { + return false; + } + Version version = versionHistory.getVersion(versionLabel); + if (version == null) + { + return false; + } + versionNodeRef = version.getFrozenStateNodeRef(); + } + + // Go back in time to the last major version + Version currentVersion = versionService.getCurrentVersion(versionSeries); + while (currentVersion != null) + { + if (currentVersion.getVersionType() == VersionType.MAJOR) + { + return currentVersion.getFrozenStateNodeRef().equals(versionNodeRef); + } + // We got to the current node and its not major. We failed! + else if (currentVersion.getFrozenStateNodeRef().equals(versionNodeRef)) + { + return false; + } + currentVersion = versionHistory.getPredecessor(currentVersion); + } + return false; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsLatestVersionProperty.java b/source/java/org/alfresco/opencmis/mapping/IsLatestVersionProperty.java new file mode 100644 index 0000000000..b655a889fe --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/IsLatestVersionProperty.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.Version; +import org.alfresco.service.cmr.version.VersionHistory; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accesser for CMIS is latest version property + * + * @author dward + */ +public class IsLatestVersionProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public IsLatestVersionProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.IS_LATEST_VERSION); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + if (isWorkingCopy(nodeRef) || getVersionSeries(nodeRef).equals(nodeRef) && !hasWorkingCopy(nodeRef)) + { + return true; + } + NodeRef versionSeries = getVersionSeries(nodeRef); + if (hasWorkingCopy(versionSeries)) + { + return false; + } + + ServiceRegistry serviceRegistry = getServiceRegistry(); + String versionLabel = (String) serviceRegistry.getNodeService().getProperty(nodeRef, + ContentModel.PROP_VERSION_LABEL); + if (versionLabel == null) + { + return false; + } + VersionHistory versionHistory = serviceRegistry.getVersionService().getVersionHistory(versionSeries); + if (versionHistory == null) + { + return false; + } + Version version = versionHistory.getVersion(versionLabel); + if (version == null) + { + return false; + } + return versionHistory.getHeadVersion().getFrozenStateNodeRef().equals(version.getFrozenStateNodeRef()); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsMajorVersionProperty.java b/source/java/org/alfresco/opencmis/mapping/IsMajorVersionProperty.java new file mode 100644 index 0000000000..df9fbddae9 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/IsMajorVersionProperty.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.version.VersionHistory; +import org.alfresco.service.cmr.version.VersionType; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for CMIS is major version property + * + * @author dward + */ +public class IsMajorVersionProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public IsMajorVersionProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.IS_MAJOR_VERSION); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + if (isWorkingCopy(nodeRef)) + { + return false; + } + ServiceRegistry serviceRegistry = getServiceRegistry(); + String versionLabel = (String) serviceRegistry.getNodeService().getProperty(nodeRef, + ContentModel.PROP_VERSION_LABEL); + if (versionLabel == null) + { + return false; + } + NodeRef versionSeries = getVersionSeries(nodeRef); + VersionHistory versionHistory = serviceRegistry.getVersionService().getVersionHistory(versionSeries); + if (versionHistory == null) + { + return false; + } + return versionHistory.getVersion(versionLabel).getVersionType() == VersionType.MAJOR; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/IsVersionSeriesCheckedOutProperty.java b/source/java/org/alfresco/opencmis/mapping/IsVersionSeriesCheckedOutProperty.java new file mode 100644 index 0000000000..93b1d011cc --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/IsVersionSeriesCheckedOutProperty.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS version series checked out property + * + * @author dward + */ +public class IsVersionSeriesCheckedOutProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public IsVersionSeriesCheckedOutProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.IS_VERSION_SERIES_CHECKED_OUT); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + return isWorkingCopy(nodeRef) || hasWorkingCopy(getVersionSeries(nodeRef)); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/NodeRefProperty.java b/source/java/org/alfresco/opencmis/mapping/NodeRefProperty.java new file mode 100644 index 0000000000..6e3f3018a3 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/NodeRefProperty.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Get the CMIS object id property. + */ +public class NodeRefProperty extends AbstractVersioningProperty +{ + public static final String NodeRefPropertyId = "alf:nodeRef"; + + /** + * Construct + * + * @param serviceRegistry + */ + public NodeRefProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, NodeRefPropertyId); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + return getLiveNodeRef(nodeRef); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ObjectIdProperty.java b/source/java/org/alfresco/opencmis/mapping/ObjectIdProperty.java new file mode 100644 index 0000000000..892b9041f9 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/ObjectIdProperty.java @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Collection; + +import org.alfresco.cmis.CMISQueryException; +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.repo.search.impl.lucene.AnalysisMode; +import org.alfresco.repo.search.impl.lucene.LuceneFunction; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; + +/** + * Get the CMIS object id property. + * + * @author andyh + * @author dward + */ +public class ObjectIdProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public ObjectIdProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.OBJECT_ID); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + if (isWorkingCopy(nodeRef)) + { + return nodeRef.toString(); + } + + QName typeQName = getServiceRegistry().getNodeService().getType(nodeRef); + if (typeQName.equals(CMISMapping.DOCUMENT_QNAME) + || getServiceRegistry().getDictionaryService().isSubClass(typeQName, ContentModel.TYPE_CONTENT)) + { + Serializable versionLabel = getServiceRegistry().getNodeService().getProperty(nodeRef, + ContentModel.PROP_VERSION_LABEL); + if (versionLabel == null) + { + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + } + + NodeRef versionSeries = getVersionSeries(nodeRef); + return new StringBuilder(1024).append(versionSeries.toString()).append(CMISConnector.ID_SEPERATOR) + .append(versionLabel).toString(); + } + + return nodeRef.toString(); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service + * .cmr.repository.AssociationRef) + */ + public Serializable getValue(AssociationRef assocRef) + { + return CMISConnector.ASSOC_ID_PREFIX + assocRef.getId(); + } + + public String getLuceneFieldName() + { + return "ID"; + } + + private String getValueAsString(Serializable value) + { + Object converted = DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService() + .getDataType(DataTypeDefinition.NODE_REF), value); + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); + return asString; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneEquality( + * org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneEquality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getFieldQuery(field, stringValue, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneExists(org + * .alfresco.repo.search.impl.lucene.LuceneQueryParser, java.lang.Boolean) + */ + public Query buildLuceneExists(LuceneQueryParser lqp, Boolean not) throws ParseException + { + if (not) + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } else + { + return new MatchAllDocsQuery(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneGreaterThan + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneGreaterThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + throw new CMISQueryException("Property " + getName() + " can not be used in a 'greater than' comparison"); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.property.PropertyLuceneBuilder# + * buildLuceneGreaterThanOrEquals + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneGreaterThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + throw new CMISQueryException("Property " + getName() + + " can not be used in a 'greater than or equals' comparison"); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneIn(org.alfresco + * .repo.search.impl.lucene.LuceneQueryParser, java.util.Collection, + * java.lang.Boolean, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneIn(LuceneQueryParser lqp, Collection values, Boolean not, PredicateMode mode) + throws ParseException + { + String field = getLuceneFieldName(); + + // Check type conversion + + @SuppressWarnings("unused") + Object converted = DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService() + .getDataType(DataTypeDefinition.NODE_REF), values); + Collection asStrings = DefaultTypeConverter.INSTANCE.convert(String.class, values); + + if (asStrings.size() == 0) + { + if (not) + { + return new MatchAllDocsQuery(); + } else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } else if (asStrings.size() == 1) + { + String value = asStrings.iterator().next(); + if (not) + { + return lqp.getDoesNotMatchFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } else + { + return lqp.getFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } + } else + { + BooleanQuery booleanQuery = new BooleanQuery(); + if (not) + { + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + } + for (String value : asStrings) + { + Query any = lqp.getFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + if (not) + { + booleanQuery.add(any, Occur.MUST_NOT); + } else + { + booleanQuery.add(any, Occur.SHOULD); + } + } + return booleanQuery; + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneInequality + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneInequality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getDoesNotMatchFieldQuery(field, stringValue, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLessThan( + * org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneLessThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + throw new CMISQueryException("Property " + getName() + " can not be used in a 'less than' comparison"); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLessThanOrEquals + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneLessThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + throw new CMISQueryException("Property " + getName() + " can not be used in a 'less than or equals' comparison"); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.NamedPropertyAccessor#buildLuceneLike(org. + * alfresco.repo.search.impl.lucene.LuceneQueryParser, java.lang.String, + * java.io.Serializable, java.lang.Boolean) + */ + public Query buildLuceneLike(LuceneQueryParser lqp, Serializable value, Boolean not) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + + if (not) + { + BooleanQuery booleanQuery = new BooleanQuery(); + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + booleanQuery.add(lqp.getLikeQuery(field, stringValue, AnalysisMode.IDENTIFIER), Occur.MUST_NOT); + return booleanQuery; + } else + { + return lqp.getLikeQuery(field, stringValue, AnalysisMode.IDENTIFIER); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.NamedPropertyAccessor#getLuceneSortField(java + * .lang.String) + */ + public String getLuceneSortField(LuceneQueryParser lqp) + { + return getLuceneFieldName(); + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/ObjectTypeIdProperty.java b/source/java/org/alfresco/opencmis/mapping/ObjectTypeIdProperty.java new file mode 100644 index 0000000000..d8704f9e5e --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/ObjectTypeIdProperty.java @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; + +import org.alfresco.cmis.CMISQueryException; +import org.alfresco.cmis.CMISScope; +import org.alfresco.cmis.CMISTypeDefinition; +import org.alfresco.repo.search.impl.lucene.AnalysisMode; +import org.alfresco.repo.search.impl.lucene.LuceneFunction; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; + +/** + * Get the CMIS object type id property + * + * @author andyh + */ +public class ObjectTypeIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public ObjectTypeIdProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.OBJECT_TYPE_ID); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + QName type = getServiceRegistry().getNodeService().getType(nodeRef); + return getServiceRegistry().getCMISDictionaryService().findTypeForClass(type).getTypeId().getId(); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service + * .cmr.repository.AssociationRef) + */ + public Serializable getValue(AssociationRef assocRef) + { + QName type = assocRef.getTypeQName(); + return getServiceRegistry().getCMISDictionaryService().findTypeForClass(type, CMISScope.RELATIONSHIP) + .getTypeId().getId(); + } + + public String getLuceneFieldName() + { + return "TYPE"; + } + + private String getValueAsString(Serializable value) + { + // Object converted = + // DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService().getDataType(DataTypeDefinition.QNAME), + // value); + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, value); + return asString; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneEquality( + * org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneEquality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + CMISTypeDefinition type = getServiceRegistry().getCMISDictionaryService().findType(stringValue); + return lqp + .getFieldQuery(field, type.getTypeId().getQName().toString(), AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneExists(org + * .alfresco.repo.search.impl.lucene.LuceneQueryParser, java.lang.Boolean) + */ + public Query buildLuceneExists(LuceneQueryParser lqp, Boolean not) throws ParseException + { + if (not) + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } else + { + return new MatchAllDocsQuery(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneGreaterThan + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneGreaterThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + throw new CMISQueryException("Property " + getName() + " can not be used in a 'greater than' comparison"); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.property.PropertyLuceneBuilder# + * buildLuceneGreaterThanOrEquals + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneGreaterThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + throw new CMISQueryException("Property " + getName() + + " can not be used in a 'greater than or equals' comparison"); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneIn(org.alfresco + * .repo.search.impl.lucene.LuceneQueryParser, java.util.Collection, + * java.lang.Boolean, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneIn(LuceneQueryParser lqp, Collection values, Boolean not, PredicateMode mode) + throws ParseException + { + String field = getLuceneFieldName(); + + Collection asStrings = new ArrayList(values.size()); + for (Serializable value : values) + { + String stringValue = getValueAsString(value); + CMISTypeDefinition type = getServiceRegistry().getCMISDictionaryService().findType(stringValue); + asStrings.add(type.getTypeId().getQName().toString()); + } + + if (asStrings.size() == 0) + { + if (not) + { + return new MatchAllDocsQuery(); + } else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } else if (asStrings.size() == 1) + { + String value = asStrings.iterator().next(); + if (not) + { + return lqp.getDoesNotMatchFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } else + { + return lqp.getFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } + } else + { + BooleanQuery booleanQuery = new BooleanQuery(); + if (not) + { + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + } + for (String value : asStrings) + { + Query any = lqp.getFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + if (not) + { + booleanQuery.add(any, Occur.MUST_NOT); + } else + { + booleanQuery.add(any, Occur.SHOULD); + } + } + return booleanQuery; + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneInequality + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneInequality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + CMISTypeDefinition type = getServiceRegistry().getCMISDictionaryService().findType(stringValue); + return lqp.getDoesNotMatchFieldQuery(field, type.getTypeId().getQName().toString(), AnalysisMode.IDENTIFIER, + luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLessThan( + * org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneLessThan(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + throw new CMISQueryException("Property " + getName() + " can not be used in a 'less than' comparison"); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLessThanOrEquals + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneLessThanOrEquals(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + throw new CMISQueryException("Property " + getName() + " can not be used in a 'less than or equals' comparison"); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLike(org. + * alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, + * java.lang.Boolean) + */ + public Query buildLuceneLike(LuceneQueryParser lqp, Serializable value, Boolean not) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + CMISTypeDefinition type = getServiceRegistry().getCMISDictionaryService().findType(stringValue); + String typeQName = type.getTypeId().getQName().toString(); + + if (not) + { + BooleanQuery booleanQuery = new BooleanQuery(); + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + booleanQuery.add(lqp.getLikeQuery(field, typeQName, AnalysisMode.IDENTIFIER), Occur.MUST_NOT); + return booleanQuery; + } else + { + return lqp.getLikeQuery(field, typeQName, AnalysisMode.IDENTIFIER); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#getLuceneSortField() + */ + public String getLuceneSortField(LuceneQueryParser lqp) + { + return getLuceneFieldName(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/ParentActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/ParentActionEvaluator.java new file mode 100644 index 0000000000..f3ddd520ec --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/ParentActionEvaluator.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Action Evaluator whose evaluation takes place on parent + * + * @author davidc + */ +public class ParentActionEvaluator extends AbstractActionEvaluator +{ + private AbstractActionEvaluator evaluator; + + /** + * Construct + * + * @param serviceRegistry + * @param action + */ + protected ParentActionEvaluator(AbstractActionEvaluator evaluator) + { + super(evaluator.getServiceRegistry(), evaluator.getAction()); + this.evaluator = evaluator; + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.CMISActionEvaluator#isAllowed(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isAllowed(NodeRef nodeRef) + { + if (nodeRef.equals(getServiceRegistry().getCMISService().getDefaultRootNodeRef())) + { + return false; + } + + ChildAssociationRef car = getServiceRegistry().getNodeService().getPrimaryParent(nodeRef); + if ((car != null) && (car.getParentRef() != null)) + { + return evaluator.isAllowed(car.getParentRef()); + } + + return false; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("ParentActionEvaluator[evaluator=").append(evaluator).append("]"); + return builder.toString(); + } +} + diff --git a/source/java/org/alfresco/opencmis/mapping/ParentProperty.java b/source/java/org/alfresco/opencmis/mapping/ParentProperty.java new file mode 100644 index 0000000000..1d30a748cc --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/ParentProperty.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; +import java.util.Collection; + +import org.alfresco.repo.search.impl.lucene.AnalysisMode; +import org.alfresco.repo.search.impl.lucene.LuceneFunction; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.apache.chemistry.opencmis.commons.PropertyIds; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; + +/** + * Get the CMIS parent property + * + * @author andyh + * + */ +public class ParentProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public ParentProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.PARENT_ID); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + if (nodeRef.equals(getServiceRegistry().getCMISService().getDefaultRootNodeRef())) + { + return null; + } + + ChildAssociationRef car = getServiceRegistry().getNodeService().getPrimaryParent(nodeRef); + if ((car != null) && (car.getParentRef() != null)) + { + return car.getParentRef().toString(); + } else + { + return null; + } + } + + public String getLuceneFieldName() + { + return "PARENT"; + } + + private String getValueAsString(Serializable value) + { + Object converted = DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService() + .getDataType(DataTypeDefinition.NODE_REF), value); + String asString = DefaultTypeConverter.INSTANCE.convert(String.class, converted); + return asString; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneEquality( + * org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneEquality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getFieldQuery(field, stringValue, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneExists(org + * .alfresco.repo.search.impl.lucene.LuceneQueryParser, java.lang.Boolean) + */ + public Query buildLuceneExists(LuceneQueryParser lqp, Boolean not) throws ParseException + { + if (not) + { + return new TermQuery(new Term("ISROOT", "T")); + } else + { + return new MatchAllDocsQuery(); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneIn(org.alfresco + * .repo.search.impl.lucene.LuceneQueryParser, java.util.Collection, + * java.lang.Boolean, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneIn(LuceneQueryParser lqp, Collection values, Boolean not, PredicateMode mode) + throws ParseException + { + String field = getLuceneFieldName(); + + // Check type conversion + + @SuppressWarnings("unused") + Object converted = DefaultTypeConverter.INSTANCE.convert(getServiceRegistry().getDictionaryService() + .getDataType(DataTypeDefinition.NODE_REF), values); + Collection asStrings = DefaultTypeConverter.INSTANCE.convert(String.class, values); + + if (asStrings.size() == 0) + { + if (not) + { + return new MatchAllDocsQuery(); + } else + { + return new TermQuery(new Term("NO_TOKENS", "__")); + } + } else if (asStrings.size() == 1) + { + String value = asStrings.iterator().next(); + if (not) + { + return lqp.getDoesNotMatchFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } else + { + return lqp.getFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + } + } else + { + BooleanQuery booleanQuery = new BooleanQuery(); + if (not) + { + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + } + for (String value : asStrings) + { + Query any = lqp.getFieldQuery(field, value, AnalysisMode.IDENTIFIER, LuceneFunction.FIELD); + if (not) + { + booleanQuery.add(any, Occur.MUST_NOT); + } else + { + booleanQuery.add(any, Occur.SHOULD); + } + } + return booleanQuery; + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneInequality + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneInequality(LuceneQueryParser lqp, Serializable value, PredicateMode mode, + LuceneFunction luceneFunction) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + return lqp.getDoesNotMatchFieldQuery(field, stringValue, AnalysisMode.IDENTIFIER, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#buildLuceneLike(org. + * alfresco.repo.search.impl.lucene.LuceneQueryParser, java.io.Serializable, + * java.lang.Boolean) + */ + public Query buildLuceneLike(LuceneQueryParser lqp, Serializable value, Boolean not) throws ParseException + { + String field = getLuceneFieldName(); + String stringValue = getValueAsString(value); + + if (not) + { + BooleanQuery booleanQuery = new BooleanQuery(); + booleanQuery.add(new MatchAllDocsQuery(), Occur.MUST); + booleanQuery.add(lqp.getLikeQuery(field, stringValue, AnalysisMode.IDENTIFIER), Occur.MUST_NOT); + return booleanQuery; + } else + { + return lqp.getLikeQuery(field, stringValue, AnalysisMode.IDENTIFIER); + } + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyLuceneBuilder#getLuceneSortField() + */ + public String getLuceneSortField(LuceneQueryParser lqp) + { + return getLuceneFieldName(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/PathProperty.java b/source/java/org/alfresco/opencmis/mapping/PathProperty.java new file mode 100644 index 0000000000..4da72c767d --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/PathProperty.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.Path.ChildAssocElement; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS path property. + * + * @author davidc + */ +public class PathProperty extends AbstractProperty +{ + private CMISConnector cmisConnector; + + /** + * Construct + * + * @param serviceRegistry + */ + public PathProperty(ServiceRegistry serviceRegistry, CMISConnector cmisConnector) + { + super(serviceRegistry, PropertyIds.PATH); + this.cmisConnector = cmisConnector; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + Path path = getServiceRegistry().getNodeService().getPath(nodeRef); + return toDisplayPath(path); + } + + private String toDisplayPath(Path path) + { + StringBuilder displayPath = new StringBuilder(64); + + // skip to CMIS root path + NodeRef rootNode = cmisConnector.getRootNodeRef(); + int i = 0; + while (i < path.size()) + { + Path.Element element = path.get(i); + if (element instanceof ChildAssocElement) + { + ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); + NodeRef node = assocRef.getChildRef(); + if (node.equals(rootNode)) + { + break; + } + } + i++; + } + + if (i == path.size()) + { + // TODO: + // throw new AlfrescoRuntimeException("Path " + path + + // " not in CMIS root node scope"); + } + + if (path.size() - i == 1) + { + // render root path + displayPath.append("/"); + } else + { + // render CMIS scoped path + i++; + while (i < path.size()) + { + Path.Element element = path.get(i); + if (element instanceof ChildAssocElement) + { + ChildAssociationRef assocRef = ((ChildAssocElement) element).getRef(); + NodeRef node = assocRef.getChildRef(); + displayPath.append("/"); + displayPath.append(getServiceRegistry().getNodeService().getProperty(node, ContentModel.PROP_NAME)); + } + i++; + } + } + + return displayPath.toString(); + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/PermissionActionEvaluator.java b/source/java/org/alfresco/opencmis/mapping/PermissionActionEvaluator.java new file mode 100644 index 0000000000..4e08183da6 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/PermissionActionEvaluator.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.apache.chemistry.opencmis.commons.enums.Action; + +/** + * Alfresco Permission based Action Evaluator + * + * @author davidc + */ +public class PermissionActionEvaluator extends AbstractActionEvaluator +{ + private String[] permissions; + private PermissionService permissionService; + + /** + * Construct + * + * @param serviceRegistry + * @param permission + */ + protected PermissionActionEvaluator(ServiceRegistry serviceRegistry, Action action, String... permission) + { + super(serviceRegistry, action); + this.permissions = permission; + this.permissionService = serviceRegistry.getPermissionService(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.cmis.CMISActionEvaluator#isAllowed(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isAllowed(NodeRef nodeRef) + { + for (String permission : permissions) + { + if (permissionService.hasPermission(nodeRef, permission) == AccessStatus.DENIED) + { + return false; + } + } + return true; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append("PermissionActionEvaluator[action=").append(getAction()); + builder.append(", permissions="); + for (String permission : permissions) + { + builder.append(permission).append(","); + } + builder.append("]"); + return builder.toString(); + } + +} diff --git a/source/java/org/alfresco/opencmis/mapping/RootFolderEvaluator.java b/source/java/org/alfresco/opencmis/mapping/RootFolderEvaluator.java new file mode 100644 index 0000000000..de53660748 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/RootFolderEvaluator.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import org.alfresco.opencmis.CMISActionEvaluator; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; + +public class RootFolderEvaluator extends AbstractActionEvaluator +{ + private CMISConnector cmisConnector; + private CMISActionEvaluator folderEvaluator; + private boolean rootFolderValue; + + protected RootFolderEvaluator(ServiceRegistry serviceRegistry, CMISConnector cmisConnector, + CMISActionEvaluator folderEvaluator, boolean rootFolderValue) + { + super(serviceRegistry, folderEvaluator.getAction()); + this.cmisConnector = cmisConnector; + this.folderEvaluator = folderEvaluator; + this.rootFolderValue = rootFolderValue; + } + + public boolean isAllowed(NodeRef nodeRef) + { + if (cmisConnector.getRootNodeRef().equals(nodeRef)) + { + return rootFolderValue; + } + + return folderEvaluator.isAllowed(nodeRef); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/SourceIdProperty.java b/source/java/org/alfresco/opencmis/mapping/SourceIdProperty.java new file mode 100644 index 0000000000..46445fc203 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/SourceIdProperty.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for the Source Id (relationship) + * + * @author davidc + */ +public class SourceIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public SourceIdProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.SOURCE_ID); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service + * .cmr.repository.AssociationRef) + */ + public Serializable getValue(AssociationRef assocRef) + { + return assocRef.getSourceRef().toString(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/TargetIdProperty.java b/source/java/org/alfresco/opencmis/mapping/TargetIdProperty.java new file mode 100644 index 0000000000..9b901e27c1 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/TargetIdProperty.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.AssociationRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Accessor for the Target Id (relationship) + * + * @author davidc + */ +public class TargetIdProperty extends AbstractProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public TargetIdProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.TARGET_ID); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.mapping.AbstractProperty#getValue(org.alfresco.service + * .cmr.repository.AssociationRef) + */ + public Serializable getValue(AssociationRef assocRef) + { + return assocRef.getTargetRef().toString(); + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/VersionLabelProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionLabelProperty.java new file mode 100644 index 0000000000..75f35e95fd --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/VersionLabelProperty.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.opencmis.CMISConnector; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * @author dward + */ +public class VersionLabelProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public VersionLabelProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.VERSION_LABEL); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + if (isWorkingCopy(nodeRef)) + { + return CMISConnector.PWC_VERSION_LABEL; + } + Serializable versionLabel = getServiceRegistry().getNodeService().getProperty(nodeRef, + ContentModel.PROP_VERSION_LABEL); + if (versionLabel == null) + { + return CMISConnector.UNVERSIONED_VERSION_LABEL; + } + return versionLabel; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutByProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutByProperty.java new file mode 100644 index 0000000000..25ca5aa7a2 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutByProperty.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS version series checked out by property + * + * @author dward + */ +public class VersionSeriesCheckedOutByProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public VersionSeriesCheckedOutByProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.VERSION_SERIES_CHECKED_OUT_BY); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + NodeRef versionSeries; + if (isWorkingCopy(nodeRef)) + { + return getServiceRegistry().getNodeService().getProperty(nodeRef, ContentModel.PROP_WORKING_COPY_OWNER); + } else if (hasWorkingCopy((versionSeries = getVersionSeries(nodeRef)))) + { + return getServiceRegistry().getNodeService().getProperty(versionSeries, ContentModel.PROP_LOCK_OWNER); + } + return null; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java new file mode 100644 index 0000000000..4e8a08c05f --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/VersionSeriesCheckedOutIdProperty.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * Get the CMIS version series checked out id property + * + * @author dward + */ +public class VersionSeriesCheckedOutIdProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public VersionSeriesCheckedOutIdProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.VERSION_SERIES_CHECKED_OUT_ID); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + NodeRef versionSeries; + if (isWorkingCopy(nodeRef)) + { + return nodeRef.toString(); + } else if (hasWorkingCopy((versionSeries = getVersionSeries(nodeRef)))) + { + NodeRef pwc = getServiceRegistry().getCheckOutCheckInService().getWorkingCopy(versionSeries); + if (pwc != null) + { + return pwc.toString(); + } + } + return null; + } +} diff --git a/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java b/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java new file mode 100644 index 0000000000..0bfa80c456 --- /dev/null +++ b/source/java/org/alfresco/opencmis/mapping/VersionSeriesIdProperty.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.mapping; + +import java.io.Serializable; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.apache.chemistry.opencmis.commons.PropertyIds; + +/** + * @author dward + */ +public class VersionSeriesIdProperty extends AbstractVersioningProperty +{ + /** + * Construct + * + * @param serviceRegistry + */ + public VersionSeriesIdProperty(ServiceRegistry serviceRegistry) + { + super(serviceRegistry, PropertyIds.VERSION_SERIES_ID); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.property.PropertyAccessor#getValue(org.alfresco.service + * .cmr.repository.NodeRef) + */ + public Serializable getValue(NodeRef nodeRef) + { + NodeService nodeService = getServiceRegistry().getNodeService(); + if (isWorkingCopy(nodeRef)) + { + return nodeService.getProperty(nodeRef, ContentModel.PROP_COPY_REFERENCE).toString(); + } else + { + return getVersionSeries(nodeRef).toString(); + } + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISFTSQueryParser.java b/source/java/org/alfresco/opencmis/search/CMISFTSQueryParser.java new file mode 100644 index 0000000000..4c91e9398c --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISFTSQueryParser.java @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.repo.search.impl.lucene.AnalysisMode; +import org.alfresco.repo.search.impl.parsers.CMIS_FTSLexer; +import org.alfresco.repo.search.impl.parsers.CMIS_FTSParser; +import org.alfresco.repo.search.impl.parsers.FTSQueryException; +import org.alfresco.repo.search.impl.querymodel.Argument; +import org.alfresco.repo.search.impl.querymodel.Column; +import org.alfresco.repo.search.impl.querymodel.Constraint; +import org.alfresco.repo.search.impl.querymodel.Function; +import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext; +import org.alfresco.repo.search.impl.querymodel.LiteralArgument; +import org.alfresco.repo.search.impl.querymodel.QueryModelFactory; +import org.alfresco.repo.search.impl.querymodel.Selector; +import org.alfresco.repo.search.impl.querymodel.Constraint.Occur; +import org.alfresco.repo.search.impl.querymodel.QueryOptions.Connective; +import org.alfresco.repo.search.impl.querymodel.impl.functions.FTSPhrase; +import org.alfresco.repo.search.impl.querymodel.impl.functions.FTSTerm; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.antlr.runtime.ANTLRStringStream; +import org.antlr.runtime.CharStream; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.RecognitionException; +import org.antlr.runtime.Token; +import org.antlr.runtime.tree.CommonTree; +import org.antlr.runtime.tree.Tree; + +public class CMISFTSQueryParser +{ + static public Constraint buildFTS(String ftsExpression, QueryModelFactory factory, FunctionEvaluationContext functionEvaluationContext, Selector selector, + Map columnMap, String defaultField) + { + // TODO: Decode sql escape for '' should do in CMIS layer + + // parse templates to trees ... + + CMIS_FTSParser parser = null; + try + { + CharStream cs = new ANTLRStringStream(ftsExpression); + CMIS_FTSLexer lexer = new CMIS_FTSLexer(cs); + CommonTokenStream tokens = new CommonTokenStream(lexer); + parser = new CMIS_FTSParser(tokens); + CommonTree ftsNode = (CommonTree) parser.cmisFtsQuery().getTree(); + return buildFTSConnective(ftsNode, factory, functionEvaluationContext, selector, columnMap, defaultField); + } + catch (RecognitionException e) + { + if (parser != null) + { + String[] tokenNames = parser.getTokenNames(); + String hdr = parser.getErrorHeader(e); + String msg = parser.getErrorMessage(e, tokenNames); + throw new FTSQueryException(hdr + "\n" + msg, e); + } + return null; + } + + } + + static private Constraint buildFTSConnective(CommonTree node, QueryModelFactory factory, FunctionEvaluationContext functionEvaluationContext, + Selector selector, Map columnMap, String defaultField) + { + Connective connective; + switch (node.getType()) + { + case CMIS_FTSParser.DISJUNCTION: + connective = Connective.OR; + break; + case CMIS_FTSParser.CONJUNCTION: + connective = Connective.AND; + break; + default: + throw new FTSQueryException("Invalid connective ..." + node.getText()); + } + + List constraints = new ArrayList(node.getChildCount()); + CommonTree testNode; + for (int i = 0; i < node.getChildCount(); i++) + { + CommonTree subNode = (CommonTree) node.getChild(i); + Constraint constraint; + switch (subNode.getType()) + { + case CMIS_FTSParser.DISJUNCTION: + case CMIS_FTSParser.CONJUNCTION: + constraint = buildFTSConnective(subNode, factory, functionEvaluationContext, selector, columnMap, defaultField); + break; + case CMIS_FTSParser.DEFAULT: + testNode = (CommonTree) subNode.getChild(0); + constraint = buildFTSTest(testNode, factory, functionEvaluationContext, selector, columnMap, defaultField); + constraint.setOccur(Occur.DEFAULT); + break; + case CMIS_FTSParser.EXCLUDE: + testNode = (CommonTree) subNode.getChild(0); + constraint = buildFTSTest(testNode, factory, functionEvaluationContext, selector, columnMap, defaultField); + constraint.setOccur(Occur.EXCLUDE); + break; + + default: + throw new FTSQueryException("Unsupported FTS option " + subNode.getText()); + } + constraints.add(constraint); + } + if (constraints.size() == 1) + { + return constraints.get(0); + } + else + { + if (connective == Connective.OR) + { + return factory.createDisjunction(constraints); + } + else + { + return factory.createConjunction(constraints); + } + } + } + + static private Constraint buildFTSTest(CommonTree argNode, QueryModelFactory factory, FunctionEvaluationContext functionEvaluationContext, + Selector selector, Map columnMap, String defaultField) + { + CommonTree testNode = argNode; + switch (testNode.getType()) + { + case CMIS_FTSParser.DISJUNCTION: + case CMIS_FTSParser.CONJUNCTION: + return buildFTSConnective(testNode, factory, functionEvaluationContext, selector, columnMap, defaultField); + case CMIS_FTSParser.TERM: + return buildTerm(testNode, factory, functionEvaluationContext, selector, columnMap); + case CMIS_FTSParser.PHRASE: + return buildPhrase(testNode, factory, functionEvaluationContext, selector, columnMap); + default: + throw new FTSQueryException("Unsupported FTS option " + testNode.getText()); + } + } + + static private Constraint buildPhrase(CommonTree testNode, QueryModelFactory factory, + FunctionEvaluationContext functionEvaluationContext, Selector selector, Map columnMap) + { + String functionName = FTSPhrase.NAME; + Function function = factory.getFunction(functionName); + Map functionArguments = new LinkedHashMap(); + LiteralArgument larg = factory.createLiteralArgument(FTSPhrase.ARG_PHRASE, DataTypeDefinition.TEXT, getText(testNode.getChild(0))); + functionArguments.put(larg.getName(), larg); + return factory.createFunctionalConstraint(function, functionArguments); + } + + static private Constraint buildTerm(CommonTree testNode, QueryModelFactory factory, FunctionEvaluationContext functionEvaluationContext, + Selector selector, Map columnMap) + { + String functionName = FTSTerm.NAME; + Function function = factory.getFunction(functionName); + Map functionArguments = new LinkedHashMap(); + LiteralArgument larg = factory.createLiteralArgument(FTSTerm.ARG_TERM, DataTypeDefinition.TEXT, getText(testNode.getChild(0))); + functionArguments.put(larg.getName(), larg); + larg = factory.createLiteralArgument(FTSTerm.ARG_TOKENISATION_MODE, DataTypeDefinition.ANY, AnalysisMode.DEFAULT); + functionArguments.put(larg.getName(), larg); + return factory.createFunctionalConstraint(function, functionArguments); + } + + + static class DisjunctionToken implements Token + { + + public int getChannel() + { + return 0; + } + + public int getCharPositionInLine() + { + return 0; + } + + public CharStream getInputStream() + { + return null; + } + + public int getLine() + { + return 0; + } + + public String getText() + { + return null; + } + + public int getTokenIndex() + { + return 0; + } + + public int getType() + { + return CMIS_FTSParser.DISJUNCTION; + } + + public void setChannel(int arg0) + { + + + } + + public void setCharPositionInLine(int arg0) + { + + + } + + public void setInputStream(CharStream arg0) + { + + } + + public void setLine(int arg0) + { + + + } + + public void setText(String arg0) + { + + + } + + public void setTokenIndex(int arg0) + { + + } + + public void setType(int arg0) + { + + } + + } + + static class DefaultToken implements Token + { + + public int getChannel() + { + + return 0; + } + + public int getCharPositionInLine() + { + return 0; + } + + public CharStream getInputStream() + { + return null; + } + + public int getLine() + { + return 0; + } + + public String getText() + { + return null; + } + + public int getTokenIndex() + { + return 0; + } + + public int getType() + { + return CMIS_FTSParser.DEFAULT; + } + + public void setChannel(int arg0) + { + + } + + public void setCharPositionInLine(int arg0) + { + + } + + public void setInputStream(CharStream arg0) + { + + } + + public void setLine(int arg0) + { + + } + + public void setText(String arg0) + { + + } + + public void setTokenIndex(int arg0) + { + + } + + public void setType(int arg0) + { + + } + + } + + static private String getText(Tree node) + { + String text = node.getText(); + int index; + switch (node.getType()) + { + case CMIS_FTSParser.FTSWORD: + index = text.indexOf('\\'); + if (index == -1) + { + return text; + } + else + { + return unescape(text); + } + case CMIS_FTSParser.FTSPHRASE: + String phrase = text.substring(1, text.length() - 1); + index = phrase.indexOf('\\'); + if (index == -1) + { + return phrase; + } + else + { + return unescape(phrase); + } + default: + return text; + } + } + + static private String unescape(String string) + { + StringBuilder builder = new StringBuilder(string.length()); + boolean lastWasEscape = false; + + for (int i = 0; i < string.length(); i++) + { + char c = string.charAt(i); + if (lastWasEscape) + { + if (c == 'u') + { + throw new UnsupportedOperationException(string); + } + else + { + builder.append(c); + } + lastWasEscape = false; + } + else + { + if (c == '\\') + { + lastWasEscape = true; + } + else + { + builder.append(c); + } + } + } + if (lastWasEscape) + { + throw new FTSQueryException("Escape character at end of string " + string); + } + + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISQueryOptions.java b/source/java/org/alfresco/opencmis/search/CMISQueryOptions.java new file mode 100644 index 0000000000..52a13df6ed --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISQueryOptions.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.util.Locale; + +import org.springframework.extensions.surf.util.I18NUtil; +import org.alfresco.repo.search.impl.querymodel.QueryOptions; +import org.alfresco.service.cmr.repository.StoreRef; + +/** + * The options for a CMIS query + * + * @author andyh + */ +public class CMISQueryOptions extends QueryOptions +{ + public enum CMISQueryMode + { + CMS_STRICT, CMS_WITH_ALFRESCO_EXTENSIONS; + } + + private CMISQueryMode queryMode = CMISQueryMode.CMS_STRICT; + + /** + * Create a CMISQueryOptions instance with the default options other than + * the query and store ref. The query will be run using the locale returned + * by I18NUtil.getLocale() + * + * @param query + * - the query to run + * @param storeRef + * - the store against which to run the query + */ + public CMISQueryOptions(String query, StoreRef storeRef) + { + this(query, storeRef, I18NUtil.getLocale()); + } + + /** + * Create a CMISQueryOptions instance with the default options other than + * the query, store ref and locale. + * + * @param query + * - the query to run + * @param storeRef + * - the store against which to run the query + */ + public CMISQueryOptions(String query, StoreRef storeRef, Locale locale) + { + super(query, storeRef, locale); + } + + /** + * Get the query mode. + * + * @return the queryMode + */ + public CMISQueryMode getQueryMode() + { + return queryMode; + } + + /** + * Set the query mode. + * + * @param queryMode + * the queryMode to set + */ + public void setQueryMode(CMISQueryMode queryMode) + { + this.queryMode = queryMode; + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISQueryParser.java b/source/java/org/alfresco/opencmis/search/CMISQueryParser.java new file mode 100644 index 0000000000..3e7cc25ff4 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISQueryParser.java @@ -0,0 +1,1706 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.PropertyDefintionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; +import org.alfresco.repo.search.impl.parsers.CMISLexer; +import org.alfresco.repo.search.impl.parsers.CMISParser; +import org.alfresco.repo.search.impl.parsers.FTSParser; +import org.alfresco.repo.search.impl.parsers.FTSQueryException; +import org.alfresco.repo.search.impl.parsers.FTSQueryParser; +import org.alfresco.repo.search.impl.querymodel.Argument; +import org.alfresco.repo.search.impl.querymodel.ArgumentDefinition; +import org.alfresco.repo.search.impl.querymodel.Column; +import org.alfresco.repo.search.impl.querymodel.Constraint; +import org.alfresco.repo.search.impl.querymodel.Constraint.Occur; +import org.alfresco.repo.search.impl.querymodel.Function; +import org.alfresco.repo.search.impl.querymodel.FunctionArgument; +import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext; +import org.alfresco.repo.search.impl.querymodel.JoinType; +import org.alfresco.repo.search.impl.querymodel.ListArgument; +import org.alfresco.repo.search.impl.querymodel.LiteralArgument; +import org.alfresco.repo.search.impl.querymodel.Order; +import org.alfresco.repo.search.impl.querymodel.Ordering; +import org.alfresco.repo.search.impl.querymodel.ParameterArgument; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.repo.search.impl.querymodel.PropertyArgument; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.repo.search.impl.querymodel.QueryModelException; +import org.alfresco.repo.search.impl.querymodel.QueryModelFactory; +import org.alfresco.repo.search.impl.querymodel.QueryOptions.Connective; +import org.alfresco.repo.search.impl.querymodel.Selector; +import org.alfresco.repo.search.impl.querymodel.SelectorArgument; +import org.alfresco.repo.search.impl.querymodel.Source; +import org.alfresco.repo.search.impl.querymodel.impl.BaseComparison; +import org.alfresco.repo.search.impl.querymodel.impl.functions.Child; +import org.alfresco.repo.search.impl.querymodel.impl.functions.Descendant; +import org.alfresco.repo.search.impl.querymodel.impl.functions.Equals; +import org.alfresco.repo.search.impl.querymodel.impl.functions.Exists; +import org.alfresco.repo.search.impl.querymodel.impl.functions.GreaterThan; +import org.alfresco.repo.search.impl.querymodel.impl.functions.GreaterThanOrEquals; +import org.alfresco.repo.search.impl.querymodel.impl.functions.In; +import org.alfresco.repo.search.impl.querymodel.impl.functions.LessThan; +import org.alfresco.repo.search.impl.querymodel.impl.functions.LessThanOrEquals; +import org.alfresco.repo.search.impl.querymodel.impl.functions.Like; +import org.alfresco.repo.search.impl.querymodel.impl.functions.NotEquals; +import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; +import org.alfresco.repo.search.impl.querymodel.impl.functions.Score; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.CachingDateFormat; +import org.antlr.runtime.ANTLRStringStream; +import org.antlr.runtime.CharStream; +import org.antlr.runtime.CommonTokenStream; +import org.antlr.runtime.RecognitionException; +import org.antlr.runtime.tree.CommonTree; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; + +/** + * @author andyh + */ +public class CMISQueryParser +{ + private enum EscapeMode + { + LITERAL, LIKE, CONTAINS; + } + + private CMISQueryOptions options; + + private CMISDictionaryService cmisDictionaryService; + + private CapabilityJoin joinSupport; + + private BaseTypeId[] validScopes; + + private boolean hasScore = false; + + private boolean hasContains = false; + + public CMISQueryParser(CMISQueryOptions options, CMISDictionaryService cmisDictionaryService, + CapabilityJoin joinSupport) + { + this.options = options; + this.cmisDictionaryService = cmisDictionaryService; + this.joinSupport = joinSupport; + this.validScopes = (options.getQueryMode() == CMISQueryMode.CMS_STRICT) ? CmisFunctionEvaluationContext.STRICT_SCOPES + : CmisFunctionEvaluationContext.ALFRESCO_SCOPES; + } + + public Query parse(QueryModelFactory factory, FunctionEvaluationContext functionEvaluationContext) + { + + CMISParser parser = null; + try + { + CharStream cs = new ANTLRStringStream(options.getQuery()); + CMISLexer lexer = new CMISLexer(cs); + CommonTokenStream tokens = new CommonTokenStream(lexer); + parser = new CMISParser(tokens); + parser.setStrict(options.getQueryMode() == CMISQueryMode.CMS_STRICT); + CommonTree queryNode = (CommonTree) parser.query().getTree(); + + CommonTree sourceNode = (CommonTree) queryNode.getFirstChildWithType(CMISParser.SOURCE); + Source source = buildSource(sourceNode, joinSupport, factory); + Map selectors = source.getSelectors(); + ArrayList columns = buildColumns(queryNode, factory, selectors, options.getQuery()); + + HashMap columnMap = new HashMap(); + for (Column column : columns) + { + if (columnMap.containsKey(column.getAlias())) + { + throw new CmisInvalidArgumentException("Duplicate column alias for " + column.getAlias()); + } else + { + columnMap.put(column.getAlias(), column); + } + } + + ArrayList orderings = buildOrderings(queryNode, factory, selectors, columns); + + Constraint constraint = null; + CommonTree orNode = (CommonTree) queryNode.getFirstChildWithType(CMISParser.DISJUNCTION); + if (orNode != null) + { + constraint = buildDisjunction(orNode, factory, functionEvaluationContext, selectors, columnMap); + } + + Query query = factory.createQuery(columns, source, constraint, orderings); + + // TODO: validate query and use of ID, function arguments matching + // up etc + + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + if (hasScore && !hasContains) + { + throw new CmisInvalidArgumentException("Function SCORE() used without matching CONTAINS() function"); + } + } + + return query; + } catch (RecognitionException e) + { + if (parser != null) + { + String[] tokenNames = parser.getTokenNames(); + String hdr = parser.getErrorHeader(e); + String msg = parser.getErrorMessage(e, tokenNames); + throw new CmisInvalidArgumentException(hdr + "\n" + msg, e); + } + } + throw new CmisInvalidArgumentException("Failed to parse"); + } + + /** + * @param queryNode + * @param factory + * @param selectors + * @param columns + * @return + */ + private Constraint buildDisjunction(CommonTree orNode, QueryModelFactory factory, + FunctionEvaluationContext functionEvaluationContext, Map selectors, + HashMap columnMap) + { + List constraints = new ArrayList(orNode.getChildCount()); + for (int i = 0; i < orNode.getChildCount(); i++) + { + CommonTree andNode = (CommonTree) orNode.getChild(i); + Constraint constraint = buildConjunction(andNode, factory, functionEvaluationContext, selectors, columnMap); + constraints.add(constraint); + } + if (constraints.size() == 1) + { + return constraints.get(0); + } else + { + return factory.createDisjunction(constraints); + } + } + + /** + * @param andNode + * @param factory + * @param selectors + * @param columns + * @return + */ + private Constraint buildConjunction(CommonTree andNode, QueryModelFactory factory, + FunctionEvaluationContext functionEvaluationContext, Map selectors, + HashMap columnMap) + { + List constraints = new ArrayList(andNode.getChildCount()); + for (int i = 0; i < andNode.getChildCount(); i++) + { + CommonTree notNode = (CommonTree) andNode.getChild(i); + Constraint constraint = buildNegation(notNode, factory, functionEvaluationContext, selectors, columnMap); + constraints.add(constraint); + } + if (constraints.size() == 1) + { + return constraints.get(0); + } else + { + return factory.createConjunction(constraints); + } + } + + /** + * @param notNode + * @param factory + * @param selectors + * @param columns + * @return + */ + private Constraint buildNegation(CommonTree notNode, QueryModelFactory factory, + FunctionEvaluationContext functionEvaluationContext, Map selectors, + HashMap columnMap) + { + if (notNode.getType() == CMISParser.NEGATION) + { + Constraint constraint = buildTest((CommonTree) notNode.getChild(0), factory, functionEvaluationContext, + selectors, columnMap); + constraint.setOccur(Occur.EXCLUDE); + return constraint; + } else + { + return buildTest(notNode, factory, functionEvaluationContext, selectors, columnMap); + } + } + + /** + * @param notNode + * @param factory + * @param selectors + * @param columns + * @return + */ + private Constraint buildTest(CommonTree testNode, QueryModelFactory factory, + FunctionEvaluationContext functionEvaluationContext, Map selectors, + HashMap columnMap) + { + if (testNode.getType() == CMISParser.DISJUNCTION) + { + return buildDisjunction(testNode, factory, functionEvaluationContext, selectors, columnMap); + } else + { + return buildPredicate(testNode, factory, functionEvaluationContext, selectors, columnMap); + } + } + + /** + * @param orNode + * @param factory + * @param selectors + * @param columns + * @return + */ + private Constraint buildPredicate(CommonTree predicateNode, QueryModelFactory factory, + FunctionEvaluationContext functionEvaluationContext, Map selectors, + Map columnMap) + { + String functionName; + Function function; + CommonTree argNode; + Map functionArguments; + Argument arg; + switch (predicateNode.getType()) + { + case CMISParser.PRED_CHILD: + functionName = Child.NAME; + function = factory.getFunction(functionName); + functionArguments = new LinkedHashMap(); + argNode = (CommonTree) predicateNode.getChild(0); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(Child.ARG_PARENT), factory, selectors, + columnMap, false); + functionArguments.put(arg.getName(), arg); + if (predicateNode.getChildCount() > 1) + { + argNode = (CommonTree) predicateNode.getChild(1); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(Child.ARG_SELECTOR), factory, + selectors, columnMap, false); + if (!arg.isQueryable()) + { + throw new CmisInvalidArgumentException("The property is not queryable: " + argNode.getText()); + } + functionArguments.put(arg.getName(), arg); + } + return factory.createFunctionalConstraint(function, functionArguments); + case CMISParser.PRED_COMPARISON: + + switch (predicateNode.getChild(2).getType()) + { + case CMISParser.EQUALS: + functionName = Equals.NAME; + function = factory.getFunction(functionName); + break; + case CMISParser.NOTEQUALS: + functionName = NotEquals.NAME; + function = factory.getFunction(functionName); + break; + case CMISParser.GREATERTHAN: + functionName = GreaterThan.NAME; + function = factory.getFunction(functionName); + break; + case CMISParser.GREATERTHANOREQUALS: + functionName = GreaterThanOrEquals.NAME; + function = factory.getFunction(functionName); + break; + case CMISParser.LESSTHAN: + functionName = LessThan.NAME; + function = factory.getFunction(functionName); + break; + case CMISParser.LESSTHANOREQUALS: + functionName = LessThanOrEquals.NAME; + function = factory.getFunction(functionName); + break; + default: + throw new CmisInvalidArgumentException("Unknown comparison function " + + predicateNode.getChild(2).getText()); + } + functionArguments = new LinkedHashMap(); + argNode = (CommonTree) predicateNode.getChild(0); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(BaseComparison.ARG_MODE), factory, + selectors, columnMap, false); + functionArguments.put(arg.getName(), arg); + argNode = (CommonTree) predicateNode.getChild(1); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(BaseComparison.ARG_LHS), factory, + selectors, columnMap, false); + functionArguments.put(arg.getName(), arg); + argNode = (CommonTree) predicateNode.getChild(3); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(BaseComparison.ARG_RHS), factory, + selectors, columnMap, false); + functionArguments.put(arg.getName(), arg); + checkPredicateConditionsForComparisons(function, functionArguments, functionEvaluationContext, columnMap); + return factory.createFunctionalConstraint(function, functionArguments); + case CMISParser.PRED_DESCENDANT: + functionName = Descendant.NAME; + function = factory.getFunction(functionName); + argNode = (CommonTree) predicateNode.getChild(0); + functionArguments = new LinkedHashMap(); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(Descendant.ARG_ANCESTOR), factory, + selectors, columnMap, false); + functionArguments.put(arg.getName(), arg); + if (predicateNode.getChildCount() > 1) + { + argNode = (CommonTree) predicateNode.getChild(1); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(Descendant.ARG_SELECTOR), factory, + selectors, columnMap, false); + functionArguments.put(arg.getName(), arg); + } + return factory.createFunctionalConstraint(function, functionArguments); + case CMISParser.PRED_EXISTS: + functionName = Exists.NAME; + function = factory.getFunction(functionName); + argNode = (CommonTree) predicateNode.getChild(0); + functionArguments = new LinkedHashMap(); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(Exists.ARG_PROPERTY), factory, selectors, + columnMap, false); + functionArguments.put(arg.getName(), arg); + arg = factory.createLiteralArgument(Exists.ARG_NOT, DataTypeDefinition.BOOLEAN, + (predicateNode.getChildCount() > 1)); + functionArguments.put(arg.getName(), arg); + // Applies to both single valued and multi-valued properties - no + // checks required + return factory.createFunctionalConstraint(function, functionArguments); + case CMISParser.PRED_FTS: + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + if (hasContains) + { + throw new CmisInvalidArgumentException( + "Only one CONTAINS() function can be included in a single query statement."); + } + } + String ftsExpression = predicateNode.getChild(0).getText(); + ftsExpression = ftsExpression.substring(1, ftsExpression.length() - 1); + ftsExpression = unescape(ftsExpression, EscapeMode.CONTAINS); + Selector selector; + if (predicateNode.getChildCount() > 1) + { + String qualifier = predicateNode.getChild(1).getText(); + selector = selectors.get(qualifier); + if (selector == null) + { + throw new CmisInvalidArgumentException("No selector for " + qualifier); + } + } else + { + if (selectors.size() == 1) + { + selector = selectors.get(selectors.keySet().iterator().next()); + } else + { + throw new CmisInvalidArgumentException( + "A selector must be specified when there are two or more selectors"); + } + } + Connective defaultConnective; + Connective defaultFieldConnective; + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + defaultConnective = Connective.AND; + defaultFieldConnective = Connective.AND; + } else + { + defaultConnective = options.getDefaultFTSConnective(); + defaultFieldConnective = options.getDefaultFTSFieldConnective(); + } + FTSParser.Mode mode; + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + mode = FTSParser.Mode.CMIS; + } else + { + if (defaultConnective == Connective.AND) + { + mode = FTSParser.Mode.DEFAULT_CONJUNCTION; + } else + { + mode = FTSParser.Mode.DEFAULT_DISJUNCTION; + } + } + Constraint ftsConstraint; + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + ftsConstraint = CMISFTSQueryParser.buildFTS(ftsExpression, factory, functionEvaluationContext, + selector, columnMap, options.getDefaultFieldName()); + } else + { + ftsConstraint = FTSQueryParser.buildFTS(ftsExpression, factory, functionEvaluationContext, selector, + columnMap, mode, defaultFieldConnective, null, options.getDefaultFieldName()); + } + ftsConstraint.setBoost(1000.0f); + hasContains = true; + return ftsConstraint; + case CMISParser.PRED_IN: + functionName = In.NAME; + function = factory.getFunction(functionName); + functionArguments = new LinkedHashMap(); + argNode = (CommonTree) predicateNode.getChild(0); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(In.ARG_MODE), factory, selectors, + columnMap, false); + functionArguments.put(arg.getName(), arg); + argNode = (CommonTree) predicateNode.getChild(1); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(In.ARG_PROPERTY), factory, selectors, + columnMap, false); + functionArguments.put(arg.getName(), arg); + argNode = (CommonTree) predicateNode.getChild(2); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(In.ARG_LIST), factory, selectors, + columnMap, false); + functionArguments.put(arg.getName(), arg); + arg = factory.createLiteralArgument(In.ARG_NOT, DataTypeDefinition.BOOLEAN, + (predicateNode.getChildCount() > 3)); + functionArguments.put(arg.getName(), arg); + checkPredicateConditionsForIn(functionArguments, functionEvaluationContext, columnMap); + return factory.createFunctionalConstraint(function, functionArguments); + case CMISParser.PRED_LIKE: + functionName = Like.NAME; + function = factory.getFunction(functionName); + functionArguments = new LinkedHashMap(); + argNode = (CommonTree) predicateNode.getChild(0); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(Like.ARG_PROPERTY), factory, selectors, + columnMap, false); + functionArguments.put(arg.getName(), arg); + argNode = (CommonTree) predicateNode.getChild(1); + arg = getFunctionArgument(argNode, function.getArgumentDefinition(Like.ARG_EXP), factory, selectors, + columnMap, true); + functionArguments.put(arg.getName(), arg); + arg = factory.createLiteralArgument(Like.ARG_NOT, DataTypeDefinition.BOOLEAN, + (predicateNode.getChildCount() > 2)); + functionArguments.put(arg.getName(), arg); + checkPredicateConditionsForLike(functionArguments, functionEvaluationContext, columnMap); + return factory.createFunctionalConstraint(function, functionArguments); + default: + return null; + } + } + + private void checkPredicateConditionsForIn(Map functionArguments, + FunctionEvaluationContext functionEvaluationContext, Map columnMap) + { + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + PropertyArgument propertyArgument = (PropertyArgument) functionArguments.get(In.ARG_PROPERTY); + LiteralArgument modeArgument = (LiteralArgument) functionArguments.get(In.ARG_MODE); + String modeString = DefaultTypeConverter.INSTANCE.convert(String.class, + modeArgument.getValue(functionEvaluationContext)); + PredicateMode mode = PredicateMode.valueOf(modeString); + String propertyName = propertyArgument.getPropertyName(); + + Column column = columnMap.get(propertyName); + if (column != null) + { + // check for function type + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + propertyName = arg.getPropertyName(); + } else + { + throw new CmisInvalidArgumentException("Complex column reference not supoprted in LIKE " + + propertyName); + } + } + + boolean isMultiValued = functionEvaluationContext.isMultiValued(propertyName); + + switch (mode) + { + case ANY: + if (isMultiValued) + { + break; + } else + { + throw new QueryModelException("Predicate mode " + PredicateMode.ANY + + " is not supported for IN and single valued properties"); + } + case SINGLE_VALUED_PROPERTY: + if (isMultiValued) + { + throw new QueryModelException("Predicate mode " + PredicateMode.SINGLE_VALUED_PROPERTY + + " is not supported for IN and multi-valued properties"); + } else + { + break; + } + default: + throw new QueryModelException("Unsupported predicate mode " + mode); + } + + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(propertyName); + if (propDef.getPropertyDefinition().getPropertyType() == PropertyType.BOOLEAN) + { + throw new QueryModelException("In is not supported for properties of type Boolean"); + } + } + + } + + private void checkPredicateConditionsForComparisons(Function function, Map functionArguments, + FunctionEvaluationContext functionEvaluationContext, Map columnMap) + { + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + ((BaseComparison) function).setPropertyAndStaticArguments(functionArguments); + String propertyName = ((BaseComparison) function).getPropertyName(); + LiteralArgument modeArgument = (LiteralArgument) functionArguments.get(BaseComparison.ARG_MODE); + String modeString = DefaultTypeConverter.INSTANCE.convert(String.class, + modeArgument.getValue(functionEvaluationContext)); + PredicateMode mode = PredicateMode.valueOf(modeString); + + Column column = columnMap.get(propertyName); + if (column != null) + { + // check for function type + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + propertyName = arg.getPropertyName(); + } else + { + throw new CmisInvalidArgumentException("Complex column reference not supoprted in LIKE " + + propertyName); + } + } + + boolean isMultiValued = functionEvaluationContext.isMultiValued(propertyName); + + switch (mode) + { + case ANY: + if (isMultiValued) + { + if (function.getName().equals(Equals.NAME)) + { + break; + } else + { + throw new QueryModelException("Predicate mode " + PredicateMode.ANY + " is only supported for " + + Equals.NAME + " (and multi-valued properties)."); + } + } else + { + throw new QueryModelException("Predicate mode " + PredicateMode.ANY + " is not supported for " + + function.getName() + " and single valued properties"); + } + case SINGLE_VALUED_PROPERTY: + if (isMultiValued) + { + throw new QueryModelException("Predicate mode " + PredicateMode.SINGLE_VALUED_PROPERTY + + " is not supported for " + function.getName() + " and multi-valued properties"); + } else + { + break; + } + default: + throw new QueryModelException("Unsupported predicate mode " + mode); + } + + // limit support for ID and Boolean + + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(propertyName); + if (propDef.getPropertyDefinition().getPropertyType() == PropertyType.ID) + { + if (function.getName().equals(Equals.NAME) || function.getName().equals(NotEquals.NAME)) + { + return; + } else + { + throw new QueryModelException("Comparison " + function.getName() + + " is not supported for properties of type ID"); + } + } else if (propDef.getPropertyDefinition().getPropertyType() == PropertyType.BOOLEAN) + { + if (function.getName().equals(Equals.NAME)) + { + return; + } else + { + throw new QueryModelException("Comparison " + function.getName() + + " is not supported for properties of type Boolean"); + } + } + } + + } + + private void checkPredicateConditionsForLike(Map functionArguments, + FunctionEvaluationContext functionEvaluationContext, Map columnMap) + { + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + PropertyArgument propertyArgument = (PropertyArgument) functionArguments.get(Like.ARG_PROPERTY); + + boolean isMultiValued = functionEvaluationContext.isMultiValued(propertyArgument.getPropertyName()); + + if (isMultiValued) + { + throw new QueryModelException("Like is not supported for multi-valued properties"); + } + + String cmisPropertyName = propertyArgument.getPropertyName(); + + Column column = columnMap.get(cmisPropertyName); + if (column != null) + { + // check for function type + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + cmisPropertyName = arg.getPropertyName(); + } else + { + throw new CmisInvalidArgumentException("Complex column reference not supoprted in LIKE " + + cmisPropertyName); + } + } + + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(cmisPropertyName); + if (propDef.getPropertyDefinition().getPropertyType() != PropertyType.STRING) + { + throw new CmisInvalidArgumentException("LIKE is only supported against String types" + cmisPropertyName); + } + } + } + + /** + * @param queryNode + * @param factory + * @param selectors + * @return + */ + private ArrayList buildOrderings(CommonTree queryNode, QueryModelFactory factory, + Map selectors, List columns) + { + ArrayList orderings = new ArrayList(); + CommonTree orderNode = (CommonTree) queryNode.getFirstChildWithType(CMISParser.ORDER); + if (orderNode != null) + { + for (int i = 0; i < orderNode.getChildCount(); i++) + { + CommonTree current = (CommonTree) orderNode.getChild(i); + + CommonTree columnRefNode = (CommonTree) current.getFirstChildWithType(CMISParser.COLUMN_REF); + if (columnRefNode != null) + { + String columnName = columnRefNode.getChild(0).getText(); + String qualifier = ""; + if (columnRefNode.getChildCount() > 1) + { + qualifier = columnRefNode.getChild(1).getText(); + } + + Order order = Order.ASCENDING; + + if (current.getChild(1).getType() == CMISParser.DESC) + { + order = Order.DESCENDING; + } + + Column orderColumn = null; + + if (qualifier.length() == 0) + { + Column match = null; + for (Column column : columns) + { + if (column.getAlias().equals(columnName)) + { + match = column; + break; + } + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + String propertyName = arg.getPropertyName(); + if (propertyName.equals(columnName)) + { + match = column; + break; + } + } + } + // in strict mode the ordered column must be selected + if ((options.getQueryMode() == CMISQueryMode.CMS_STRICT) && (match == null)) + { + throw new CmisInvalidArgumentException("Ordered column is not selected: " + qualifier + "." + + columnName); + } + if (match == null) + { + + Selector selector = selectors.get(qualifier); + if (selector == null) + { + if ((qualifier.equals("")) && (selectors.size() == 1)) + { + selector = selectors.get(selectors.keySet().iterator().next()); + } else + { + throw new CmisInvalidArgumentException("No selector for " + qualifier); + } + } + + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), + BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " + + selector.getAlias()); + } + PropertyDefintionWrapper propDef = cmisDictionaryService + .findPropertyByQueryName(columnName); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Invalid column for " + + typeDef.getTypeDefinition(false).getQueryName() + "." + columnName); + } + + // Check column/property applies to selector/type + + if (typeDef.getPropertyById(propDef.getPropertyId()) == null) + { + throw new CmisInvalidArgumentException("Invalid column for " + + typeDef.getTypeDefinition(false).getQueryName() + "." + columnName); + } + + // check there is a matching selector + + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + boolean found = false; + for (Column column : columns) + { + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument pa = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + if (pa.getPropertyName().equals(propDef.getPropertyId())) + { + found = true; + break; + } + } + } + if (!found) + { + throw new CmisInvalidArgumentException("Ordered column is not selected: " + + qualifier + "." + columnName); + } + } + + Function function = factory.getFunction(PropertyAccessor.NAME); + Argument arg = factory.createPropertyArgument(PropertyAccessor.ARG_PROPERTY, propDef + .getPropertyDefinition().isQueryable(), propDef.getPropertyDefinition() + .isOrderable(), selector.getAlias(), propDef.getPropertyId()); + Map functionArguments = new LinkedHashMap(); + functionArguments.put(arg.getName(), arg); + + String alias = (selector.getAlias().length() > 0) ? selector.getAlias() + "." + + propDef.getPropertyId() : propDef.getPropertyId(); + + match = factory.createColumn(function, functionArguments, alias); + } + + orderColumn = match; + } else + { + Selector selector = selectors.get(qualifier); + if (selector == null) + { + if ((qualifier.equals("")) && (selectors.size() == 1)) + { + selector = selectors.get(selectors.keySet().iterator().next()); + } else + { + throw new CmisInvalidArgumentException("No selector for " + qualifier); + } + } + + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), + BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " + + selector.getAlias()); + } + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(columnName); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Invalid column for " + + typeDef.getTypeDefinition(false).getQueryName() + "." + columnName + + " selector alias " + selector.getAlias()); + } + + // check there is a matching selector + + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + boolean found = false; + for (Column column : columns) + { + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument pa = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + if (pa.getPropertyName().equals(propDef.getPropertyId())) + { + found = true; + break; + } + } + } + if (!found) + { + throw new CmisInvalidArgumentException("Ordered column is not selected: " + qualifier + + "." + columnName); + } + } + + Function function = factory.getFunction(PropertyAccessor.NAME); + Argument arg = factory.createPropertyArgument(PropertyAccessor.ARG_PROPERTY, propDef + .getPropertyDefinition().isQueryable(), propDef.getPropertyDefinition().isOrderable(), + selector.getAlias(), propDef.getPropertyId()); + Map functionArguments = new LinkedHashMap(); + functionArguments.put(arg.getName(), arg); + + String alias = (selector.getAlias().length() > 0) ? selector.getAlias() + "." + + propDef.getPropertyId() : propDef.getPropertyId(); + + orderColumn = factory.createColumn(function, functionArguments, alias); + } + + if (!orderColumn.isOrderable() || !orderColumn.isQueryable()) + { + throw new CmisInvalidArgumentException("Ordering is not support for " + orderColumn.getAlias()); + } + + Ordering ordering = factory.createOrdering(orderColumn, order); + orderings.add(ordering); + + } + } + } + return orderings; + } + + @SuppressWarnings("unchecked") + private ArrayList buildColumns(CommonTree queryNode, QueryModelFactory factory, + Map selectors, String query) + { + ArrayList columns = new ArrayList(); + CommonTree starNode = (CommonTree) queryNode.getFirstChildWithType(CMISParser.ALL_COLUMNS); + if (starNode != null) + { + for (Selector selector : selectors.values()) + { + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), validScopes); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " + selector.getAlias()); + } + Collection propDefs = typeDef.getProperties(); + for (PropertyDefintionWrapper definition : propDefs) + { + Function function = factory.getFunction(PropertyAccessor.NAME); + Argument arg = factory.createPropertyArgument(PropertyAccessor.ARG_PROPERTY, definition + .getPropertyDefinition().isQueryable(), definition.getPropertyDefinition().isOrderable(), + selector.getAlias(), definition.getPropertyId()); + Map functionArguments = new LinkedHashMap(); + functionArguments.put(arg.getName(), arg); + String alias = (selector.getAlias().length() > 0) ? selector.getAlias() + "." + + definition.getPropertyId() : definition.getPropertyId(); + Column column = factory.createColumn(function, functionArguments, alias); + columns.add(column); + } + } + } + + CommonTree columnsNode = (CommonTree) queryNode.getFirstChildWithType(CMISParser.COLUMNS); + if (columnsNode != null) + { + for (CommonTree columnNode : (List) columnsNode.getChildren()) + { + if (columnNode.getType() == CMISParser.ALL_COLUMNS) + { + String qualifier = columnNode.getChild(0).getText(); + Selector selector = selectors.get(qualifier); + if (selector == null) + { + if ((qualifier.equals("")) && (selectors.size() == 1)) + { + selector = selectors.get(selectors.keySet().iterator().next()); + } else + { + throw new CmisInvalidArgumentException("No selector for " + qualifier + " in " + qualifier + + ".*"); + } + } + + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), + validScopes); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " + + selector.getAlias()); + } + Collection propDefs = typeDef.getProperties(); + for (PropertyDefintionWrapper definition : propDefs) + { + Function function = factory.getFunction(PropertyAccessor.NAME); + Argument arg = factory.createPropertyArgument(PropertyAccessor.ARG_PROPERTY, definition + .getPropertyDefinition().isQueryable(), definition.getPropertyDefinition() + .isOrderable(), selector.getAlias(), definition.getPropertyId()); + Map functionArguments = new LinkedHashMap(); + functionArguments.put(arg.getName(), arg); + String alias = (selector.getAlias().length() > 0) ? selector.getAlias() + "." + + definition.getPropertyId() : definition.getPropertyId(); + Column column = factory.createColumn(function, functionArguments, alias); + columns.add(column); + } + } + + if (columnNode.getType() == CMISParser.COLUMN) + { + CommonTree columnRefNode = (CommonTree) columnNode.getFirstChildWithType(CMISParser.COLUMN_REF); + if (columnRefNode != null) + { + String columnName = columnRefNode.getChild(0).getText(); + String qualifier = ""; + if (columnRefNode.getChildCount() > 1) + { + qualifier = columnRefNode.getChild(1).getText(); + } + Selector selector = selectors.get(qualifier); + if (selector == null) + { + if ((qualifier.equals("")) && (selectors.size() == 1)) + { + selector = selectors.get(selectors.keySet().iterator().next()); + } else + { + throw new CmisInvalidArgumentException("No selector for " + qualifier); + } + } + + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), + validScopes); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " + + selector.getAlias()); + } + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(columnName); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Invalid column for " + + typeDef.getTypeDefinition(false).getQueryName() + " " + columnName); + } + + // Check column/property applies to selector/type + + if (typeDef.getPropertyById(propDef.getPropertyId()) == null) + { + throw new CmisInvalidArgumentException("Invalid column for " + + typeDef.getTypeDefinition(false).getQueryName() + "." + columnName); + } + + Function function = factory.getFunction(PropertyAccessor.NAME); + Argument arg = factory.createPropertyArgument(PropertyAccessor.ARG_PROPERTY, propDef + .getPropertyDefinition().isQueryable(), propDef.getPropertyDefinition().isOrderable(), + selector.getAlias(), propDef.getPropertyId()); + Map functionArguments = new LinkedHashMap(); + functionArguments.put(arg.getName(), arg); + + String alias = (selector.getAlias().length() > 0) ? selector.getAlias() + "." + + propDef.getPropertyId() : propDef.getPropertyId(); + if (columnNode.getChildCount() > 1) + { + alias = columnNode.getChild(1).getText(); + } + + Column column = factory.createColumn(function, functionArguments, alias); + columns.add(column); + + } + + CommonTree functionNode = (CommonTree) columnNode.getFirstChildWithType(CMISParser.FUNCTION); + if (functionNode != null) + { + CommonTree functionNameNode = (CommonTree) functionNode.getChild(0); + Function function = factory.getFunction(functionNameNode.getText()); + if (function == null) + { + throw new CmisInvalidArgumentException("Unknown function: " + functionNameNode.getText()); + } + Collection definitions = function.getArgumentDefinitions().values(); + Map functionArguments = new LinkedHashMap(); + + int childIndex = 2; + for (ArgumentDefinition definition : definitions) + { + if (functionNode.getChildCount() > childIndex + 1) + { + CommonTree argNode = (CommonTree) functionNode.getChild(childIndex++); + Argument arg = getFunctionArgument(argNode, definition, factory, selectors, null, + function.getName().equals(Like.NAME)); + functionArguments.put(arg.getName(), arg); + } else + { + if (definition.isMandatory()) + { + // throw new + // CmisInvalidArgumentException("Insufficient + // aruments + // for function " + + // ((CommonTree) + // functionNode.getChild(0)).getText() ); + break; + } else + { + // ok + } + } + } + + CommonTree rparenNode = (CommonTree) functionNode.getChild(functionNode.getChildCount() - 1); + + int start = getStringPosition(query, functionNode.getLine(), + functionNode.getCharPositionInLine()); + int end = getStringPosition(query, rparenNode.getLine(), rparenNode.getCharPositionInLine()); + + if (function.getName().equals(Score.NAME)) + { + hasScore = true; + } + + String alias; + if (function.getName().equals(Score.NAME)) + { + alias = "SEARCH_SCORE"; + // check no args + if (functionNode.getChildCount() > 3) + { + throw new CmisInvalidArgumentException( + "The function SCORE() is not allowed any arguments"); + } + } else + { + alias = query.substring(start, end + 1); + } + if (columnNode.getChildCount() > 1) + { + alias = columnNode.getChild(1).getText(); + } + + Column column = factory.createColumn(function, functionArguments, alias); + columns.add(column); + } + } + } + } + + return columns; + } + + /** + * @param query + * @param line + * @param charPositionInLine + * @return + */ + private int getStringPosition(String query, int line, int charPositionInLine) + { + StringTokenizer tokenizer = new StringTokenizer(query, "\n\r\f"); + String[] lines = new String[tokenizer.countTokens()]; + int i = 0; + while (tokenizer.hasMoreElements()) + { + lines[i++] = tokenizer.nextToken(); + } + + int position = 0; + for (i = 0; i < line - 1; i++) + { + position += lines[i].length(); + position++; + } + return position + charPositionInLine; + } + + private Argument getFunctionArgument(CommonTree argNode, ArgumentDefinition definition, QueryModelFactory factory, + Map selectors, Map columnMap, boolean inLike) + { + if (argNode.getType() == CMISParser.COLUMN_REF) + { + PropertyArgument arg = buildColumnReference(definition.getName(), argNode, factory, selectors, columnMap); + if (!arg.isQueryable()) + { + throw new CmisInvalidArgumentException("Column refers to unqueryable property " + arg.getPropertyName()); + } + if (!selectors.containsKey(arg.getSelector())) + { + throw new CmisInvalidArgumentException("No table with alias " + arg.getSelector()); + } + return arg; + } else if (argNode.getType() == CMISParser.ID) + { + String id = argNode.getText(); + if (selectors.containsKey(id)) + { + SelectorArgument arg = factory.createSelectorArgument(definition.getName(), id); + if (!arg.isQueryable()) + { + throw new CmisInvalidArgumentException("Selector is not queryable " + arg.getSelector()); + } + return arg; + } else + { + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(id); + if (propDef == null || !propDef.getPropertyDefinition().isQueryable()) + { + throw new CmisInvalidArgumentException("Column refers to unqueryable property " + + definition.getName()); + } + PropertyArgument arg = factory.createPropertyArgument(definition.getName(), propDef + .getPropertyDefinition().isQueryable(), propDef.getPropertyDefinition().isOrderable(), "", + propDef.getPropertyId()); + return arg; + } + } else if (argNode.getType() == CMISParser.PARAMETER) + { + ParameterArgument arg = factory.createParameterArgument(definition.getName(), argNode.getText()); + if (!arg.isQueryable()) + { + throw new CmisInvalidArgumentException("Parameter is not queryable " + arg.getParameterName()); + } + return arg; + } else if (argNode.getType() == CMISParser.NUMERIC_LITERAL) + { + CommonTree literalNode = (CommonTree) argNode.getChild(0); + if (literalNode.getType() == CMISParser.FLOATING_POINT_LITERAL) + { + QName type = DataTypeDefinition.DOUBLE; + Number value = Double.parseDouble(literalNode.getText()); + if (value.floatValue() == value.doubleValue()) + { + type = DataTypeDefinition.FLOAT; + value = Float.valueOf(value.floatValue()); + } + LiteralArgument arg = factory.createLiteralArgument(definition.getName(), type, value); + return arg; + } else if (literalNode.getType() == CMISParser.DECIMAL_INTEGER_LITERAL) + { + QName type = DataTypeDefinition.LONG; + Number value = Long.parseLong(literalNode.getText()); + if (value.intValue() == value.longValue()) + { + type = DataTypeDefinition.INT; + value = Integer.valueOf(value.intValue()); + } + LiteralArgument arg = factory.createLiteralArgument(definition.getName(), type, value); + return arg; + } else + { + throw new CmisInvalidArgumentException("Invalid numeric literal " + literalNode.getText()); + } + } else if (argNode.getType() == CMISParser.STRING_LITERAL) + { + String text = argNode.getChild(0).getText(); + text = text.substring(1, text.length() - 1); + text = unescape(text, inLike ? EscapeMode.LIKE : EscapeMode.LITERAL); + LiteralArgument arg = factory.createLiteralArgument(definition.getName(), DataTypeDefinition.TEXT, text); + return arg; + } else if (argNode.getType() == CMISParser.DATETIME_LITERAL) + { + String text = argNode.getChild(0).getText(); + text = text.substring(1, text.length() - 1); + StringBuilder builder = new StringBuilder(); + if (text.endsWith("Z")) + { + builder.append(text.substring(0, text.length() - 1)); + builder.append("+0000"); + } else + { + if (text.charAt(text.length() - 3) != ':') + { + throw new CmisInvalidArgumentException("Invalid datetime literal " + text); + } + // remove TZ colon .... + builder.append(text.substring(0, text.length() - 3)); + builder.append(text.substring(text.length() - 2, text.length())); + } + text = builder.toString(); + + SimpleDateFormat df = CachingDateFormat.getCmisSqlDatetimeFormat(); + Date date; + try + { + date = df.parse(text); + } catch (ParseException e) + { + throw new CmisInvalidArgumentException("Invalid datetime literal " + text); + } + // Convert back :-) + String alfrescoDate = DefaultTypeConverter.INSTANCE.convert(String.class, date); + LiteralArgument arg = factory.createLiteralArgument(definition.getName(), DataTypeDefinition.TEXT, + alfrescoDate); + return arg; + } else if (argNode.getType() == CMISParser.BOOLEAN_LITERAL) + { + String text = argNode.getChild(0).getText(); + if (text.equalsIgnoreCase("TRUE") || text.equalsIgnoreCase("FALSE")) + { + LiteralArgument arg = factory + .createLiteralArgument(definition.getName(), DataTypeDefinition.TEXT, text); + return arg; + } else + { + throw new CmisInvalidArgumentException("Invalid boolean literal " + text); + } + + } else if (argNode.getType() == CMISParser.LIST) + { + ArrayList arguments = new ArrayList(); + for (int i = 0; i < argNode.getChildCount(); i++) + { + CommonTree arg = (CommonTree) argNode.getChild(i); + arguments.add(getFunctionArgument(arg, definition, factory, selectors, columnMap, inLike)); + } + ListArgument arg = factory.createListArgument(definition.getName(), arguments); + if (!arg.isQueryable()) + { + throw new CmisInvalidArgumentException("Not all members of the list are queryable"); + } + return arg; + } else if (argNode.getType() == CMISParser.ANY) + { + LiteralArgument arg = factory.createLiteralArgument(definition.getName(), DataTypeDefinition.TEXT, + argNode.getText()); + return arg; + } else if (argNode.getType() == CMISParser.SINGLE_VALUED_PROPERTY) + { + LiteralArgument arg = factory.createLiteralArgument(definition.getName(), DataTypeDefinition.TEXT, + argNode.getText()); + return arg; + } else if (argNode.getType() == CMISParser.NOT) + { + LiteralArgument arg = factory.createLiteralArgument(definition.getName(), DataTypeDefinition.TEXT, + argNode.getText()); + return arg; + } else if (argNode.getType() == CMISParser.FUNCTION) + { + CommonTree functionNameNode = (CommonTree) argNode.getChild(0); + Function function = factory.getFunction(functionNameNode.getText()); + if (function == null) + { + throw new CmisInvalidArgumentException("Unknown function: " + functionNameNode.getText()); + } + Collection definitions = function.getArgumentDefinitions().values(); + Map functionArguments = new LinkedHashMap(); + + int childIndex = 2; + for (ArgumentDefinition currentDefinition : definitions) + { + if (argNode.getChildCount() > childIndex + 1) + { + CommonTree currentArgNode = (CommonTree) argNode.getChild(childIndex++); + Argument arg = getFunctionArgument(currentArgNode, currentDefinition, factory, selectors, + columnMap, inLike); + functionArguments.put(arg.getName(), arg); + } else + { + if (definition.isMandatory()) + { + // throw new CmisInvalidArgumentException("Insufficient + // aruments + // for function " + ((CommonTree) + // functionNode.getChild(0)).getText() ); + break; + } else + { + // ok + } + } + } + FunctionArgument arg = factory.createFunctionArgument(definition.getName(), function, functionArguments); + if (!arg.isQueryable()) + { + throw new CmisInvalidArgumentException("Not all function arguments refer to orderable arguments: " + + arg.getFunction().getName()); + } + return arg; + } else + { + throw new CmisInvalidArgumentException("Invalid function argument " + argNode.getText()); + } + } + + @SuppressWarnings("unchecked") + private Source buildSource(CommonTree source, CapabilityJoin joinSupport, QueryModelFactory factory) + { + if (source.getChildCount() == 1) + { + // single table reference + CommonTree singleTableNode = (CommonTree) source.getChild(0); + if (singleTableNode.getType() == CMISParser.TABLE) + { + if (joinSupport == CapabilityJoin.NONE) + { + throw new UnsupportedOperationException("Joins are not supported"); + } + CommonTree tableSourceNode = (CommonTree) singleTableNode.getFirstChildWithType(CMISParser.SOURCE); + return buildSource(tableSourceNode, joinSupport, factory); + + } + if (singleTableNode.getType() != CMISParser.TABLE_REF) + { + throw new CmisInvalidArgumentException("Expecting TABLE_REF token but found " + + singleTableNode.getText()); + } + String tableName = singleTableNode.getChild(0).getText(); + String alias = ""; + if (singleTableNode.getChildCount() > 1) + { + alias = singleTableNode.getChild(1).getText(); + } + + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeByQueryName(tableName); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type is unsupported in query: " + tableName); + } + if (typeDef.getBaseTypeId() != BaseTypeId.CMIS_POLICY) + { + if (!typeDef.getTypeDefinition(false).isQueryable()) + { + throw new CmisInvalidArgumentException("Type is not queryable " + tableName + " -> " + + typeDef.getTypeId()); + } + } + return factory.createSelector(typeDef.getAlfrescoClass(), alias); + } else + { + if (joinSupport == CapabilityJoin.NONE) + { + throw new UnsupportedOperationException("Joins are not supported"); + } + CommonTree singleTableNode = (CommonTree) source.getChild(0); + if (singleTableNode.getType() != CMISParser.TABLE_REF) + { + throw new CmisInvalidArgumentException("Expecting TABLE_REF token but found " + + singleTableNode.getText()); + } + String tableName = singleTableNode.getChild(0).getText(); + String alias = ""; + if (singleTableNode.getChildCount() == 2) + { + alias = singleTableNode.getChild(1).getText(); + } + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeByQueryName(tableName); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type is unsupported in query " + tableName); + } + if (typeDef.getBaseTypeId() != BaseTypeId.CMIS_POLICY) + { + if (!typeDef.getTypeDefinition(false).isQueryable()) + { + throw new CmisInvalidArgumentException("Type is not queryable " + tableName + " -> " + + typeDef.getTypeId()); + } + } + + Source lhs = factory.createSelector(typeDef.getAlfrescoClass(), alias); + + List list = (List) (source.getChildren()); + for (CommonTree joinNode : list) + { + if (joinNode.getType() == CMISParser.JOIN) + { + CommonTree rhsSource = (CommonTree) joinNode.getFirstChildWithType(CMISParser.SOURCE); + Source rhs = buildSource(rhsSource, joinSupport, factory); + + JoinType joinType = JoinType.INNER; + CommonTree joinTypeNode = (CommonTree) joinNode.getFirstChildWithType(CMISParser.LEFT); + if (joinTypeNode != null) + { + joinType = JoinType.LEFT; + } + + if ((joinType == JoinType.LEFT) && (joinSupport == CapabilityJoin.INNERONLY)) + { + throw new UnsupportedOperationException("Outer joins are not supported"); + } + + Constraint joinCondition = null; + CommonTree joinConditionNode = (CommonTree) joinNode.getFirstChildWithType(CMISParser.ON); + if (joinConditionNode != null) + { + PropertyArgument arg1 = buildColumnReference(Equals.ARG_LHS, + (CommonTree) joinConditionNode.getChild(0), factory, null, null); + if (!lhs.getSelectors().containsKey(arg1.getSelector()) + && !rhs.getSelectors().containsKey(arg1.getSelector())) + { + throw new CmisInvalidArgumentException("No table with alias " + arg1.getSelector()); + } + CommonTree functionNameNode = (CommonTree) joinConditionNode.getChild(1); + if (functionNameNode.getType() != CMISParser.EQUALS) + { + throw new CmisInvalidArgumentException("Only Equi-join is supported " + + functionNameNode.getText()); + } + Function function = factory.getFunction(Equals.NAME); + if (function == null) + { + throw new CmisInvalidArgumentException("Unknown function: " + functionNameNode.getText()); + } + PropertyArgument arg2 = buildColumnReference(Equals.ARG_RHS, + (CommonTree) joinConditionNode.getChild(2), factory, null, null); + if (!lhs.getSelectors().containsKey(arg2.getSelector()) + && !rhs.getSelectors().containsKey(arg2.getSelector())) + { + throw new CmisInvalidArgumentException("No table with alias " + arg2.getSelector()); + } + Map functionArguments = new LinkedHashMap(); + functionArguments.put(arg1.getName(), arg1); + functionArguments.put(arg2.getName(), arg2); + joinCondition = factory.createFunctionalConstraint(function, functionArguments); + } + + Source join = factory.createJoin(lhs, rhs, joinType, joinCondition); + lhs = join; + } + } + + return lhs; + + } + } + + public PropertyArgument buildColumnReference(String argumentName, CommonTree columnReferenceNode, + QueryModelFactory factory, Map selectors, Map columnMap) + { + String cmisPropertyName = columnReferenceNode.getChild(0).getText(); + String qualifier = ""; + if (columnReferenceNode.getChildCount() > 1) + { + qualifier = columnReferenceNode.getChild(1).getText(); + } + + if ((qualifier == "") && (columnMap != null)) + { + Column column = columnMap.get(cmisPropertyName); + if (column != null) + { + // check for function type + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + cmisPropertyName = arg.getPropertyName(); + qualifier = arg.getSelector(); + } else + { + // TODO: should be able to return non property arguments + // The implementation should throw out what it can not + // support at build time. + throw new CmisInvalidArgumentException( + "Complex column reference unsupported (only direct column references are currently supported) " + + cmisPropertyName); + } + } + } + + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(cmisPropertyName); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Unknown column/property " + cmisPropertyName); + } + + if (selectors != null) + { + Selector selector = selectors.get(qualifier); + if (selector == null) + { + if ((qualifier.equals("")) && (selectors.size() == 1)) + { + selector = selectors.get(selectors.keySet().iterator().next()); + } else + { + throw new CmisInvalidArgumentException("No selector for " + qualifier); + } + } + + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), validScopes); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " + selector.getAlias()); + } + + // Check column/property applies to selector/type + + if (typeDef.getPropertyById(propDef.getPropertyId()) == null) + { + throw new CmisInvalidArgumentException("Invalid column for " + + typeDef.getTypeDefinition(false).getQueryName() + "." + cmisPropertyName); + } + } + + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + if (!propDef.getPropertyDefinition().isQueryable()) + { + throw new CmisInvalidArgumentException("Column is not queryable " + qualifier + "." + cmisPropertyName); + } + } + return factory.createPropertyArgument(argumentName, propDef.getPropertyDefinition().isQueryable(), propDef + .getPropertyDefinition().isOrderable(), qualifier, propDef.getPropertyId()); + } + + private String unescape(String string, EscapeMode mode) + { + StringBuilder builder = new StringBuilder(string.length()); + + boolean lastWasEscape = false; + + for (int i = 0; i < string.length(); i++) + { + char c = string.charAt(i); + if (lastWasEscape) + { + + // Need to keep escaping for like as we have the same escaping + if (mode == EscapeMode.LIKE) + { + // Like does its own escaping - so pass through \ % and _ + if (c == '\'') + { + builder.append(c); + } else if (c == '%') + { + builder.append('\\'); + builder.append(c); + } else if (c == '_') + { + builder.append('\\'); + builder.append(c); + } else if (c == '\\') + { + builder.append('\\'); + builder.append(c); + } else + { + throw new UnsupportedOperationException("Unsupported escape pattern in <" + string + + "> at position " + i); + } + } else if (mode == EscapeMode.CONTAINS) + { + if (options.getQueryMode() == CMISQueryMode.CMS_STRICT) + { + if (c == '\'') + { + builder.append(c); + } else if (c == '\\') + { + builder.append('\\'); + builder.append(c); + } + + else + { + throw new UnsupportedOperationException("Unsupported escape pattern in <" + string + + "> at position " + i); + } + } else + { + if (c == '\'') + { + builder.append(c); + } else + { + builder.append(c); + } + } + } else if (mode == EscapeMode.LITERAL) + { + if (c == '\'') + { + builder.append(c); + } else if (c == '\\') + { + builder.append(c); + } else + { + throw new UnsupportedOperationException("Unsupported escape pattern in <" + string + + "> at position " + i); + + } + } else + { + throw new UnsupportedOperationException("Unsupported escape pattern in <" + string + + "> at position " + i); + + } + lastWasEscape = false; + } else + { + if (c == '\\') + { + lastWasEscape = true; + } else + { + builder.append(c); + } + } + } + if (lastWasEscape) + { + throw new FTSQueryException("Escape character at end of string " + string); + } + + return builder.toString(); + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISQueryService.java b/source/java/org/alfresco/opencmis/search/CMISQueryService.java new file mode 100644 index 0000000000..3185c3c4b0 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISQueryService.java @@ -0,0 +1,20 @@ +package org.alfresco.opencmis.search; + +import org.alfresco.service.cmr.repository.StoreRef; +import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; +import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; + +public interface CMISQueryService +{ + CMISResultSet query(CMISQueryOptions options); + + CMISResultSet query(String query, StoreRef storeRef); + + boolean getPwcSearchable(); + + boolean getAllVersionsSearchable(); + + CapabilityQuery getQuerySupport(); + + CapabilityJoin getJoinSupport(); +} \ No newline at end of file diff --git a/source/java/org/alfresco/opencmis/search/CMISQueryServiceImpl.java b/source/java/org/alfresco/opencmis/search/CMISQueryServiceImpl.java new file mode 100644 index 0000000000..edbe7befa1 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISQueryServiceImpl.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.repo.search.impl.querymodel.QueryEngine; +import org.alfresco.repo.search.impl.querymodel.QueryEngineResults; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.CapabilityJoin; +import org.apache.chemistry.opencmis.commons.enums.CapabilityQuery; + +/** + * @author andyh + */ +public class CMISQueryServiceImpl implements CMISQueryService +{ + private CMISDictionaryService cmisDictionaryService; + + private QueryEngine queryEngine; + + private NodeService nodeService; + + private DictionaryService alfrescoDictionaryService; + + public void setOpenCMISDictionaryService(CMISDictionaryService cmisDictionaryService) + { + this.cmisDictionaryService = cmisDictionaryService; + } + + /** + * @param queryEngine + * the queryEngine to set + */ + public void setQueryEngine(QueryEngine queryEngine) + { + this.queryEngine = queryEngine; + } + + /** + * @param nodeService + * the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param alfrescoDictionaryService + * the Alfresco Dictionary Service to set + */ + public void setAlfrescoDictionaryService(DictionaryService alfrescoDictionaryService) + { + this.alfrescoDictionaryService = alfrescoDictionaryService; + } + + public CMISResultSet query(CMISQueryOptions options) + { + CapabilityJoin joinSupport = getJoinSupport(); + if (options.getQueryMode() == CMISQueryOptions.CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS) + { + joinSupport = CapabilityJoin.INNERONLY; + } + + // TODO: Refactor to avoid duplication of valid scopes here and in + // CMISQueryParser + + BaseTypeId[] validScopes = (options.getQueryMode() == CMISQueryMode.CMS_STRICT) ? CmisFunctionEvaluationContext.STRICT_SCOPES + : CmisFunctionEvaluationContext.ALFRESCO_SCOPES; + CmisFunctionEvaluationContext functionContext = new CmisFunctionEvaluationContext(); + functionContext.setCmisDictionaryService(cmisDictionaryService); + functionContext.setNodeService(nodeService); + functionContext.setValidScopes(validScopes); + + CMISQueryParser parser = new CMISQueryParser(options, cmisDictionaryService, joinSupport); + Query query = parser.parse(queryEngine.getQueryModelFactory(), functionContext); + + QueryEngineResults results = queryEngine.executeQuery(query, options, functionContext); + Map wrapped = new HashMap(); + Map, ResultSet> map = results.getResults(); + for (Set group : map.keySet()) + { + ResultSet current = map.get(group); + for (String selector : group) + { + wrapped.put(selector, current); + } + } + LimitBy limitBy = null; + if ((null != results.getResults()) && !results.getResults().isEmpty() + && (null != results.getResults().values()) && !results.getResults().values().isEmpty()) + { + limitBy = results.getResults().values().iterator().next().getResultSetMetaData().getLimitedBy(); + } + CMISResultSet cmis = new CMISResultSet(wrapped, options, limitBy, nodeService, query, cmisDictionaryService, + alfrescoDictionaryService); + return cmis; + } + + public CMISResultSet query(String query, StoreRef storeRef) + { + CMISQueryOptions options = new CMISQueryOptions(query, storeRef); + return query(options); + } + + public boolean getPwcSearchable() + { + return true; + } + + public boolean getAllVersionsSearchable() + { + return false; + } + + public CapabilityQuery getQuerySupport() + { + return CapabilityQuery.BOTHCOMBINED; + } + + public CapabilityJoin getJoinSupport() + { + return CapabilityJoin.NONE; + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSet.java b/source/java/org/alfresco/opencmis/search/CMISResultSet.java new file mode 100644 index 0000000000..014f1769e4 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISResultSet.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.ResultSetSPI; + +/** + * @author andyh + */ +public class CMISResultSet implements ResultSetSPI, Serializable +{ + private static final long serialVersionUID = 2014688399588268994L; + + private Map wrapped; + + private LimitBy limitBy; + + CMISQueryOptions options; + + NodeService nodeService; + + Query query; + + CMISDictionaryService cmisDictionaryService; + + DictionaryService alfrescoDictionaryService; + + public CMISResultSet(Map wrapped, CMISQueryOptions options, LimitBy limitBy, + NodeService nodeService, Query query, CMISDictionaryService cmisDictionaryService, + DictionaryService alfrescoDictionaryService) + { + this.wrapped = wrapped; + this.options = options; + this.limitBy = limitBy; + this.nodeService = nodeService; + this.query = query; + this.cmisDictionaryService = cmisDictionaryService; + this.alfrescoDictionaryService = alfrescoDictionaryService; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#close() + */ + public void close() + { + // results sets can be used for more than one selector so we need to + // keep track of what we have closed + Set closed = new HashSet(); + for (ResultSet resultSet : wrapped.values()) + { + if (!closed.contains(resultSet)) + { + resultSet.close(); + closed.add(resultSet); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#getMetaData() + */ + public CMISResultSetMetaData getMetaData() + { + return new CMISResultSetMetaData(options, query, limitBy, cmisDictionaryService, alfrescoDictionaryService); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#getRow(int) + */ + public CMISResultSetRow getRow(int i) + { + return new CMISResultSetRow(this, i, getScores(i), nodeService, getNodeRefs(i), query, cmisDictionaryService); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#hasMore() + */ + public boolean hasMore() + { + for (ResultSet resultSet : wrapped.values()) + { + if (resultSet.hasMore()) + { + return true; + } + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#length() + */ + public int getLength() + { + for (ResultSet resultSet : wrapped.values()) + { + return resultSet.length(); + } + throw new IllegalStateException(); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSet#start() + */ + public int getStart() + { + return options.getSkipCount(); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Iterable#iterator() + */ + public Iterator iterator() + { + return new CMISResultSetRowIterator(this); + } + + private Map getNodeRefs(int i) + { + HashMap refs = new HashMap(); + for (String selector : wrapped.keySet()) + { + ResultSet rs = wrapped.get(selector); + refs.put(selector, rs.getNodeRef(i)); + } + return refs; + } + + private Map getScores(int i) + { + HashMap scores = new HashMap(); + for (String selector : wrapped.keySet()) + { + ResultSet rs = wrapped.get(selector); + scores.put(selector, Float.valueOf(rs.getScore(i))); + } + return scores; + } + + public ChildAssociationRef getChildAssocRef(int n) + { + NodeRef nodeRef = getNodeRef(n); + return nodeService.getPrimaryParent(nodeRef); + } + + public List getChildAssocRefs() + { + ArrayList cars = new ArrayList(length()); + for (ResultSetRow row : this) + { + cars.add(row.getChildAssocRef()); + } + return cars; + } + + public NodeRef getNodeRef(int n) + { + Map refs = getNodeRefs(n); + if (refs.size() == 1) + { + return refs.values().iterator().next(); + } else if (allNodeRefsEqual(refs)) + { + return refs.values().iterator().next(); + } else + { + throw new IllegalStateException("Ambiguous selector"); + } + } + + private boolean allNodeRefsEqual(Map selected) + { + NodeRef last = null; + for (NodeRef current : selected.values()) + { + if (last == null) + { + last = current; + } else + { + if (!last.equals(current)) + { + return false; + } + } + } + return true; + } + + public List getNodeRefs() + { + ArrayList nodeRefs = new ArrayList(length()); + for (ResultSetRow row : this) + { + nodeRefs.add(row.getNodeRef()); + } + return nodeRefs; + } + + public CMISResultSetMetaData getResultSetMetaData() + { + return getMetaData(); + } + + public float getScore(int n) + { + Map scores = getScores(n); + if (scores.size() == 1) + { + return scores.values().iterator().next(); + } else if (allScoresEqual(scores)) + { + return scores.values().iterator().next(); + } else + { + throw new IllegalStateException("Ambiguous selector"); + } + } + + private boolean allScoresEqual(Map scores) + { + Float last = null; + for (Float current : scores.values()) + { + if (last == null) + { + last = current; + } else + { + if (!last.equals(current)) + { + return false; + } + } + } + return true; + } + + public int length() + { + return getLength(); + } + + /** + * Bulk fetch results in the cache - not supported here + * + * @param bulkFetch + */ + public boolean setBulkFetch(boolean bulkFetch) + { + return false; + } + + /** + * Do we bulk fetch - not supported here + * + * @return - true if we do + */ + public boolean getBulkFetch() + { + return false; + } + + /** + * Set the bulk fetch size + * + * @param bulkFetchSize + */ + public int setBulkFetchSize(int bulkFetchSize) + { + return 0; + } + + /** + * Get the bulk fetch size. + * + * @return the fetch size + */ + public int getBulkFetchSize() + { + return 0; + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetColumn.java b/source/java/org/alfresco/opencmis/search/CMISResultSetColumn.java new file mode 100644 index 0000000000..7b81752ba3 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetColumn.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import org.alfresco.opencmis.dictionary.PropertyDefintionWrapper; +import org.alfresco.service.cmr.search.ResultSetColumn; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; + +/** + * @author andyh + * + */ +public class CMISResultSetColumn implements ResultSetColumn +{ + + private String name; + + private PropertyDefintionWrapper propertyDefinition; + + private PropertyType dataType; + + private QName alfrescoPropertyQName; + + private QName alfrescoDataTypeQName; + + CMISResultSetColumn(String name, PropertyDefintionWrapper propertyDefinition, PropertyType dataType, + QName alfrescoPropertyQName, QName alfrescoDataTypeQName) + { + this.name = name; + this.propertyDefinition = propertyDefinition; + this.dataType = dataType; + this.alfrescoPropertyQName = alfrescoPropertyQName; + this.alfrescoDataTypeQName = alfrescoDataTypeQName; + } + + public String getName() + { + return name; + } + + public PropertyDefintionWrapper getCMISPropertyDefinition() + { + return propertyDefinition; + } + + public PropertyType getCMISDataType() + { + return dataType; + } + + public QName getDataType() + { + return alfrescoDataTypeQName; + } + + public QName getPropertyType() + { + return alfrescoPropertyQName; + } +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetMetaData.java b/source/java/org/alfresco/opencmis/search/CMISResultSetMetaData.java new file mode 100644 index 0000000000..2894208a19 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetMetaData.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.PropertyDefintionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.repo.search.impl.querymodel.Column; +import org.alfresco.repo.search.impl.querymodel.PropertyArgument; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.repo.search.impl.querymodel.Selector; +import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.PermissionEvaluationMode; +import org.alfresco.service.cmr.search.ResultSetMetaData; +import org.alfresco.service.cmr.search.ResultSetType; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.opencmis.commons.enums.PropertyType; + +/** + * @author andyh + */ +public class CMISResultSetMetaData implements ResultSetMetaData +{ + private CMISQueryOptions options; + private SearchParameters searchParams; + private LimitBy limitBy; + + private Map columnMetaData; + + private Map selectorMetaData; + + public CMISResultSetMetaData(CMISQueryOptions options, Query query, LimitBy limitBy, + CMISDictionaryService cmisDictionaryService, DictionaryService alfrescoDictionaryService) + { + this.options = options; + this.searchParams = new SearchParameters(options); + this.limitBy = limitBy; + Map selectors = query.getSource().getSelectors(); + selectorMetaData = new LinkedHashMap(); + for (Selector selector : selectors.values()) + { + TypeDefinitionWrapper type = cmisDictionaryService.findTypeForClass(selector.getType()); + CMISResultSetSelector smd = new CMISResultSetSelector(selector.getAlias(), type); + selectorMetaData.put(smd.getName(), smd); + } + + columnMetaData = new LinkedHashMap(); + for (Column column : query.getColumns()) + { + PropertyDefintionWrapper propertyDefinition = null; + PropertyType type = null; + QName alfrescoPropertyQName = null; + QName alfrescoDataTypeQName = null; + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + String propertyName = arg.getPropertyName(); + alfrescoPropertyQName = QName.createQName(propertyName); + PropertyDefinition alfPropDef = alfrescoDictionaryService.getProperty(alfrescoPropertyQName); + if (alfPropDef == null) + { + alfrescoPropertyQName = null; + } else + { + alfrescoDataTypeQName = alfPropDef.getDataType().getName(); + } + propertyDefinition = cmisDictionaryService.findProperty(propertyName); + type = propertyDefinition.getPropertyDefinition().getPropertyType(); + } + if (type == null) + { + type = cmisDictionaryService.findDataType(column.getFunction().getReturnType()); + } + if (alfrescoDataTypeQName == null) + { + alfrescoDataTypeQName = cmisDictionaryService.findAlfrescoDataType(type); + } + CMISResultSetColumn cmd = new CMISResultSetColumn(column.getAlias(), propertyDefinition, type, + alfrescoPropertyQName, alfrescoDataTypeQName); + columnMetaData.put(cmd.getName(), cmd); + } + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getColumnNames() + */ + public String[] getColumnNames() + { + return columnMetaData.keySet().toArray(new String[0]); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getColumns() + */ + public CMISResultSetColumn[] getColumns() + { + return columnMetaData.values().toArray(new CMISResultSetColumn[0]); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.search.CMISResultSetMetaData#getCoumn(java.lang.String) + */ + public CMISResultSetColumn getColumn(String name) + { + return columnMetaData.get(name); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getQueryOptions() + */ + public CMISQueryOptions getQueryOptions() + { + return options; + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.cmis.search.CMISResultSetMetaData#getSelector(java.lang. + * String) + */ + public CMISResultSetSelector getSelector(String name) + { + return selectorMetaData.get(name); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getSelectorNames() + */ + public String[] getSelectorNames() + { + return selectorMetaData.keySet().toArray(new String[0]); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetMetaData#getSelectors() + */ + public CMISResultSetSelector[] getSelectors() + { + return selectorMetaData.values().toArray(new CMISResultSetSelector[0]); + } + + public LimitBy getLimitedBy() + { + return limitBy; + } + + public PermissionEvaluationMode getPermissionEvaluationMode() + { + throw new UnsupportedOperationException(); + } + + public ResultSetType getResultSetType() + { + return ResultSetType.COLUMN_AND_NODE_REF; + } + + public SearchParameters getSearchParameters() + { + return searchParams; + } + +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetRow.java b/source/java/org/alfresco/opencmis/search/CMISResultSetRow.java new file mode 100644 index 0000000000..42e1e51041 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetRow.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.repo.search.impl.querymodel.Column; +import org.alfresco.repo.search.impl.querymodel.PropertyArgument; +import org.alfresco.repo.search.impl.querymodel.Query; +import org.alfresco.repo.search.impl.querymodel.impl.functions.PropertyAccessor; +import org.alfresco.repo.search.results.ResultSetSPIWrapper; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.namespace.QName; + +/** + * @author andyh + */ +public class CMISResultSetRow implements ResultSetRow +{ + /** + * The containing result set + */ + private CMISResultSet resultSet; + + /** + * The current position in the containing result set + */ + private int index; + + private Map scores; + + private NodeService nodeService; + + private Map nodeRefs; + + private Query query; + + private CMISDictionaryService cmisDictionaryService; + + public CMISResultSetRow(CMISResultSet resultSet, int index, Map scores, NodeService nodeService, + Map nodeRefs, Query query, CMISDictionaryService cmisDictionaryService) + { + this.resultSet = resultSet; + this.index = index; + this.scores = scores; + this.nodeService = nodeService; + this.nodeRefs = nodeRefs; + this.query = query; + this.cmisDictionaryService = cmisDictionaryService; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getIndex() + */ + public int getIndex() + { + return index; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getResultSet() + */ + public ResultSet getResultSet() + { + return new ResultSetSPIWrapper(resultSet); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScore() + */ + public float getScore() + { + float count = 0; + float overall = 0; + for (Float score : scores.values()) + { + overall = (overall * (count / (count + 1.0f))) + (score / (count + 1.0f)); + } + return overall; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScore(java.lang.String) + */ + public float getScore(String selectorName) + { + return scores.get(selectorName); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScores() + */ + public Map getScores() + { + return scores; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScore(java.lang.String) + */ + public NodeRef getNodeRef(String selectorName) + { + return nodeRefs.get(selectorName); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getScores() + */ + public Map getNodeRefs() + { + return nodeRefs; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getValue(java.lang.String) + */ + public Serializable getValue(String columnName) + { + CmisFunctionEvaluationContext context = new CmisFunctionEvaluationContext(); + context.setCmisDictionaryService(cmisDictionaryService); + context.setNodeRefs(nodeRefs); + context.setNodeService(nodeService); + context.setScores(scores); + context.setScore(getScore()); + for (Column column : query.getColumns()) + { + if (column.getAlias().equals(columnName)) + { + return column.getFunction().getValue(column.getFunctionArguments(), context); + } + // Special case for one selector - ignore any table aliases + // also allows look up direct and not by alias + // Perhaps we should add the duplicates instead + // TODO: check SQL 92 for single alias table behaviour for selectors + if (nodeRefs.size() == 1) + { + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + String propertyName = arg.getPropertyName(); + if (propertyName.equals(columnName)) + { + return column.getFunction().getValue(column.getFunctionArguments(), context); + } + StringBuilder builder = new StringBuilder(); + builder.append(arg.getSelector()).append(".").append(propertyName); + propertyName = builder.toString(); + if (propertyName.equals(columnName)) + { + return column.getFunction().getValue(column.getFunctionArguments(), context); + } + } + } else + { + if (column.getFunction().getName().equals(PropertyAccessor.NAME)) + { + PropertyArgument arg = (PropertyArgument) column.getFunctionArguments().get( + PropertyAccessor.ARG_PROPERTY); + StringBuilder builder = new StringBuilder(); + builder.append(arg.getSelector()).append(".").append(arg.getPropertyName()); + String propertyName = builder.toString(); + if (propertyName.equals(columnName)) + { + return column.getFunction().getValue(column.getFunctionArguments(), context); + } + } + } + } + return null; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.cmis.search.CMISResultSetRow#getValues() + */ + public Map getValues() + { + LinkedHashMap answer = new LinkedHashMap(); + for (String column : resultSet.getMetaData().getColumnNames()) + { + answer.put(column, getValue(column)); + } + return answer; + } + + public CMISResultSet getCMISResultSet() + { + return resultSet; + } + + public ChildAssociationRef getChildAssocRef() + { + NodeRef nodeRef = getNodeRef(); + return nodeService.getPrimaryParent(nodeRef); + } + + public NodeRef getNodeRef() + { + if (nodeRefs.size() == 1) + { + return nodeRefs.values().iterator().next(); + } else if (allNodeRefsEqual(nodeRefs)) + { + return nodeRefs.values().iterator().next(); + } + throw new UnsupportedOperationException("Ambiguous selector"); + } + + private boolean allNodeRefsEqual(Map selected) + { + NodeRef last = null; + for (NodeRef current : selected.values()) + { + if (last == null) + { + last = current; + } else + { + if (!last.equals(current)) + { + return false; + } + } + } + return true; + } + + public QName getQName() + { + return getChildAssocRef().getQName(); + } + + public Serializable getValue(QName qname) + { + throw new UnsupportedOperationException(); + } + +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetRowIterator.java b/source/java/org/alfresco/opencmis/search/CMISResultSetRowIterator.java new file mode 100644 index 0000000000..c67f60e4e5 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetRowIterator.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.util.ListIterator; + +/** + * @author andyh + */ +public class CMISResultSetRowIterator implements ListIterator +{ + /** + * The result set + */ + private CMISResultSet resultSet; + + /** + * The current position + */ + private int position = -1; + + /** + * The maximum position + */ + private int max; + + /** + * Create an iterator over the result set. Follows stadard ListIterator + * conventions + * + * @param resultSet + */ + public CMISResultSetRowIterator(CMISResultSet resultSet) + { + this.resultSet = resultSet; + this.max = resultSet.getLength(); + } + + public CMISResultSet getResultSet() + { + return resultSet; + } + + /* + * ListIterator implementation + */ + public boolean hasNext() + { + return position < (max - 1); + } + + public boolean allowsReverse() + { + return true; + } + + public boolean hasPrevious() + { + return position > 0; + } + + public CMISResultSetRow next() + { + return resultSet.getRow(moveToNextPosition()); + } + + protected int moveToNextPosition() + { + return ++position; + } + + public CMISResultSetRow previous() + { + return resultSet.getRow(moveToPreviousPosition()); + } + + protected int moveToPreviousPosition() + { + return --position; + } + + public int nextIndex() + { + return position + 1; + } + + public int previousIndex() + { + return position - 1; + } + + /* + * Mutation is not supported + */ + + public void remove() + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + public void set(CMISResultSetRow o) + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + public void add(CMISResultSetRow o) + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + +} diff --git a/source/java/org/alfresco/opencmis/search/CMISResultSetSelector.java b/source/java/org/alfresco/opencmis/search/CMISResultSetSelector.java new file mode 100644 index 0000000000..9b86512e3f --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CMISResultSetSelector.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.service.cmr.search.ResultSetSelector; +import org.alfresco.service.namespace.QName; + +/** + * @author andyh + * + */ +public class CMISResultSetSelector implements ResultSetSelector +{ + private String name; + + private TypeDefinitionWrapper typeDefinition; + + public CMISResultSetSelector(String name, TypeDefinitionWrapper typeDefinition) + { + this.name = name; + this.typeDefinition = typeDefinition; + } + + public String getName() + { + return name; + } + + public TypeDefinitionWrapper getTypeDefinition() + { + return typeDefinition; + } + + public QName getType() + { + return typeDefinition.getAlfrescoName(); + } + +} diff --git a/source/java/org/alfresco/opencmis/search/CmisFunctionEvaluationContext.java b/source/java/org/alfresco/opencmis/search/CmisFunctionEvaluationContext.java new file mode 100644 index 0000000000..344052f420 --- /dev/null +++ b/source/java/org/alfresco/opencmis/search/CmisFunctionEvaluationContext.java @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.opencmis.search; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; + +import org.alfresco.cmis.CMISDictionaryModel; +import org.alfresco.opencmis.dictionary.CMISDictionaryService; +import org.alfresco.opencmis.dictionary.PropertyDefintionWrapper; +import org.alfresco.opencmis.dictionary.TypeDefinitionWrapper; +import org.alfresco.repo.search.impl.lucene.LuceneFunction; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.repo.search.impl.querymodel.FunctionArgument; +import org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext; +import org.alfresco.repo.search.impl.querymodel.PredicateMode; +import org.alfresco.repo.search.impl.querymodel.QueryModelException; +import org.alfresco.repo.search.impl.querymodel.Selector; +import org.alfresco.repo.search.impl.querymodel.impl.functions.Lower; +import org.alfresco.repo.search.impl.querymodel.impl.functions.Upper; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; +import org.apache.chemistry.opencmis.commons.enums.Cardinality; +import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.search.Query; + +/** + * @author andyh + */ +public class CmisFunctionEvaluationContext implements FunctionEvaluationContext +{ + public static BaseTypeId[] STRICT_SCOPES = new BaseTypeId[] { BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER }; + + public static BaseTypeId[] ALFRESCO_SCOPES = new BaseTypeId[] { BaseTypeId.CMIS_DOCUMENT, BaseTypeId.CMIS_FOLDER, + BaseTypeId.CMIS_POLICY }; + + private Map nodeRefs; + + private Map scores; + + private NodeService nodeService; + + private CMISDictionaryService cmisDictionaryService; + + private BaseTypeId[] validScopes; + + private Float score; + + /** + * @param nodeRefs + * the nodeRefs to set + */ + public void setNodeRefs(Map nodeRefs) + { + this.nodeRefs = nodeRefs; + } + + /** + * @param scores + * the scores to set + */ + public void setScores(Map scores) + { + this.scores = scores; + } + + /** + * @param nodeService + * the nodeService to set + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param cmisDictionaryService + * the cmisDictionaryService to set + */ + public void setCmisDictionaryService(CMISDictionaryService cmisDictionaryService) + { + this.cmisDictionaryService = cmisDictionaryService; + } + + /** + * @param validScopes + * the valid scopes to set + */ + public void setValidScopes(BaseTypeId[] validScopes) + { + this.validScopes = validScopes; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * getNodeRefs() + */ + public Map getNodeRefs() + { + return nodeRefs; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * getNodeService() + */ + public NodeService getNodeService() + { + return nodeService; + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * getProperty(org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.namespace.QName) + */ + public Serializable getProperty(NodeRef nodeRef, String propertyName) + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyAccessor().getValue(nodeRef); + } + + /* + * (non-Javadoc) + * + * @see + * org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext#getScores + * () + */ + public Map getScores() + { + return scores; + } + + /** + * @return the score + */ + public Float getScore() + { + return score; + } + + /** + * @param score + * the score to set + */ + public void setScore(Float score) + { + this.score = score; + } + + public Query buildLuceneEquality(LuceneQueryParser lqp, String propertyName, Serializable value, + PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneEquality(lqp, value, mode, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * buildLuceneExists(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * org.alfresco.service.namespace.QName, java.lang.Boolean) + */ + public Query buildLuceneExists(LuceneQueryParser lqp, String propertyName, Boolean not) throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneExists(lqp, not); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * buildLuceneGreaterThan + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * org.alfresco.service.namespace.QName, java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneGreaterThan(LuceneQueryParser lqp, String propertyName, Serializable value, + PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneGreaterThan(lqp, value, mode, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * buildLuceneGreaterThanOrEquals + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * org.alfresco.service.namespace.QName, java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneGreaterThanOrEquals(LuceneQueryParser lqp, String propertyName, Serializable value, + PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneGreaterThanOrEquals(lqp, value, mode, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * buildLuceneIn(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * org.alfresco.service.namespace.QName, java.util.Collection, + * java.lang.Boolean, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneIn(LuceneQueryParser lqp, String propertyName, Collection values, + Boolean not, PredicateMode mode) throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneIn(lqp, values, not, mode); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * buildLuceneInequality + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * org.alfresco.service.namespace.QName, java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneInequality(LuceneQueryParser lqp, String propertyName, Serializable value, + PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneInequality(lqp, value, mode, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * buildLuceneLessThan + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * org.alfresco.service.namespace.QName, java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneLessThan(LuceneQueryParser lqp, String propertyName, Serializable value, + PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneLessThan(lqp, value, mode, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * buildLuceneLessThanOrEquals + * (org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * org.alfresco.service.namespace.QName, java.io.Serializable, + * org.alfresco.repo.search.impl.querymodel.PredicateMode) + */ + public Query buildLuceneLessThanOrEquals(LuceneQueryParser lqp, String propertyName, Serializable value, + PredicateMode mode, LuceneFunction luceneFunction) throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneLessThanOrEquals(lqp, value, mode, luceneFunction); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * buildLuceneLike(org.alfresco.repo.search.impl.lucene.LuceneQueryParser, + * org.alfresco.service.namespace.QName, java.io.Serializable, + * java.lang.Boolean) + */ + public Query buildLuceneLike(LuceneQueryParser lqp, String propertyName, Serializable value, Boolean not) + throws ParseException + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().buildLuceneLike(lqp, value, not); + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * getLuceneSortField(org.alfresco.service.namespace.QName) + */ + public String getLuceneSortField(LuceneQueryParser lqp, String propertyName) + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + return propertyDef.getPropertyLuceneBuilder().getLuceneSortField(lqp); + } + + public boolean isObjectId(String propertyName) + { + return CMISDictionaryModel.PROP_OBJECT_ID.equalsIgnoreCase(propertyName); + } + + public boolean isOrderable(String fieldName) + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(fieldName); + if (propertyDef == null) + { + return false; + } else + { + return propertyDef.getPropertyDefinition().isOrderable(); + } + } + + public boolean isQueryable(String fieldName) + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(fieldName); + if (propertyDef == null) + { + return true; + } else + { + return propertyDef.getPropertyDefinition().isQueryable(); + } + } + + public String getLuceneFieldName(String propertyName) + { + PropertyDefintionWrapper propertyDef = cmisDictionaryService.findProperty(propertyName); + if (propertyDef != null) + { + return propertyDef.getPropertyLuceneBuilder().getLuceneFieldName(); + } else + { + // TODO: restrict to supported "special" fields + return propertyName; + } + } + + public LuceneFunction getLuceneFunction(FunctionArgument functionArgument) + { + if (functionArgument == null) + { + return LuceneFunction.FIELD; + } else + { + String functionName = functionArgument.getFunction().getName(); + if (functionName.equals(Upper.NAME)) + { + return LuceneFunction.UPPER; + } else if (functionName.equals(Lower.NAME)) + { + return LuceneFunction.LOWER; + } else + { + throw new QueryModelException("Unsupported function: " + functionName); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * checkFieldApplies(org.alfresco.service.namespace.QName, java.lang.String) + */ + public void checkFieldApplies(Selector selector, String propertyName) + { + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(propertyName); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Unknown column/property " + propertyName); + } + + TypeDefinitionWrapper typeDef = cmisDictionaryService.findTypeForClass(selector.getType(), validScopes); + if (typeDef == null) + { + throw new CmisInvalidArgumentException("Type unsupported in CMIS queries: " + selector.getAlias()); + } + + // Check column/property applies to selector/type + + if (typeDef.getPropertyById(propDef.getPropertyId()) == null) + { + throw new CmisInvalidArgumentException("Invalid column for " + + typeDef.getTypeDefinition(false).getQueryName() + "." + propertyName); + } + + } + + /* + * (non-Javadoc) + * + * @see org.alfresco.repo.search.impl.querymodel.FunctionEvaluationContext# + * isMultiValued(java.lang.String) + */ + public boolean isMultiValued(String propertyName) + { + PropertyDefintionWrapper propDef = cmisDictionaryService.findPropertyByQueryName(propertyName); + if (propDef == null) + { + throw new CmisInvalidArgumentException("Unknown column/property " + propertyName); + } + return propDef.getPropertyDefinition().getCardinality() == Cardinality.MULTI; + } + +} diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneOpenCMISAlfrescoSqlQueryLanguage.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneOpenCMISAlfrescoSqlQueryLanguage.java new file mode 100644 index 0000000000..a789df3b4e --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneOpenCMISAlfrescoSqlQueryLanguage.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.search.impl.lucene; + +import java.util.List; + +import org.alfresco.opencmis.search.CMISQueryOptions; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; +import org.alfresco.opencmis.search.CMISQueryService; +import org.alfresco.opencmis.search.CMISResultSetMetaData; +import org.alfresco.opencmis.search.CMISResultSetRow; +import org.alfresco.repo.search.impl.querymodel.QueryOptions.Connective; +import org.alfresco.repo.search.results.ResultSetSPIWrapper; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; + +/** + * Support for sql-cmis-strict in the search service + * + * @author andyh + * + */ +public class LuceneOpenCMISAlfrescoSqlQueryLanguage implements LuceneQueryLanguageSPI +{ + private CMISQueryService cmisQueryService; + + /** + * Set the search service + * + * @param cmisQueryService + */ + public void setCmisQueryService(CMISQueryService cmisQueryService) + { + this.cmisQueryService = cmisQueryService; + } + + public ResultSet executeQuery(SearchParameters searchParameters, ADMLuceneSearcherImpl admLuceneSearcher) + { + String sql = searchParameters.getQuery(); + + CMISQueryOptions options = new CMISQueryOptions(sql, searchParameters.getStores().get(0)); + options.setIncludeInTransactionData(!searchParameters.excludeDataInTheCurrentTransaction()); + options.setDefaultFTSConnective(searchParameters.getDefaultOperator() == SearchParameters.Operator.OR ? Connective.OR + : Connective.AND); + options.setDefaultFTSFieldConnective(searchParameters.getDefaultOperator() == SearchParameters.Operator.OR ? Connective.OR + : Connective.AND); + options.setSkipCount(searchParameters.getSkipCount()); + options.setMaxPermissionChecks(searchParameters.getMaxPermissionChecks()); + options.setMaxPermissionCheckTimeMillis(searchParameters.getMaxPermissionCheckTimeMillis()); + options.setDefaultFieldName(searchParameters.getDefaultFieldName()); + if (searchParameters.getLimitBy() == LimitBy.FINAL_SIZE) + { + options.setMaxItems(searchParameters.getLimit()); + } else + { + options.setMaxItems(searchParameters.getMaxItems()); + } + options.setMlAnalaysisMode(searchParameters.getMlAnalaysisMode()); + options.setLocales(searchParameters.getLocales()); + options.setStores(searchParameters.getStores()); + + options.setQueryMode(CMISQueryMode.CMS_WITH_ALFRESCO_EXTENSIONS); + + return new ResultSetSPIWrapper(cmisQueryService.query(options)); + } + + public String getName() + { + return SearchService.LANGUAGE_CMIS_ALFRESCO; + } + + public void setFactories(List factories) + { + for (AbstractLuceneIndexerAndSearcherFactory factory : factories) + { + factory.registerQueryLanguage(this); + } + } + +} diff --git a/source/java/org/alfresco/repo/search/impl/lucene/LuceneOpenCMISStrictSqlQueryLanguage.java b/source/java/org/alfresco/repo/search/impl/lucene/LuceneOpenCMISStrictSqlQueryLanguage.java new file mode 100644 index 0000000000..6057ff4a8c --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/lucene/LuceneOpenCMISStrictSqlQueryLanguage.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.search.impl.lucene; + +import java.util.List; + +import org.alfresco.opencmis.search.CMISQueryOptions; +import org.alfresco.opencmis.search.CMISQueryOptions.CMISQueryMode; +import org.alfresco.opencmis.search.CMISQueryService; +import org.alfresco.opencmis.search.CMISResultSetMetaData; +import org.alfresco.opencmis.search.CMISResultSetRow; +import org.alfresco.repo.search.impl.querymodel.QueryOptions.Connective; +import org.alfresco.repo.search.results.ResultSetSPIWrapper; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; + +/** + * Support for Alfresco SQL in the search service + * + * @author andyh + * + */ +public class LuceneOpenCMISStrictSqlQueryLanguage implements LuceneQueryLanguageSPI +{ + private CMISQueryService cmisQueryService; + + /** + * Set the search service + * + * @param cmisQueryService + */ + public void setCmisQueryService(CMISQueryService cmisQueryService) + { + this.cmisQueryService = cmisQueryService; + } + + public ResultSet executeQuery(SearchParameters searchParameters, ADMLuceneSearcherImpl admLuceneSearcher) + { + String sql = searchParameters.getQuery(); + + CMISQueryOptions options = new CMISQueryOptions(sql, searchParameters.getStores().get(0)); + options.setIncludeInTransactionData(!searchParameters.excludeDataInTheCurrentTransaction()); + options.setDefaultFTSConnective(searchParameters.getDefaultOperator() == SearchParameters.Operator.OR ? Connective.OR + : Connective.AND); + options.setDefaultFTSFieldConnective(searchParameters.getDefaultOperator() == SearchParameters.Operator.OR ? Connective.OR + : Connective.AND); + options.setSkipCount(searchParameters.getSkipCount()); + options.setMaxPermissionChecks(searchParameters.getMaxPermissionChecks()); + options.setMaxPermissionCheckTimeMillis(searchParameters.getMaxPermissionCheckTimeMillis()); + if (searchParameters.getLimitBy() == LimitBy.FINAL_SIZE) + { + options.setMaxItems(searchParameters.getLimit()); + } else + { + options.setMaxItems(searchParameters.getMaxItems()); + } + options.setMlAnalaysisMode(searchParameters.getMlAnalaysisMode()); + options.setLocales(searchParameters.getLocales()); + options.setStores(searchParameters.getStores()); + + options.setQueryMode(CMISQueryMode.CMS_STRICT); + + return new ResultSetSPIWrapper(cmisQueryService.query(options)); + } + + public String getName() + { + return SearchService.LANGUAGE_CMIS_STRICT; + } + + public void setFactories(List factories) + { + for (AbstractLuceneIndexerAndSearcherFactory factory : factories) + { + factory.registerQueryLanguage(this); + } + } + +}