/* * Copyright (c) 2006, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.wso2.carbon.registry.cmis; import org.apache.chemistry.opencmis.commons.PropertyIds; import org.apache.chemistry.opencmis.commons.data.AllowableActions; import org.apache.chemistry.opencmis.commons.data.ObjectData; import org.apache.chemistry.opencmis.commons.data.Properties; import org.apache.chemistry.opencmis.commons.data.PropertyData; 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.Updatability; import org.apache.chemistry.opencmis.commons.exceptions.*; import org.apache.chemistry.opencmis.commons.impl.dataobjects.*; import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl; import org.apache.chemistry.opencmis.commons.server.ObjectInfoHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.carbon.registry.cmis.util.CMISConstants; import org.wso2.carbon.registry.cmis.util.CommonUtil; import org.wso2.carbon.registry.cmis.util.PropertyHelper; import org.wso2.carbon.registry.core.CollectionImpl; import org.wso2.carbon.registry.core.Registry; import org.wso2.carbon.registry.core.Resource; import org.wso2.carbon.registry.core.exceptions.RegistryException; import org.wso2.carbon.registry.cmis.impl.FolderTypeHandler; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.util.*; /** * This class wraps a registry object along with other related type/path manager instances * Act as the super class for Other Document/Folder classes. */ public abstract class RegistryObject { private static final Logger log = LoggerFactory.getLogger(RegistryObject.class); private final Registry repository; private Resource resource; protected final RegistryTypeManager typeManager; protected final PathManager pathManager; /** * Create a new instance wrapping a Registry <code>node</code>. * * @param resource the Registry <code>node</code> to represent * @param typeManager * @param pathManager * //@param typeHandlerManager */ protected RegistryObject(Registry repository,Resource resource, RegistryTypeManager typeManager, PathManager pathManager) { this.resource = resource; this.typeManager = typeManager; this.pathManager = pathManager; this.repository = repository; //this.typeHandlerManager = typeHandlerManager; } /** * @return the Registry <code>node</code> represented by this instance */ public Resource getNode() { return resource; } public void setNode(Resource node){ resource = node; } protected Registry getRepository(){ return this.repository; } /** * @return the name of the CMIS object represented by this instance * @throws CmisRuntimeException */ public String getName() { try { return getNodeName(); } catch (RegistryException e) { String msg = "Unable to get the name of CMIS object"; log.error(msg, e); throw new CmisRuntimeException(msg, e); } } /** * @return the id of the CMIS object represented by this instance * @throws CmisRuntimeException */ public String getId() { try { return getObjectId(); } catch (RegistryException e) { String msg = "Failed to get the id of the CMIS object"; log.error(msg, e); throw new CmisRuntimeException(msg, e); } } /** * @return the typeId of the CMIS object represented by this instance */ public String getTypeId() { return getTypeIdInternal(); } /** * @return <code>true</code> if this instance represent the root of the CMIS folder hierarchy. */ public boolean isRoot() { return pathManager.isRoot(resource); } /** * @return <code>true</code> if this instance represents a cmis:document type */ public boolean isDocument() { return BaseTypeId.CMIS_DOCUMENT == getBaseTypeId(); } /** * @return <code>true</code> if this instance represents a cmis:folder type */ public boolean isFolder() { return BaseTypeId.CMIS_FOLDER == getBaseTypeId(); } /** * @return <code>true</code> if this instance represents a versionable CMIS object */ public boolean isVersionable() { return true; } /** * @return this instance as a <code>RegistryDocument</code> * @throws CmisConstraintException if <code>this.isDocument() == false</code> */ public RegistryDocument asDocument() { if (isDocument()) { return (RegistryDocument) this; } else { throw new CmisConstraintException("Not a document: " + this); } } /** * @return this instance as a <code>RegistryFolder</code> * @throws CmisConstraintException if <code>this.isFolder() == false</code> */ public RegistryFolder asFolder() { if (isFolder()) { return (RegistryFolder) this; } else { throw new CmisObjectNotFoundException("Not a folder: " + this); } } /** * @return this instance as a <code>RegistryVersionBase</code> * @throws CmisConstraintException if <code>this.isVersionable() == false</code> */ public RegistryVersionBase asVersion() { // TODO Removed isVersionable() check here as it always returns true now. Any changes are // done to isVersionable() just make sure to change this method as necessary to reflect those.. return (RegistryVersionBase) this; } /** * Factory method creating a new <code>RegistryNode</code> from a node at a given Registry path. * * @param path Registry path of the node * @return A new instance representing the Registry node at <code>path</code> * @throws CmisObjectNotFoundException if <code>path</code> does not identify a Registry node * @throws CmisRuntimeException */ public RegistryObject getNode(String path) throws RegistryException { return create(repository.get(path)); } private Resource getResourceByPath(String path) throws RegistryException { // TODO Auto-generated method stub return null; } /** * Factory method for creating a new <code>RegistryNode</code> instance from a Registry <code>Node</code> * * @param resource of registry <code>Resource</code> * @return a new <code>RegistryObject</code> * Since my implementation is fixed for types Document & Folder, this method * is implemented in the respective RegistryDocument or RegistryFolder classes. */ protected abstract RegistryObject create(Resource resource); //Registry implementation calls getGregNode from DefaultDocumentHandler & DefaultFolderHandler. //Doc returns a new Registry version //Folder returns a new Registry Folder /** * Compile the <code>ObjectData</code> for this node */ public ObjectData compileObjectType(Set<String> filter, Boolean includeAllowableActions, ObjectInfoHandler objectInfos, boolean requiresObjectInfo) { try { ObjectDataImpl result = new ObjectDataImpl(); ObjectInfoImpl objectInfo = new ObjectInfoImpl(); PropertiesImpl properties = new PropertiesImpl(); //filter = filter == null ? null : new HashSet<String>(filter); if (filter != null) filter = new HashSet<String>(filter); compileProperties(properties, filter, objectInfo); result.setProperties(properties); if (filter != null && !filter.isEmpty()) { log.debug("Unknown filter properties: " + filter.toString()); } if (Boolean.TRUE.equals(includeAllowableActions)) { result.setAllowableActions(getAllowableActions()); } if (requiresObjectInfo) { objectInfo.setObject(result); objectInfos.addObjectInfo(objectInfo); } return result; } catch (RegistryException e) { String msg = ""; log.error(msg, e); throw new CmisRuntimeException(msg, e); } } public AllowableActions getAllowableActions() { AllowableActionsImpl aas = new AllowableActionsImpl(); aas.setAllowableActions(compileAllowableActions(new HashSet<Action>())); return aas; } /** * See CMIS 1.0 section 2.2.3.5 getObjectParents * * @return parent of this object * @throws CmisObjectNotFoundException if this is the root folder * @throws CmisRuntimeException */ public RegistryFolder getParent() { try { if(resource.getPath().equals("/")){ throw new CmisObjectNotFoundException("No parent for root folder"); } Resource parent = repository.get(resource.getParentPath()); if (parent instanceof CollectionImpl){ FolderTypeHandler handler = new FolderTypeHandler(getRepository(), pathManager, typeManager); return handler.getGregNode(parent); } else{ throw new CmisInvalidArgumentException("Resource found. Collection expected"); } } catch (RegistryException e) { String msg = "Error trying to retrieve the resource "; log.error(msg, e); throw new CmisRuntimeException(msg, e); } } /** * Compile the allowed actions on the CMIS object represented by this instance * See CMIS 1.0 section 2.2.4.6 getAllowableActions * * @param aas compilation of allowed actions * @return */ protected Set<Action> compileAllowableActions(Set<Action> aas) { setAction(aas, Action.CAN_GET_OBJECT_PARENTS, true); setAction(aas, Action.CAN_GET_PROPERTIES, true); setAction(aas, Action.CAN_UPDATE_PROPERTIES, true); setAction(aas, Action.CAN_MOVE_OBJECT, true); if(pathManager.isRoot(getNode())){ setAction(aas, Action.CAN_DELETE_OBJECT, false); } else { setAction(aas, Action.CAN_DELETE_OBJECT, true); } setAction(aas, Action.CAN_GET_ACL, false); setAction(aas, Action.CAN_APPLY_ACL, false); setAction(aas, Action.CAN_GET_OBJECT_RELATIONSHIPS, false); setAction(aas, Action.CAN_ADD_OBJECT_TO_FOLDER, false); setAction(aas, Action.CAN_REMOVE_OBJECT_FROM_FOLDER, false); setAction(aas, Action.CAN_APPLY_POLICY, false); setAction(aas, Action.CAN_GET_APPLIED_POLICIES, false); setAction(aas, Action.CAN_REMOVE_POLICY, false); setAction(aas, Action.CAN_CREATE_RELATIONSHIP, false); return aas; } /** * Add <code>action</code> to <code>actions</code> iff <code>condition</code> is true. * * @param actions * @param action * @param condition */ protected static void setAction(Set<Action> actions, Action action, boolean condition) { if (condition) { actions.add(action); } else { actions.remove(action); } } /** * @return the change token of the CMIS object represented by this instance * @throws RegistryException */ protected String getChangeToken() throws RegistryException { return null; } protected String getCreatedBy() throws RegistryException { return resource.getAuthorUserName(); } /** * @return the last modification date of the CMIS object represented by this instance * @throws RegistryException */ protected GregorianCalendar getLastModified() throws RegistryException { GregorianCalendar gregorianCalendar = new GregorianCalendar(); gregorianCalendar.setTime(resource.getLastModified()); return gregorianCalendar; } /** * @return the last modifier of the CMIS object represented by this instance * @throws RegistryException */ protected String getLastModifiedBy() throws RegistryException { return resource.getLastUpdaterUserName(); } protected GregorianCalendar getCreated() throws RegistryException { GregorianCalendar gregorianCalendar = new GregorianCalendar(); gregorianCalendar.setTime(resource.getCreatedTime()); return gregorianCalendar; } /** * Utility function to retrieve the length of a property of a Registry <code>Node</code>. * * @param node * @param propertyName * @return * @throws RegistryException */ protected static long getPropertyLength(Resource node, String propertyName) throws RegistryException { String property = node.getProperty(propertyName); //if property asks for REGISTRY_DATA then this should return the length of the content stream //my assumption if(propertyName.equals(CMISConstants.GREG_DATA)){ if(property != null && !property.equals("true")){ return 0; } long count = 0; //node.getContent(); Object dataObject = node.getContent(); if(dataObject == null){ return count; } else{ count = 0; byte[] buffer = new byte[64 * 1024]; InputStream inputStream = node.getContentStream(); try { int b = inputStream.read(buffer); while (b > -1) { count += b; b = inputStream.read(buffer); } inputStream.close(); } catch (IOException e) { throw new CmisRuntimeException(e.getMessage(),e); } return count; } } else{ if(property == null){ return -1; } else{ return property.length(); } } } /** * Retrieve the context node of the CMIS object represented by this instance. The * context node is the node which is used to derive the common properties from * (creation date, modification date, ...) * * @return the context node * @throws RegistryException */ protected abstract Resource getContextNode() throws RegistryException; /** * @return the value of the <code>cmis:objectTypeId</code> property */ protected abstract String getTypeIdInternal(); /** * @return the value of the <code>cmis:baseTypeId</code> property */ protected abstract BaseTypeId getBaseTypeId(); protected String getVersionSeriesId() throws RegistryException { //TODO Think on this later String path = resource.getPath(); if(path.endsWith(CMISConstants.PWC_SUFFIX)){ int endIndex = path.indexOf(CMISConstants.PWC_SUFFIX); return path.substring(0, endIndex); } else if(path.contains(";")){ int endIndex = path.indexOf(';'); return path.substring(0, endIndex); } return resource.getPath(); } /** * @return the name of the underlying Registry <code>node</code>. * @throws RegistryException */ protected String getNodeName() throws RegistryException { String path = resource.getPath(); //TODO check if it starts with '/' if(path.equals("/")){ return "/"; } String[] parts = path.split("/"); String actualName = null; if(parts == null){ actualName = path; } else{ actualName = parts[parts.length-1]; } //Stored as abc_pwc in the registry but presented as pwc to cmis client if(actualName.endsWith(CMISConstants.PWC_SUFFIX)){ return actualName.substring(0, actualName.indexOf(CMISConstants.PWC_SUFFIX)); } else { //TODO if name includes the ;version:xxx part maybe remove the ;version:xxx return actualName; } } /** * @return the object id of the CMIS object represented by this instance * @throws RegistryException */ protected String getObjectId() throws RegistryException { return getVersionSeriesId(); } /** * Add Id property to the CMIS object represented by this instance */ protected final void addPropertyId(PropertiesImpl props, String typeId, Set<String> filter, String id, String value) { if (!checkAddProperty(props, typeId, filter, id)) { return; } if (value.equals(CMISConstants.GREG_PROPERTY_NOT_SET)) { value = null; } PropertyIdImpl prop = new PropertyIdImpl(id, value); prop.setQueryName(id); props.addProperty(prop); } protected final void addPropertyId(PropertiesImpl props, String typeId, Set<String> filter, String id, List<String> values) { if (!checkAddProperty(props, typeId, filter, id)) { return; } PropertyIdImpl prop = new PropertyIdImpl(id, values); prop.setQueryName(id); props.addProperty(prop); } /** * Add string property to the CMIS object represented by this instance */ protected final void addPropertyString(PropertiesImpl props, String typeId, Set<String> filter, String id, String value) { if (!checkAddProperty(props, typeId, filter, id)) { return; } PropertyStringImpl prop = new PropertyStringImpl(id, value); prop.setQueryName(id); props.addProperty(prop); } /** * Add integer property to the CMIS object represented by this instance */ protected final void addPropertyInteger(PropertiesImpl props, String typeId, Set<String> filter, String id, long value) { if (!checkAddProperty(props, typeId, filter, id)) { return; } PropertyIntegerImpl prop = new PropertyIntegerImpl(id, BigInteger.valueOf(value)); prop.setQueryName(id); props.addProperty(prop); } /** * Add boolean property to the CMIS object represented by this instance */ protected final void addPropertyBoolean(PropertiesImpl props, String typeId, Set<String> filter, String id, boolean value) { if (!checkAddProperty(props, typeId, filter, id)) { return; } PropertyBooleanImpl prop = new PropertyBooleanImpl(id, value); prop.setQueryName(id); props.addProperty(prop); } /** * Add date-time property to the CMIS object represented by this instance */ protected final void addPropertyDateTime(PropertiesImpl props, String typeId, Set<String> filter, String id, GregorianCalendar value) { if (!checkAddProperty(props, typeId, filter, id)) { return; } PropertyDateTimeImpl prop = new PropertyDateTimeImpl(id, value); prop.setQueryName(id); props.addProperty(prop); } /** * Validate a set of properties against a filter and its definitions */ protected final boolean checkAddProperty(Properties properties, String typeId, Set<String> filter, String id) { if (properties == null || properties.getProperties() == null) { throw new IllegalArgumentException("Properties must not be null!"); } if (id == null) { throw new IllegalArgumentException("Id must not be null!"); } TypeDefinition type = typeManager.getType(typeId); if (type == null) { throw new IllegalArgumentException("Unknown type: " + typeId); } if (!type.getPropertyDefinitions().containsKey(id)) { throw new IllegalArgumentException("Unknown property: " + id); } String queryName = type.getPropertyDefinitions().get(id).getQueryName(); if (queryName != null && filter != null) { if (filter.contains(queryName)) { filter.remove(queryName); } else { return false; } } return true; } public final void updateProperties(Resource resource, String typeId, Properties properties) { PropertyUpdater.create(typeManager, typeId, properties).apply(repository, resource); } /** * See CMIS 1.0 section 2.2.4.12 updateProperties * * @throws CmisStorageException */ public RegistryObject updateProperties(Properties properties) { // get and check the new name String newName = PropertyHelper.getStringProperty(properties, PropertyIds.NAME); boolean rename = newName != null && !getName().equals(newName); if (rename && isRoot()) { throw new CmisUpdateConflictException("Cannot rename root node"); } try{ Resource newNode; if (rename) { String destPath = CommonUtil.getDestPathOfNode(getNode().getParentPath(), newName); repository.rename(getNode().getPath(), destPath); newNode = repository.get(destPath); //newNode.setProperty(PropertyIds.NAME); setNode(newNode); } else { newNode = resource; } // Are there properties to update? PropertyUpdater propertyUpdater = PropertyUpdater.create(typeManager, getTypeId(), properties); RegistryVersionBase gregVersion = isVersionable() ? asVersion() : null; // Update properties. Checkout if required boolean autoCheckout = false; if (!propertyUpdater.isEmpty()) { autoCheckout = gregVersion != null && !gregVersion.isCheckedOut(); if (autoCheckout) { gregVersion.checkout(); } // update the properties propertyUpdater.apply(repository, resource); } //session.save(); if (autoCheckout) { // auto versioning -> return new version created by checkin return gregVersion.checkin(null, null, "auto checkout"); } else if (gregVersion != null && gregVersion.isCheckedOut()) { // the node is checked out -> return pwc. RegistryVersionBase gregNewVersion = create(newNode).asVersion(); return gregNewVersion.getPwc(); } else { // non versionable or not a new node -> return this return create(newNode); } } catch (RegistryException e) { String msg = "Failed to update properties "; log.error(msg, e); throw new CmisStorageException(msg, e); } } /** * See CMIS 1.0 section 2.2.4.14 deleteObject * * @throws CmisRuntimeException */ public void delete(boolean allVersions, boolean isPwc) { try { String path = resource.getPath(); repository.delete(path); } catch (RegistryException e) { String msg = "Failed to delete the object "; log.error(msg, e); throw new CmisRuntimeException(msg, e); } } /** * See CMIS 1.0 section 2.2.4.13 moveObject * * @throws CmisStorageException */ public RegistryObject move(RegistryFolder parent) { try { // move it if target location is not same as source location //TODO String destPath = CommonUtil.getDestPathOfNode(parent.getNode().getPath(), getNodeName()); String srcPath = resource.getPath(); Resource newNode; if (srcPath.equals(destPath)) { newNode = resource; } else { repository.move(srcPath, destPath); newNode = repository.get(destPath); } return create(newNode); } catch (RegistryException e) { String msg = "Failed ot move the object "; log.error(msg, e); throw new CmisStorageException(msg, e); } } @Override public String toString() { return resource.getPath(); } protected static final class PropertyUpdater { private final List<PropertyData<?>> removeProperties = new ArrayList<PropertyData<?>>(); private final List<PropertyData<?>> updateProperties = new ArrayList<PropertyData<?>>(); private PropertyUpdater() { } public static PropertyUpdater create(RegistryTypeManager typeManager, String typeId, Properties properties) { if (properties == null) { throw new CmisConstraintException("No properties!"); } // get the property definitions TypeDefinition type = typeManager.getType(typeId); if (type == null) { throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!"); } PropertyUpdater propertyUpdater = new PropertyUpdater(); // update properties for (PropertyData<?> prop : properties.getProperties().values()) { PropertyDefinition<?> propDef = type.getPropertyDefinitions().get(prop.getId()); // do we know that property? if (propDef == null) { throw new CmisInvalidArgumentException("Property '" + prop.getId() + "' is unknown!"); } // skip content stream file name if (propDef.getId().equals(PropertyIds.CONTENT_STREAM_FILE_NAME)) { log.warn("Cannot set " + PropertyIds.CONTENT_STREAM_FILE_NAME + ". Ignoring"); continue; } // silently skip name if (propDef.getId().equals(PropertyIds.NAME)) { continue; } // can it be set? if (propDef.getUpdatability() == Updatability.READONLY) { throw new CmisConstraintException("Property '" + prop.getId() + "' is readonly!"); } if (propDef.getUpdatability() == Updatability.ONCREATE) { throw new CmisConstraintException("Property '" + prop.getId() + "' can only be set on create!"); } // default or value PropertyData<?> newProp; newProp = PropertyHelper.isPropertyEmpty(prop) ? PropertyHelper.getDefaultValue(propDef) : prop; // Schedule for remove or update if (newProp == null) { propertyUpdater.removeProperties.add(prop); } else { propertyUpdater.updateProperties.add(newProp); } } return propertyUpdater; } public boolean isEmpty() { return removeProperties.isEmpty() && updateProperties.isEmpty(); } public void apply(Registry repository,Resource resource) { try { for (PropertyData<?> prop: removeProperties) { CommonUtil.removeProperty(repository, resource, prop); } for (PropertyData<?> prop: updateProperties) { CommonUtil.setProperty(repository, resource, prop); } } catch (RegistryException e) { String msg = "Failed to apply properties to the resource"; log.error(msg, e); throw new CmisStorageException(msg, e); } } } protected static GregorianCalendar getPropertyOrElse(Resource resource, String propertyName, GregorianCalendar defaultValue) throws RegistryException { if (hasProperty(resource, propertyName)) { Calendar date = toDate(resource.getProperty(propertyName)); return toCalendar(date); } else { return defaultValue; } } protected static Calendar toDate (String propertyValue){ /* * TODO * Check whether propertyValue is ISO 8601 compliant * */ Calendar calendar = javax.xml.bind.DatatypeConverter.parseDateTime(propertyValue); return calendar; } protected static GregorianCalendar toCalendar(Calendar date) { if (date instanceof GregorianCalendar) { return (GregorianCalendar) date; } else { GregorianCalendar calendar = new GregorianCalendar(); calendar.setTimeZone(date.getTimeZone()); calendar.setTimeInMillis(date.getTimeInMillis()); return calendar; } } protected static String getPropertyOrElse(Resource resource, String propertyName, String defaultValue) throws RegistryException { return hasProperty(resource, propertyName) ? resource.getProperty(propertyName) : defaultValue; } protected static boolean hasProperty(Resource resource,String propertyName){ String property = resource.getProperty(propertyName); return property != null; } protected void compileProperties(PropertiesImpl properties, Set<String> filter, ObjectInfoImpl objectInfo) throws RegistryException { String typeId = getTypeIdInternal(); BaseTypeId baseTypeId = getBaseTypeId(); objectInfo.setBaseType(baseTypeId); objectInfo.setTypeId(typeId); objectInfo.setHasAcl(false); objectInfo.setVersionSeriesId(getVersionSeriesId()); objectInfo.setRelationshipSourceIds(null); objectInfo.setRelationshipTargetIds(null); objectInfo.setRenditionInfos(null); objectInfo.setSupportsPolicies(false); objectInfo.setSupportsRelationships(false); // id String objectId = getObjectId(); addPropertyId(properties, typeId, filter, PropertyIds.OBJECT_ID, objectId); objectInfo.setId(objectId); // name String name = getName(); addPropertyString(properties, typeId, filter, PropertyIds.NAME, name); objectInfo.setName(name); // base type and type name addPropertyId(properties, typeId, filter, PropertyIds.BASE_TYPE_ID, baseTypeId.value()); addPropertyId(properties, typeId, filter, PropertyIds.OBJECT_TYPE_ID, typeId); // created and modified by String createdBy = getCreatedBy(); addPropertyString(properties, typeId, filter, PropertyIds.CREATED_BY, createdBy); objectInfo.setCreatedBy(createdBy); addPropertyString(properties, typeId, filter, PropertyIds.LAST_MODIFIED_BY, getLastModifiedBy()); // creation and modification date GregorianCalendar created = getCreated(); addPropertyDateTime(properties, typeId, filter, PropertyIds.CREATION_DATE, created); objectInfo.setCreationDate(created); GregorianCalendar lastModified = getLastModified(); addPropertyDateTime(properties, typeId, filter, PropertyIds.LAST_MODIFICATION_DATE, lastModified); objectInfo.setLastModificationDate(lastModified); addPropertyString(properties, typeId, filter, PropertyIds.CHANGE_TOKEN, getChangeToken()); } }