View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.document.modules.cmis.service;
35  
36  import fr.paris.lutece.plugins.document.business.Document;
37  import fr.paris.lutece.plugins.document.business.spaces.DocumentSpace;
38  import fr.paris.lutece.portal.service.util.AppLogService;
39  
40  import org.apache.chemistry.opencmis.commons.PropertyIds;
41  import org.apache.chemistry.opencmis.commons.data.*;
42  import org.apache.chemistry.opencmis.commons.definitions.PermissionDefinition;
43  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
44  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
45  import org.apache.chemistry.opencmis.commons.enums.*;
46  import org.apache.chemistry.opencmis.commons.exceptions.*;
47  import org.apache.chemistry.opencmis.commons.impl.dataobjects.*;
48  import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl;
49  import org.apache.chemistry.opencmis.commons.server.CallContext;
50  import org.apache.chemistry.opencmis.commons.server.ObjectInfoHandler;
51  
52  import org.apache.tika.mime.MimeTypes;
53  
54  import java.io.*;
55  
56  import java.math.BigInteger;
57  
58  import java.util.*;
59  
60  
61  /**
62   * Document Repository
63   */
64  public class DocumentRepository extends BaseRepository
65  {
66      private static final String REPOSITORY_ID = "document";
67      private static final String CMIS_VERSION = "1.0";
68      private static final String PRODUCT_NAME = "CMIS document module";
69      private static final String PRODUCT_VERSION = "1.0.0";
70      private static final String VENDOR_NAME = "Lutece";
71      private static final String ROOT_ID = "@root@";
72      private static final String CREATED_BY = "Lutece";
73      private static final String CMIS_READ = "cmis:read";
74      private static final String CMIS_WRITE = "cmis:write";
75      private static final String CMIS_ALL = "cmis:all";
76      private static final String ROOT_SPACE_ID = "S0";
77      private static final String PREFIX_DOC = "D";
78      private static final String PREFIX_SPACE = "S";
79      private static final String MIME_TYPE_XML = MimeTypes.PLAIN_TEXT;
80  
81      /**
82       * Types
83       */
84      private final TypeManager types = new TypeManager(  );
85  
86      /**
87       * Get repository infos
88       * @return The infos
89       */
90      public RepositoryInfo getInfos(  )
91      {
92          RepositoryInfoImpl repositoryInfo = new RepositoryInfoImpl(  );
93  
94          repositoryInfo.setId( REPOSITORY_ID );
95          repositoryInfo.setName( REPOSITORY_ID );
96          repositoryInfo.setDescription( REPOSITORY_ID );
97  
98          repositoryInfo.setCmisVersionSupported( CMIS_VERSION );
99  
100         repositoryInfo.setProductName( PRODUCT_NAME );
101         repositoryInfo.setProductVersion( PRODUCT_VERSION );
102         repositoryInfo.setVendorName( VENDOR_NAME );
103 
104         repositoryInfo.setRootFolder( ROOT_ID );
105 
106         repositoryInfo.setThinClientUri( "" );
107 
108         RepositoryCapabilitiesImpl capabilities = new RepositoryCapabilitiesImpl(  );
109         capabilities.setCapabilityAcl( CapabilityAcl.DISCOVER );
110         capabilities.setAllVersionsSearchable( false );
111         capabilities.setCapabilityJoin( CapabilityJoin.NONE );
112         capabilities.setSupportsMultifiling( false );
113         capabilities.setSupportsUnfiling( false );
114         capabilities.setSupportsVersionSpecificFiling( false );
115         capabilities.setIsPwcSearchable( false );
116         capabilities.setIsPwcUpdatable( false );
117         capabilities.setCapabilityQuery( CapabilityQuery.NONE );
118         capabilities.setCapabilityChanges( CapabilityChanges.NONE );
119         capabilities.setCapabilityContentStreamUpdates( CapabilityContentStreamUpdates.ANYTIME );
120         capabilities.setSupportsGetDescendants( true );
121         capabilities.setSupportsGetFolderTree( true );
122         capabilities.setCapabilityRendition( CapabilityRenditions.NONE );
123 
124         repositoryInfo.setCapabilities( capabilities );
125 
126         AclCapabilitiesDataImpl aclCapability = new AclCapabilitiesDataImpl(  );
127         aclCapability.setSupportedPermissions( SupportedPermissions.BASIC );
128         aclCapability.setAclPropagation( AclPropagation.OBJECTONLY );
129 
130         // permissions
131         List<PermissionDefinition> permissions = new ArrayList<PermissionDefinition>(  );
132         permissions.add( createPermission( CMIS_READ, "Read" ) );
133         permissions.add( createPermission( CMIS_WRITE, "Write" ) );
134         permissions.add( createPermission( CMIS_ALL, "All" ) );
135         aclCapability.setPermissionDefinitionData( permissions );
136 
137         // mapping
138         List<PermissionMapping> list = new ArrayList<PermissionMapping>(  );
139         list.add( createMapping( PermissionMapping.CAN_CREATE_DOCUMENT_FOLDER, CMIS_READ ) );
140         list.add( createMapping( PermissionMapping.CAN_CREATE_FOLDER_FOLDER, CMIS_READ ) );
141         list.add( createMapping( PermissionMapping.CAN_DELETE_CONTENT_DOCUMENT, CMIS_WRITE ) );
142         list.add( createMapping( PermissionMapping.CAN_DELETE_OBJECT, CMIS_ALL ) );
143         list.add( createMapping( PermissionMapping.CAN_DELETE_TREE_FOLDER, CMIS_ALL ) );
144         list.add( createMapping( PermissionMapping.CAN_GET_ACL_OBJECT, CMIS_READ ) );
145         list.add( createMapping( PermissionMapping.CAN_GET_ALL_VERSIONS_VERSION_SERIES, CMIS_READ ) );
146         list.add( createMapping( PermissionMapping.CAN_GET_CHILDREN_FOLDER, CMIS_READ ) );
147         list.add( createMapping( PermissionMapping.CAN_GET_DESCENDENTS_FOLDER, CMIS_READ ) );
148         list.add( createMapping( PermissionMapping.CAN_GET_FOLDER_PARENT_OBJECT, CMIS_READ ) );
149         list.add( createMapping( PermissionMapping.CAN_GET_PARENTS_FOLDER, CMIS_READ ) );
150         list.add( createMapping( PermissionMapping.CAN_GET_PROPERTIES_OBJECT, CMIS_READ ) );
151         list.add( createMapping( PermissionMapping.CAN_MOVE_OBJECT, CMIS_WRITE ) );
152         list.add( createMapping( PermissionMapping.CAN_MOVE_SOURCE, CMIS_READ ) );
153         list.add( createMapping( PermissionMapping.CAN_MOVE_TARGET, CMIS_WRITE ) );
154         list.add( createMapping( PermissionMapping.CAN_SET_CONTENT_DOCUMENT, CMIS_WRITE ) );
155         list.add( createMapping( PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT, CMIS_WRITE ) );
156         list.add( createMapping( PermissionMapping.CAN_VIEW_CONTENT_OBJECT, CMIS_READ ) );
157 
158         Map<String, PermissionMapping> map = new LinkedHashMap<String, PermissionMapping>(  );
159 
160         for ( PermissionMapping pm : list )
161         {
162             map.put( pm.getKey(  ), pm );
163         }
164 
165         aclCapability.setPermissionMappingData( map );
166 
167         repositoryInfo.setAclCapabilities( aclCapability );
168 
169         return repositoryInfo;
170     }
171 
172     /**
173      * CMIS getTypesChildren.
174      *
175      * @param context The context
176      * @param typeId The type id
177      * @param includePropertyDefinitions include property definition
178      * @param maxItems Max items
179      * @param skipCount Skip count
180      * @return The list
181      */
182     public TypeDefinitionList getTypesChildren( CallContext context, String typeId, boolean includePropertyDefinitions,
183         BigInteger maxItems, BigInteger skipCount )
184     {
185         return types.getTypesChildren( context, typeId, includePropertyDefinitions, maxItems, skipCount );
186     }
187 
188     /**
189      * CMIS getTypeDefinition.
190      *
191      * @param context The context
192      * @param typeId The type id
193      * @return
194      */
195     public TypeDefinition getTypeDefinition( CallContext context, String typeId )
196     {
197         return types.getTypeDefinition( context, typeId );
198     }
199 
200     /**
201      * Get children
202      * @param context The context
203      * @param folderId The folder id
204      * @param filter Filters
205      * @param orderBy The order
206      * @param includeAllowableActions include actions
207      * @param includeRelationships include relationship
208      * @param renditionFilter filters
209      * @param includePathSegment include path segment
210      * @param maxItems max items
211      * @param skipCount skip count  
212      * @param extension extension
213      * @param objectInfos object infos
214      * @return The list of children
215      */
216     public ObjectInFolderList getChildren( CallContext context, String folderId, String filter, String orderBy,
217         Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
218         Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension,
219         ObjectInfoHandler objectInfos )
220     {
221         ObjectInFolderListImpl result = new ObjectInFolderListImpl(  );
222         result.setObjects( new ArrayList<ObjectInFolderData>(  ) );
223         result.setHasMoreItems( false );
224 
225         int count = 0;
226 
227         // skip and max
228         int skip = ( ( skipCount == null ) ? 0 : skipCount.intValue(  ) );
229 
230         if ( skip < 0 )
231         {
232             skip = 0;
233         }
234 
235         int max = ( ( maxItems == null ) ? Integer.MAX_VALUE : maxItems.intValue(  ) );
236 
237         if ( max < 0 )
238         {
239             max = Integer.MAX_VALUE;
240         }
241 
242         if ( folderId.equalsIgnoreCase( ROOT_ID ) )
243         {
244             folderId = ROOT_SPACE_ID;
245         }
246 
247         RepositoryObject object = new RepositoryObject( folderId );
248 
249         if ( object.isDocument(  ) )
250         {
251             return result;
252         }
253 
254         String folderPath = object.getName(  );
255 
256         List<Document> listDocuments = object.getDocumentChildren(  );
257 
258         // iterate through children
259         for ( Document document : listDocuments )
260         {
261             // skip hidden and shadow files
262             if ( document.isOutOfDate(  ) || !document.isValid(  ) )
263             {
264                 continue;
265             }
266 
267             count++;
268 
269             if ( skip > 0 )
270             {
271                 skip--;
272 
273                 continue;
274             }
275 
276             if ( result.getObjects(  ).size(  ) >= max )
277             {
278                 result.setHasMoreItems( true );
279 
280                 continue;
281             }
282 
283             // build and add child object
284             ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl(  );
285             objectInFolder.setObject( getObject( context, PREFIX_DOC + document.getId(  ), filter,
286                     includeAllowableActions, includeRelationships, renditionFilter, includePathSegment,
287                     includePathSegment, extension, objectInfos, folderPath ) );
288 
289             result.getObjects(  ).add( objectInFolder );
290         }
291 
292         List<DocumentSpace> listSpaces = object.getSpaceChildren(  );
293 
294         // iterate through children
295         for ( DocumentSpace space : listSpaces )
296         {
297             count++;
298 
299             if ( skip > 0 )
300             {
301                 skip--;
302 
303                 continue;
304             }
305 
306             if ( result.getObjects(  ).size(  ) >= max )
307             {
308                 result.setHasMoreItems( true );
309 
310                 continue;
311             }
312 
313             // build and add child object
314             ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl(  );
315             objectInFolder.setObject( getObject( context, PREFIX_SPACE + space.getId(  ), filter,
316                     includeAllowableActions, includeRelationships, renditionFilter, includePathSegment,
317                     includePathSegment, extension, objectInfos, folderPath ) );
318 
319             result.getObjects(  ).add( objectInFolder );
320         }
321 
322         result.setNumItems( BigInteger.valueOf( count ) );
323 
324         return result;
325     }
326 
327     /**
328      * Get Object
329      * 
330      * @param context The context
331      * @param objectId The object's ID
332      * @param filter Filters
333      * @param includeAllowableActions include actions
334      * @param includeRelationships include relations
335      * @param renditionFilter use filters
336      * @param includePolicyIds include policies
337      * @param includeAcl include ACL
338      * @param extension extension
339      * @param objectInfo Object infos
340      * @return The object
341      */
342     public ObjectData getObject( CallContext context, String objectId, String filter, Boolean includeAllowableActions,
343         IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
344         Boolean includeAcl, ExtensionsData extension, ObjectInfoHandler objectInfo )
345     {
346         return getObject( context, objectId, filter, includeAllowableActions, includeRelationships, renditionFilter,
347             includePolicyIds, includeAcl, extension, objectInfo, "/" );
348     }
349 
350     /**
351      * Get Object
352      * 
353      * @param context The context
354      * @param objectId The object's ID
355      * @param filter Filters
356      * @param includeAllowableActions include actions
357      * @param includeRelationships include relations
358      * @param renditionFilter use filters
359      * @param includePolicyIds include policies
360      * @param includeAcl include ACL
361      * @param extension extension
362      * @param objectInfo Object infos
363      * @param folderPath The folder path
364      * @return The object
365      */
366     private ObjectData getObject( CallContext context, String objectId, String filter, Boolean includeAllowableActions,
367         IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
368         Boolean includeAcl, ExtensionsData extension, ObjectInfoHandler objectInfo, String folderPath )
369     {
370         if ( objectId.equalsIgnoreCase( ROOT_ID ) )
371         {
372             objectId = ROOT_SPACE_ID;
373         }
374 
375         RepositoryObject object = new RepositoryObject( objectId );
376 
377         return compileObjectType( context, object, null, true, true, true, objectInfo, folderPath );
378     }
379 
380     /**
381      * CMIS getObjectByPath.
382      *
383      * @param context The context
384      * @param folderPath The folder path
385      * @param includeAllowableActions include actions
386      * @param filter Filters
387      * @param objectInfos object infos
388      * @param includeACL include ACL
389      * @return The object
390      */
391     public ObjectData getObjectByPath( CallContext context, String folderPath, String filter,
392         boolean includeAllowableActions, boolean includeACL, ObjectInfoHandler objectInfos )
393     {
394         boolean userReadOnly = true;
395 
396         // split filter
397         Set<String> filterCollection = splitFilter( filter );
398 
399         // check path
400         if ( ( folderPath == null ) || ( !folderPath.startsWith( "/" ) ) )
401         {
402             throw new CmisInvalidArgumentException( "Invalid folder path!" );
403         }
404 
405         RepositoryObject object = new RepositoryObject( ROOT_SPACE_ID );
406 
407         return compileObjectType( context, object, filterCollection, includeAllowableActions, includeACL, userReadOnly,
408             objectInfos, folderPath );
409     }
410 
411     /**
412      * CMIS getContentStream.
413      *
414      * @param context The context
415      * @param objectId The object id
416      * @param length The length
417      * @param offset The offset
418      * @return The content stream
419      */
420     public ContentStream getContentStream( CallContext context, String objectId, BigInteger offset, BigInteger length )
421     {
422         if ( ( offset != null ) || ( length != null ) )
423         {
424             throw new CmisInvalidArgumentException( "Offset and Length are not supported!" );
425         }
426 
427         RepositoryObject object = new RepositoryObject( objectId );
428 
429         Document document = object.getDocument(  );
430 
431         if ( document == null )
432         {
433             throw new CmisStreamNotSupportedException( "Document not found" );
434         }
435 
436         String xml = document.getXmlValidatedContent(  );
437 
438         if ( xml == null )
439         {
440             xml = document.getXmlWorkingContent(  );
441         }
442 
443         byte[] bytes = xml.getBytes(  );
444         InputStream stream = new ByteArrayInputStream( bytes );
445         ContentStreamImpl result = new ContentStreamImpl(  );
446         result.setFileName( document.getTitle(  ) );
447         result.setLength( BigInteger.valueOf( bytes.length ) );
448         result.setMimeType( "application/xml" );
449         result.setStream( stream );
450 
451         return result;
452     }
453 
454 
455     /**
456      * Get Descendants 
457      * 
458      * @param context The context
459      * @param folderId The folder's ID
460      * @param depth the depth
461      * @param filter Filters
462      * @param includeAllowableActions include actions
463      * @param includePathSegment include path segment
464      * @param objectInfos Objects infos
465      * @param userReadOnly user Read Only
466      * @return The descendants
467      */
468     public List<ObjectInFolderContainer> getDescendants( CallContext context, String folderId, BigInteger depth,
469         String filter, Boolean includeAllowableActions, Boolean includePathSegment, ObjectInfoHandler objectInfos,
470         boolean userReadOnly )
471     {
472         RepositoryObject object = new RepositoryObject( folderId );
473         String folderPath = "/" + object.getName(  );
474 
475         // check depth
476         int d = ( ( depth == null ) ? 2 : depth.intValue(  ) );
477 
478         if ( d == 0 )
479         {
480             throw new CmisInvalidArgumentException( "Depth must not be 0!" );
481         }
482 
483         if ( d < -1 )
484         {
485             d = -1;
486         }
487 
488         // split filter
489         Set<String> filterCollection = splitFilter( filter );
490 
491         boolean foldersOnly = true;
492         List<ObjectInFolderContainer> result = new ArrayList<ObjectInFolderContainer>(  );
493 
494         // set defaults if values not set
495         boolean iaa = ( ( includeAllowableActions == null ) ? false : includeAllowableActions.booleanValue(  ) );
496         boolean ips = ( ( includePathSegment == null ) ? false : includePathSegment.booleanValue(  ) );
497 
498         if ( context.isObjectInfoRequired(  ) )
499         {
500             compileObjectType( context, object, null, false, false, userReadOnly, objectInfos, folderPath );
501         }
502 
503         AppLogService.debug( "object=" + object );
504 
505         gatherDescendants( context, object, result, foldersOnly, d, filterCollection, iaa, ips, userReadOnly,
506             objectInfos, folderPath );
507 
508         return result;
509     }
510 
511     private ObjectData compileObjectType( CallContext context, RepositoryObject object, Set<String> filter,
512         boolean includeAllowableActions, boolean includeAcl, boolean userReadOnly, ObjectInfoHandler objectInfos,
513         String folderPath )
514     {
515         ObjectDataImpl result = new ObjectDataImpl(  );
516         ObjectInfoImpl objectInfo = new ObjectInfoImpl(  );
517 
518         result.setProperties( compileProperties( object, filter, objectInfo, folderPath ) );
519 
520         /*
521          * if (includeAllowableActions) {
522          * result.setAllowableActions(compileAllowableActions( document,
523          * userReadOnly)); }
524          *
525          * if (includeAcl) { result.setAcl(compileAcl(file));
526          * result.setIsExactAcl(true); }
527          */
528         if ( context.isObjectInfoRequired(  ) )
529         {
530             objectInfo.setObject( result );
531             objectInfos.addObjectInfo( objectInfo );
532         }
533 
534         return result;
535     }
536 
537     private org.apache.chemistry.opencmis.commons.data.Properties compileProperties( RepositoryObject object,
538         Set<String> orgfilter, ObjectInfoImpl objectInfo, String folderPath )
539     {
540         if ( object == null )
541         {
542             throw new IllegalArgumentException( "Document must not be null!" );
543         }
544 
545         // copy filter
546         Set<String> filter = ( ( orgfilter == null ) ? null : new HashSet<String>( orgfilter ) );
547 
548         // find base type
549         String typeId = null;
550 
551         try
552         {
553             PropertiesImpl result = new PropertiesImpl(  );
554 
555             String id = object.getId(  );
556             String name = object.getName(  );
557 
558             objectInfo.setId( id );
559             objectInfo.setName( name );
560             objectInfo.setCreatedBy( CREATED_BY );
561             objectInfo.setHasAcl( false );
562             objectInfo.setHasParent( true );
563             objectInfo.setVersionSeriesId( null );
564             objectInfo.setIsCurrentVersion( true );
565             objectInfo.setRelationshipSourceIds( null );
566             objectInfo.setRelationshipTargetIds( null );
567             objectInfo.setRenditionInfos( null );
568             objectInfo.setSupportsPolicies( false );
569             objectInfo.setSupportsRelationships( false );
570             objectInfo.setWorkingCopyId( null );
571             objectInfo.setWorkingCopyOriginalId( null );
572 
573             addPropertyId( result, typeId, filter, PropertyIds.OBJECT_ID, id );
574             addPropertyString( result, typeId, filter, PropertyIds.NAME, name );
575             addPropertyString( result, typeId, filter, PropertyIds.CREATED_BY, CREATED_BY );
576             addPropertyString( result, typeId, filter, PropertyIds.LAST_MODIFIED_BY, CREATED_BY );
577             addPropertyString( result, typeId, filter, PropertyIds.PATH, folderPath );
578             addPropertyString( result, typeId, filter, PropertyIds.CHANGE_TOKEN, null );
579 
580             if ( object.isDocument(  ) )
581             {
582                 Document doc = object.getDocument(  );
583                 typeId = TypeManager.DOCUMENT_TYPE_ID;
584 
585                 GregorianCalendar created = millisToCalendar( object.getDocument(  ).getDateCreation(  ).getTime(  ) );
586                 GregorianCalendar lastModified = millisToCalendar( object.getDocument(  ).getDateModification(  )
587                                                                          .getTime(  ) );
588 
589                 objectInfo.setBaseType( BaseTypeId.CMIS_DOCUMENT );
590                 objectInfo.setTypeId( typeId );
591                 objectInfo.setSupportsDescendants( false );
592                 objectInfo.setSupportsFolderTree( false );
593                 objectInfo.setHasContent( true );
594                 objectInfo.setContentType( MIME_TYPE_XML );
595                 objectInfo.setFileName( doc.getTitle(  ) );
596                 objectInfo.setCreationDate( created );
597                 objectInfo.setLastModificationDate( lastModified );
598 
599                 addPropertyId( result, typeId, filter, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value(  ) );
600                 addPropertyId( result, typeId, filter, PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_DOCUMENT.value(  ) );
601                 addPropertyDateTime( result, typeId, filter, PropertyIds.CREATION_DATE, created );
602                 addPropertyDateTime( result, typeId, filter, PropertyIds.LAST_MODIFICATION_DATE, lastModified );
603                 addPropertyId( result, typeId, filter, PropertyIds.CONTENT_STREAM_MIME_TYPE, MIME_TYPE_XML );
604                 addPropertyId( result, typeId, filter, PropertyIds.CONTENT_STREAM_ID, object.getId(  ) );
605                 addPropertyId( result, typeId, filter, PropertyIds.CONTENT_STREAM_LENGTH,
606                     "" + doc.getXmlWorkingContent(  ).length(  ) );
607             }
608             else if ( object.isSpace(  ) )
609             {
610                 typeId = TypeManager.FOLDER_TYPE_ID;
611                 objectInfo.setBaseType( BaseTypeId.CMIS_FOLDER );
612                 objectInfo.setTypeId( typeId );
613                 objectInfo.setSupportsDescendants( true );
614                 objectInfo.setSupportsFolderTree( true );
615                 objectInfo.setHasContent( false );
616                 addPropertyId( result, typeId, filter, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_FOLDER.value(  ) );
617                 addPropertyId( result, typeId, filter, PropertyIds.OBJECT_TYPE_ID, BaseTypeId.CMIS_FOLDER.value(  ) );
618             }
619 
620             // read custom properties
621             // readCustomProperties(file, result, filter, objectInfo);
622             if ( filter != null )
623             {
624                 if ( !filter.isEmpty(  ) )
625                 {
626                     // debug("Unknown filter properties: " + filter.toString(), null);
627                 }
628             }
629 
630             return result;
631         }
632         catch ( Exception e )
633         {
634             if ( e instanceof CmisBaseException )
635             {
636                 throw (CmisBaseException) e;
637             }
638 
639             throw new CmisRuntimeException( e.getMessage(  ), e );
640         }
641     }
642 
643     /**
644      * Splits a filter statement into a collection of properties. If
645      * <code>filter</code> is
646      * <code>null</code>, empty or one of the properties is '*' , an empty
647      * collection will be returned.
648      */
649     private static Set<String> splitFilter( String filter )
650     {
651         if ( filter == null )
652         {
653             return null;
654         }
655 
656         if ( filter.trim(  ).length(  ) == 0 )
657         {
658             return null;
659         }
660 
661         Set<String> result = new HashSet<String>(  );
662 
663         for ( String s : filter.split( "," ) )
664         {
665             s = s.trim(  );
666 
667             if ( s.equals( "*" ) )
668             {
669                 return null;
670             }
671             else if ( s.length(  ) > 0 )
672             {
673                 result.add( s );
674             }
675         }
676 
677         // set a few base properties
678         // query name == id (for base type properties)
679         result.add( PropertyIds.OBJECT_ID );
680         result.add( PropertyIds.OBJECT_TYPE_ID );
681         result.add( PropertyIds.BASE_TYPE_ID );
682 
683         return result;
684     }
685 
686     private void gatherDescendants( CallContext context, RepositoryObject object, List<ObjectInFolderContainer> list,
687         boolean foldersOnly, int depth, Set<String> filter, boolean includeAllowableActions,
688         boolean includePathSegments, boolean userReadOnly, ObjectInfoHandler objectInfos, String folderPath )
689     {
690         // iterate through children
691         if ( object.getSpaceChildren(  ) == null )
692         {
693             AppLogService.debug( "No childs " );
694 
695             return;
696         }
697 
698         folderPath = folderPath + "/" + object.getName(  );
699 
700         for ( DocumentSpace space : object.getSpaceChildren(  ) )
701         {
702             AppLogService.debug( "child space " + space.getName(  ) );
703 
704             // add to list
705             ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl(  );
706             RepositoryObject child = new RepositoryObject( PREFIX_SPACE + space.getId(  ) );
707             objectInFolder.setObject( compileObjectType( context, child, filter, includeAllowableActions, false,
708                     userReadOnly, objectInfos, folderPath ) );
709 
710             if ( includePathSegments )
711             {
712                 objectInFolder.setPathSegment( space.getName(  ) );
713             }
714 
715             ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl(  );
716             container.setObject( objectInFolder );
717 
718             list.add( container );
719 
720             // move to next level
721             if ( ( depth != 1 ) )
722             {
723                 container.setChildren( new ArrayList<ObjectInFolderContainer>(  ) );
724                 gatherDescendants( context, child, container.getChildren(  ), foldersOnly, depth - 1, filter,
725                     includeAllowableActions, includePathSegments, userReadOnly, objectInfos, folderPath );
726             }
727         }
728 
729         // folders only?
730         if ( !foldersOnly )
731         {
732             for ( Document doc : object.getDocumentChildren(  ) )
733             {
734                 AppLogService.debug( "document " + doc.getTitle(  ) );
735 
736                 // add to list
737                 ObjectInFolderDataImpl objectInFolder = new ObjectInFolderDataImpl(  );
738                 RepositoryObject child = new RepositoryObject( PREFIX_DOC + doc.getId(  ) );
739                 objectInFolder.setObject( compileObjectType( context, child, filter, includeAllowableActions, false,
740                         userReadOnly, objectInfos, folderPath ) );
741 
742                 if ( includePathSegments )
743                 {
744                     objectInFolder.setPathSegment( doc.getTitle(  ) );
745                 }
746 
747                 ObjectInFolderContainerImpl container = new ObjectInFolderContainerImpl(  );
748                 container.setObject( objectInFolder );
749 
750                 list.add( container );
751 
752                 // move to next level
753                 if ( ( depth != 1 ) )
754                 {
755                     container.setChildren( new ArrayList<ObjectInFolderContainer>(  ) );
756                     gatherDescendants( context, child, container.getChildren(  ), foldersOnly, depth - 1, filter,
757                         includeAllowableActions, includePathSegments, userReadOnly, objectInfos, folderPath );
758                 }
759             }
760         }
761     }
762 }