This project has retired. For details please refer to its Attic page.
JcrFolder xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.chemistry.opencmis.jcr;
21  
22  import org.apache.chemistry.opencmis.commons.PropertyIds;
23  import org.apache.chemistry.opencmis.commons.data.Properties;
24  import org.apache.chemistry.opencmis.commons.data.PropertyData;
25  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
26  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
27  import org.apache.chemistry.opencmis.commons.enums.Action;
28  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
29  import org.apache.chemistry.opencmis.commons.enums.Updatability;
30  import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
31  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
32  import org.apache.chemistry.opencmis.commons.exceptions.CmisStorageException;
33  import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
34  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
35  import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl;
36  import org.apache.chemistry.opencmis.jcr.type.JcrTypeHandlerManager;
37  import org.apache.chemistry.opencmis.jcr.util.FilterIterator;
38  import org.apache.chemistry.opencmis.jcr.util.Predicate;
39  import org.apache.chemistry.opencmis.jcr.util.Util;
40  import org.apache.commons.logging.Log;
41  import org.apache.commons.logging.LogFactory;
42  
43  import javax.jcr.Node;
44  import javax.jcr.RepositoryException;
45  import javax.jcr.Session;
46  import javax.jcr.query.Query;
47  import javax.jcr.query.QueryManager;
48  import javax.jcr.query.QueryResult;
49  import javax.jcr.version.Version;
50  import java.util.Collections;
51  import java.util.HashSet;
52  import java.util.Iterator;
53  import java.util.Set;
54  
55  
56  /**
57   * Instances of this class represent a cmis:folder backed by an underlying JCR <code>Node</code>. 
58   */
59  public class JcrFolder extends JcrNode {
60      private static final Log log = LogFactory.getLog(JcrFolder.class);
61  
62      public JcrFolder(Node node, JcrTypeManager typeManager, PathManager pathManager, JcrTypeHandlerManager typeHandlerManager) {
63          super(node, typeManager, pathManager, typeHandlerManager);
64      }
65  
66      /**
67       * See CMIS 1.0 section 2.2.3.1 getChildren
68       * 
69       * @return  Iterator of <code>JcrNode</code>. Children which are created in the checked out
70       *      state are left out from the iterator.
71       * @throws CmisRuntimeException
72       */
73      public Iterator<JcrNode> getNodes() {
74          try {
75              final FilterIterator<Node> nodes = new FilterIterator<Node>(getNode().getNodes(), typeHandlerManager.getNodePredicate());
76  
77              Iterator<JcrNode> jcrNodes = new Iterator<JcrNode>() {
78                  public boolean hasNext() {
79                      return nodes.hasNext();
80                  }
81  
82                  public JcrNode next() {
83                      return create(nodes.next());
84                  }
85  
86                  public void remove() {
87                      throw new UnsupportedOperationException();
88                  }
89              };
90  
91              // Filter out nodes which are checked out and do not have a version history (i.e. only a root version)
92              // These are created with VersioningState checkedout and not yet checked in.
93              return new FilterIterator<JcrNode>(jcrNodes, new Predicate<JcrNode>() {
94                  public boolean evaluate(JcrNode node) {
95                      try {
96                          if (node.isVersionable()) {
97                              Version baseVersion = getBaseVersion(node.getNode());
98                              return baseVersion.getPredecessors().length > 0;
99                          }
100                         else {
101                             return true;
102                         }
103                     }
104                     catch (RepositoryException e) {
105                         log.debug(e.getMessage(), e);
106                         throw new CmisRuntimeException(e.getMessage(), e);
107                     }
108                 }
109             });
110 
111         }
112         catch (RepositoryException e) {
113             log.debug(e.getMessage(), e);
114             throw new CmisRuntimeException(e.getMessage(), e);
115         }
116     }
117 
118     /**
119      * See CMIS 1.0 section 2.2.4.2 createDocumentFromSource
120      *
121      * @throws CmisStorageException
122      */
123     public JcrNode addNodeFromSource(JcrDocument source, Properties properties) {
124         try {
125             String destPath = PathManager.createCmisPath(getNode().getPath(), source.getName());
126             Session session = getNode().getSession();
127 
128             session.getWorkspace().copy(source.getNode().getPath(), destPath);  
129             JcrNode jcrNode = create(session.getNode(destPath));
130 
131             // overlay new properties
132             if (properties != null && properties.getProperties() != null) {
133                 updateProperties(jcrNode.getNode(), jcrNode.getTypeId(), properties);
134             }
135 
136             session.save();
137             return jcrNode;
138         }
139         catch (RepositoryException e) {
140             log.debug(e.getMessage(), e);
141             throw new CmisStorageException(e.getMessage(), e);
142         }
143     }
144 
145     /**
146      * See CMIS 1.0 section 2.2.4.14 deleteObject
147      *
148      * @throws CmisRuntimeException
149      */
150     @Override
151     public void delete(boolean allVersions, boolean isPwc) {
152         try {
153             if (getNode().hasNodes()) {
154                 throw new CmisConstraintException("Folder is not empty!");
155             }
156             else {
157                 super.delete(allVersions, isPwc);
158             }
159         }
160         catch (RepositoryException e) {
161             log.debug(e.getMessage(), e);
162             throw new CmisRuntimeException(e.getMessage(), e);
163         }
164     }
165 
166     /**
167      * See CMIS 1.0 section 2.2.4.15 deleteTree
168      */
169     public FailedToDeleteDataImpl deleteTree() {
170         FailedToDeleteDataImpl result = new FailedToDeleteDataImpl();
171 
172         String id = getId();
173         try {
174             Node node = getNode();
175             if (hasCheckOuts(node)) {
176                 result.setIds(Collections.<String>singletonList(id));                
177             }
178             else {
179                 Session session = node.getSession();
180                 node.remove();
181                 session.save();
182                 result.setIds(Collections.<String>emptyList());
183             }
184         }
185         catch (RepositoryException e) {
186             result.setIds(Collections.singletonList(id));
187         }
188 
189         return result;
190     }
191 
192     //------------------------------------------< protected >---
193 
194     @Override
195     protected void compileProperties(PropertiesImpl properties, Set<String> filter, ObjectInfoImpl objectInfo)
196             throws RepositoryException {
197 
198         super.compileProperties(properties, filter, objectInfo);
199 
200         objectInfo.setHasContent(false);
201         objectInfo.setSupportsDescendants(true);
202         objectInfo.setSupportsFolderTree(true);
203 
204         String typeId = getTypeIdInternal();
205 
206         addPropertyString(properties, typeId, filter, PropertyIds.PATH, pathManager.getPath(getNode()));
207 
208         // folder properties
209         if (pathManager.isRoot(getNode())) {
210             objectInfo.setHasParent(false);
211         }
212         else {
213             objectInfo.setHasParent(true);
214             addPropertyId(properties, typeId, filter, PropertyIds.PARENT_ID, getParent().getObjectId());
215         }
216     }
217 
218     @Override
219     protected Set<Action> compileAllowableActions(Set<Action> aas) {
220         Set<Action> result = super.compileAllowableActions(aas);
221         setAction(result, Action.CAN_GET_DESCENDANTS, true);
222         setAction(result, Action.CAN_GET_CHILDREN, true);
223         setAction(result, Action.CAN_GET_FOLDER_PARENT, !pathManager.isRoot(getNode()));
224         setAction(result, Action.CAN_GET_OBJECT_PARENTS, !pathManager.isRoot(getNode()));
225         setAction(result, Action.CAN_GET_FOLDER_TREE, true);
226         setAction(result, Action.CAN_CREATE_DOCUMENT, true);
227         setAction(result, Action.CAN_CREATE_FOLDER, true);
228         setAction(result, Action.CAN_DELETE_TREE, true);
229         return result;
230     }
231 
232     @Override
233     protected Node getContextNode() {
234         return getNode();
235     }
236 
237     @Override
238     protected String getObjectId() throws RepositoryException {
239         return isRoot()
240                 ? PathManager.CMIS_ROOT_ID
241                 : super.getObjectId();
242     }
243 
244     @Override
245     protected BaseTypeId getBaseTypeId() {
246         return BaseTypeId.CMIS_FOLDER;
247     }
248 
249     @Override
250     protected String getTypeIdInternal() {
251         return JcrTypeManager.FOLDER_TYPE_ID;
252     }
253 
254     public static void setProperties(Node node, TypeDefinition type, Properties properties) {
255         if (properties == null || properties.getProperties() == null) {
256             throw new CmisConstraintException("No properties!");
257         }
258 
259         Set<String> addedProps = new HashSet<String>();
260 
261         try {
262             // check if all required properties are there
263             for (PropertyData<?> prop : properties.getProperties().values()) {
264                 PropertyDefinition<?> propDef = type.getPropertyDefinitions().get(prop.getId());
265 
266                 // do we know that property?
267                 if (propDef == null) {
268                     throw new CmisConstraintException("Property '" + prop.getId() + "' is unknown!");
269                 }
270 
271                 // skip type id
272                 if (propDef.getId().equals(PropertyIds.OBJECT_TYPE_ID)) {
273                     log.warn("Cannot set " + PropertyIds.OBJECT_TYPE_ID + ". Ignoring");
274                     addedProps.add(prop.getId());
275                     continue;
276                 }
277 
278                 // skip content stream file name
279                 if (propDef.getId().equals(PropertyIds.CONTENT_STREAM_FILE_NAME)) {
280                     log.warn("Cannot set " + PropertyIds.CONTENT_STREAM_FILE_NAME + ". Ignoring");
281                     addedProps.add(prop.getId());
282                     continue;
283                 }
284 
285                 // can it be set?
286                 if (propDef.getUpdatability() == Updatability.READONLY) {
287                     throw new CmisConstraintException("Property '" + prop.getId() + "' is readonly!");
288                 }
289 
290                 // empty properties are invalid
291                 if (PropertyHelper.isPropertyEmpty(prop)) {
292                     throw new CmisConstraintException("Property '" + prop.getId() + "' must not be empty!");
293                 }
294 
295                 // add it
296                 JcrConverter.setProperty(node, prop);
297                 addedProps.add(prop.getId());
298             }
299 
300             // check if required properties are missing and try to add default values if defined
301             for (PropertyDefinition<?> propDef : type.getPropertyDefinitions().values()) {
302                 if (!addedProps.contains(propDef.getId()) && propDef.getUpdatability() != Updatability.READONLY) {
303                     PropertyData<?> prop = PropertyHelper.getDefaultValue(propDef);
304                     if (prop == null && propDef.isRequired()) {
305                         throw new CmisConstraintException("Property '" + propDef.getId() + "' is required!");
306                     }
307                     else if (prop != null) {
308                         JcrConverter.setProperty(node, prop);
309                     }
310                 }
311             }
312         }
313         catch (RepositoryException e) {
314             log.debug(e.getMessage(), e);
315             throw new CmisStorageException(e.getMessage(), e);
316         }
317     }
318 
319     //------------------------------------------< private >---
320 
321     private static boolean hasCheckOuts(Node node) throws RepositoryException {
322         // Build xpath query of the form
323         // '//path/to/node//*[jcr:isCheckedOut='true']'
324         String xPath = "/*[jcr:isCheckedOut='true']";
325         String path = node.getPath();
326         if ("/".equals(path)) {
327             path = "";
328         }
329         xPath = '/' + Util.escape(path) + xPath;
330 
331         // Execute query
332         QueryManager queryManager = node.getSession().getWorkspace().getQueryManager();
333         Query query = queryManager.createQuery(xPath, Query.XPATH);
334         QueryResult queryResult = query.execute();
335         return queryResult.getNodes().hasNext();
336     }
337     
338 }