blob: 961876541b8e2a91442df9981b442d7294a3b2b3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.chemistry.opencmis.jcr;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import javax.jcr.version.Version;
import org.apache.chemistry.opencmis.commons.PropertyIds;
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.definitions.TypeDefinitionContainer;
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.CmisConstraintException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl;
import org.apache.chemistry.opencmis.jcr.type.JcrTypeHandlerManager;
import org.apache.chemistry.opencmis.jcr.util.FilterIterator;
import org.apache.chemistry.opencmis.jcr.util.Predicate;
import org.apache.chemistry.opencmis.jcr.util.Util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Instances of this class represent a cmis:folder backed by an underlying JCR
* <code>Node</code>.
*/
public class JcrFolder extends JcrNode {
private static final Logger log = LoggerFactory.getLogger(JcrFolder.class);
public JcrFolder(Node node, JcrTypeManager typeManager, PathManager pathManager,
JcrTypeHandlerManager typeHandlerManager) {
super(node, typeManager, pathManager, typeHandlerManager);
}
/**
* See CMIS 1.0 section 2.2.3.1 getChildren
*
* @return Iterator of <code>JcrNode</code>. Children which are created in
* the checked out state are left out from the iterator.
* @throws CmisRuntimeException
*/
public Iterator<JcrNode> getNodes() {
try {
final FilterIterator<Node> nodes = new FilterIterator<Node>(getNode().getNodes(),
typeHandlerManager.getNodePredicate());
Iterator<JcrNode> jcrNodes = new Iterator<JcrNode>() {
public boolean hasNext() {
return nodes.hasNext();
}
public JcrNode next() {
return create(nodes.next());
}
public void remove() {
throw new UnsupportedOperationException();
}
};
// Filter out nodes which are checked out and do not have a version
// history (i.e. only a root version)
// These are created with VersioningState checkedout and not yet
// checked in.
return new FilterIterator<JcrNode>(jcrNodes, new Predicate<JcrNode>() {
public boolean evaluate(JcrNode node) {
try {
if (node.isVersionable()) {
Version baseVersion = getBaseVersion(node.getNode());
return baseVersion.getPredecessors().length > 0;
} else {
return true;
}
} catch (RepositoryException e) {
log.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
});
} catch (RepositoryException e) {
log.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
/**
* See CMIS 1.0 section 2.2.4.2 createDocumentFromSource
*
* @throws CmisStorageException
*/
public JcrNode addNodeFromSource(JcrDocument source, Properties properties) {
try {
String destPath = PathManager.createCmisPath(getNode().getPath(), source.getName());
Session session = getNode().getSession();
session.getWorkspace().copy(source.getNode().getPath(), destPath);
JcrNode jcrNode = create(session.getNode(destPath));
// overlay new properties
if (properties != null && properties.getProperties() != null) {
updateProperties(jcrNode.getNode(), jcrNode.getTypeId(), properties);
}
session.save();
return jcrNode;
} catch (RepositoryException e) {
log.debug(e.getMessage(), e);
throw new CmisStorageException(e.getMessage(), e);
}
}
/**
* See CMIS 1.0 section 2.2.4.14 deleteObject
*
* @throws CmisRuntimeException
*/
@Override
public void delete(boolean allVersions, boolean isPwc) {
try {
if (getNode().hasNodes()) {
throw new CmisConstraintException("Folder is not empty!");
} else {
super.delete(allVersions, isPwc);
}
} catch (RepositoryException e) {
log.debug(e.getMessage(), e);
throw new CmisRuntimeException(e.getMessage(), e);
}
}
/**
* See CMIS 1.0 section 2.2.4.15 deleteTree
*/
public FailedToDeleteDataImpl deleteTree() {
FailedToDeleteDataImpl result = new FailedToDeleteDataImpl();
String id = getId();
try {
Node node = getNode();
if (hasCheckOuts(node)) {
result.setIds(Collections.<String> singletonList(id));
} else {
Session session = node.getSession();
node.remove();
session.save();
result.setIds(Collections.<String> emptyList());
}
} catch (RepositoryException e) {
result.setIds(Collections.singletonList(id));
}
return result;
}
// ------------------------------------------< protected >---
@Override
protected void compileProperties(PropertiesImpl properties, Set<String> filter, ObjectInfoImpl objectInfo)
throws RepositoryException {
super.compileProperties(properties, filter, objectInfo);
objectInfo.setHasContent(false);
objectInfo.setSupportsDescendants(true);
objectInfo.setSupportsFolderTree(true);
String typeId = getTypeIdInternal();
addPropertyString(properties, typeId, filter, PropertyIds.PATH, pathManager.getPath(getNode()));
// folder properties
if (pathManager.isRoot(getNode())) {
objectInfo.setHasParent(false);
addPropertyList(properties, typeId, filter, PropertyIds.PARENT_ID, Collections.<String> emptyList());
} else {
objectInfo.setHasParent(true);
addPropertyId(properties, typeId, filter, PropertyIds.PARENT_ID, getParent().getObjectId());
}
addPropertyAllowedChildObjectTypeIds(properties, filter, typeId);
}
@Override
protected Set<Action> compileAllowableActions(Set<Action> aas) {
Set<Action> result = super.compileAllowableActions(aas);
setAction(result, Action.CAN_GET_DESCENDANTS, true);
setAction(result, Action.CAN_GET_CHILDREN, true);
setAction(result, Action.CAN_GET_FOLDER_PARENT, !pathManager.isRoot(getNode()));
setAction(result, Action.CAN_GET_OBJECT_PARENTS, !pathManager.isRoot(getNode()));
setAction(result, Action.CAN_GET_FOLDER_TREE, true);
setAction(result, Action.CAN_CREATE_DOCUMENT, true);
setAction(result, Action.CAN_CREATE_FOLDER, true);
setAction(result, Action.CAN_DELETE_TREE, true);
return result;
}
@Override
protected Node getContextNode() {
return getNode();
}
@Override
protected String getObjectId() throws RepositoryException {
return isRoot() ? PathManager.CMIS_ROOT_ID : super.getObjectId();
}
@Override
protected BaseTypeId getBaseTypeId() {
return BaseTypeId.CMIS_FOLDER;
}
@Override
protected String getTypeIdInternal() {
return JcrTypeManager.FOLDER_TYPE_ID;
}
public static void setProperties(Node node, TypeDefinition type, Properties properties) {
if (properties == null || properties.getProperties() == null) {
throw new CmisConstraintException("No properties!");
}
Set<String> addedProps = new HashSet<String>();
try {
// check if all required properties are there
for (PropertyData<?> prop : properties.getProperties().values()) {
PropertyDefinition<?> propDef = type.getPropertyDefinitions().get(prop.getId());
// do we know that property?
if (propDef == null) {
throw new CmisConstraintException("Property '" + prop.getId() + "' is unknown!");
}
// skip type id
if (propDef.getId().equals(PropertyIds.OBJECT_TYPE_ID)) {
log.warn("Cannot set " + PropertyIds.OBJECT_TYPE_ID + ". Ignoring");
addedProps.add(prop.getId());
continue;
}
// skip content stream file name
if (propDef.getId().equals(PropertyIds.CONTENT_STREAM_FILE_NAME)) {
log.warn("Cannot set " + PropertyIds.CONTENT_STREAM_FILE_NAME + ". Ignoring");
addedProps.add(prop.getId());
continue;
}
// can it be set?
if (propDef.getUpdatability() == Updatability.READONLY) {
throw new CmisConstraintException("Property '" + prop.getId() + "' is readonly!");
}
// empty properties are invalid
if (PropertyHelper.isPropertyEmpty(prop)) {
throw new CmisConstraintException("Property '" + prop.getId() + "' must not be empty!");
}
// add it
JcrConverter.setProperty(node, prop);
addedProps.add(prop.getId());
}
// check if required properties are missing and try to add default
// values if defined
for (PropertyDefinition<?> propDef : type.getPropertyDefinitions().values()) {
if (!addedProps.contains(propDef.getId()) && propDef.getUpdatability() != Updatability.READONLY) {
PropertyData<?> prop = PropertyHelper.getDefaultValue(propDef);
if (prop == null && propDef.isRequired()) {
throw new CmisConstraintException("Property '" + propDef.getId() + "' is required!");
} else if (prop != null) {
JcrConverter.setProperty(node, prop);
}
}
}
} catch (RepositoryException e) {
log.debug(e.getMessage(), e);
throw new CmisStorageException(e.getMessage(), e);
}
}
// ------------------------------------------< private >---
private static boolean hasCheckOuts(Node node) throws RepositoryException {
// Build xpath query of the form
// '//path/to/node//*[jcr:isCheckedOut='true']'
String xPath = "/*[jcr:isCheckedOut='true']";
String path = node.getPath();
if ("/".equals(path)) {
path = "";
}
xPath = '/' + Util.escape(path) + xPath;
// Execute query
QueryManager queryManager = node.getSession().getWorkspace().getQueryManager();
Query query = queryManager.createQuery(xPath, Query.XPATH);
QueryResult queryResult = query.execute();
return queryResult.getNodes().hasNext();
}
/**
* Add property "cmis:allowedChildObjectTypeIds" to the CMIS object. See
* CMIS specification v.1.0, 2.1.5.4.2 Property Definitions.
*
* @param properties
* - the properties of the CMIS object represented by this
* instance.
* @param filter
* @param typeId
* - type ID of the instance.
*/
private void addPropertyAllowedChildObjectTypeIds(PropertiesImpl properties, Set<String> filter, String typeId) {
Iterator<TypeDefinitionContainer> typeDefIterator = super.typeManager.getTypeDefinitionList().iterator();
List<String> typeIds = new ArrayList<String>(super.typeManager.getTypeDefinitionList().size());
while (typeDefIterator.hasNext()) {
TypeDefinitionContainer definition = typeDefIterator.next();
typeIds.add(definition.getTypeDefinition().getId());
}
addPropertyList(properties, typeId, filter, PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS, typeIds);
}
}