View Javadoc
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   * Contributors:
20   *     Florian Mueller
21   *     Florent Guillaume, Nuxeo
22   */
23  package org.apache.chemistry.opencmis.server.impl.atompub;
24  
25  import org.apache.chemistry.opencmis.commons.data.ObjectData;
26  import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
27  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
28  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
29  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
30  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
31  import org.apache.chemistry.opencmis.commons.impl.Constants;
32  import org.apache.chemistry.opencmis.commons.impl.ReturnVersion;
33  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
34  import org.apache.chemistry.opencmis.commons.server.CmisService;
35  import org.apache.chemistry.opencmis.commons.server.ObjectInfo;
36  import org.apache.chemistry.opencmis.commons.server.RenditionInfo;
37  
38  import java.math.BigInteger;
39  
40  import java.util.GregorianCalendar;
41  import java.util.List;
42  
43  import javax.servlet.http.HttpServletRequest;
44  
45  import javax.xml.bind.JAXBException;
46  import javax.xml.stream.XMLStreamException;
47  
48  
49  /**
50   * This class contains operations used by all services.
51   */
52  public final class AtomPubUtils
53  {
54      public static final String RESOURCE_CHILDREN = "children";
55      public static final String RESOURCE_DESCENDANTS = "descendants";
56      public static final String RESOURCE_FOLDERTREE = "foldertree";
57      public static final String RESOURCE_TYPE = "type";
58      public static final String RESOURCE_TYPES = "types";
59      public static final String RESOURCE_TYPESDESC = "typedesc";
60      public static final String RESOURCE_ENTRY = "entry";
61      public static final String RESOURCE_PARENTS = "parents";
62      public static final String RESOURCE_VERSIONS = "versions";
63      public static final String RESOURCE_ALLOWABLEACIONS = "allowableactions";
64      public static final String RESOURCE_ACL = "acl";
65      public static final String RESOURCE_POLICIES = "policies";
66      public static final String RESOURCE_RELATIONSHIPS = "relationships";
67      public static final String RESOURCE_OBJECTBYID = "id";
68      public static final String RESOURCE_OBJECTBYPATH = "path";
69      public static final String RESOURCE_QUERY = "query";
70      public static final String RESOURCE_CHECKEDOUT = "checkedout";
71      public static final String RESOURCE_UNFILED = "unfiled";
72      public static final String RESOURCE_CHANGES = "changes";
73      public static final String RESOURCE_CONTENT = "content";
74      public static final BigInteger PAGE_SIZE = BigInteger.valueOf( 100 );
75      public static final String TYPE_AUTHOR = "unknown";
76  
77      /**
78       * Private constructor.
79       */
80      private AtomPubUtils(  )
81      {
82      }
83  
84      /**
85       * Compiles the base URL for links, collections and templates.
86       */
87      public static UrlBuilder compileBaseUrl( HttpServletRequest request, String repositoryId )
88      {
89          UrlBuilder url = new UrlBuilder( request.getScheme(  ), request.getServerName(  ), request.getServerPort(  ),
90                  null );
91  
92          url.addPath( request.getContextPath(  ) );
93          url.addPath( request.getServletPath(  ) );
94          url.addPath( "/document/cmis/atom/" ); // HACK PLE for plugin's servlet
95  
96          if ( repositoryId != null )
97          {
98              url.addPathSegment( repositoryId );
99          }
100 
101         return url;
102     }
103 
104     /**
105      * Compiles a URL for links, collections and templates.
106      */
107     public static String compileUrl( UrlBuilder baseUrl, String resource, String id )
108     {
109         return compileUrlBuilder( baseUrl, resource, id ).toString(  );
110     }
111 
112     /**
113      * Compiles a URL for links, collections and templates.
114      */
115     public static UrlBuilder compileUrlBuilder( UrlBuilder baseUrl, String resource, String id )
116     {
117         UrlBuilder url = new UrlBuilder( baseUrl );
118         url.addPathSegment( resource );
119 
120         if ( id != null )
121         {
122             url.addParameter( "id", id );
123         }
124 
125         return url;
126     }
127 
128     // -------------------------------------------------------------------------
129     // --- entry builder ---
130     // -------------------------------------------------------------------------
131 
132     /**
133      * Writes the a object entry.
134      */
135     public static void writeObjectEntry( CmisService service, AtomEntry entry, ObjectData object,
136         List<ObjectInFolderContainer> children, String repositoryId, String pathSegment, String relativePathSegment,
137         UrlBuilder baseUrl, boolean isRoot ) throws XMLStreamException, JAXBException
138     {
139         if ( object == null )
140         {
141             throw new CmisRuntimeException( "Object not set!" );
142         }
143 
144         ObjectInfo info = service.getObjectInfo( repositoryId, object.getId(  ) );
145 
146         if ( info == null )
147         {
148             throw new CmisRuntimeException( "Object Info not found for: " + object.getId(  ) );
149         }
150 
151         // start
152         entry.startEntry( isRoot );
153 
154         // write object
155         String contentSrc = null;
156 
157         if ( info.hasContent(  ) )
158         {
159             UrlBuilder contentSrcBuilder = compileUrlBuilder( baseUrl, RESOURCE_CONTENT, info.getId(  ) );
160 
161             if ( info.getFileName(  ) != null )
162             {
163                 contentSrcBuilder.addPathSegment( info.getFileName(  ) );
164             }
165 
166             contentSrc = contentSrcBuilder.toString(  );
167         }
168 
169         entry.writeObject( object, info, contentSrc, info.getContentType(  ), pathSegment, relativePathSegment );
170 
171         // write links
172         entry.writeServiceLink( baseUrl.toString(  ), repositoryId );
173 
174         entry.writeSelfLink( compileUrl( baseUrl, RESOURCE_ENTRY, info.getId(  ) ), info.getId(  ) );
175         entry.writeEnclosureLink( compileUrl( baseUrl, RESOURCE_ENTRY, info.getId(  ) ) );
176         entry.writeEditLink( compileUrl( baseUrl, RESOURCE_ENTRY, info.getId(  ) ) );
177         entry.writeDescribedByLink( compileUrl( baseUrl, RESOURCE_TYPE, info.getTypeId(  ) ) );
178         entry.writeAllowableActionsLink( compileUrl( baseUrl, RESOURCE_ALLOWABLEACIONS, info.getId(  ) ) );
179 
180         if ( info.hasParent(  ) )
181         {
182             entry.writeUpLink( compileUrl( baseUrl, RESOURCE_PARENTS, info.getId(  ) ), Constants.MEDIATYPE_FEED );
183         }
184 
185         if ( info.getBaseType(  ) == BaseTypeId.CMIS_FOLDER )
186         {
187             entry.writeDownLink( compileUrl( baseUrl, RESOURCE_CHILDREN, info.getId(  ) ), Constants.MEDIATYPE_FEED );
188 
189             if ( info.supportsDescendants(  ) )
190             {
191                 entry.writeDownLink( compileUrl( baseUrl, RESOURCE_DESCENDANTS, info.getId(  ) ),
192                     Constants.MEDIATYPE_DESCENDANTS );
193             }
194 
195             if ( info.supportsFolderTree(  ) )
196             {
197                 entry.writeFolderTreeLink( compileUrl( baseUrl, RESOURCE_FOLDERTREE, info.getId(  ) ) );
198             }
199         }
200 
201         if ( info.getVersionSeriesId(  ) != null )
202         {
203             UrlBuilder vsUrl = compileUrlBuilder( baseUrl, RESOURCE_VERSIONS, info.getId(  ) );
204             vsUrl.addParameter( Constants.PARAM_VERSION_SERIES_ID, info.getVersionSeriesId(  ) );
205             entry.writeVersionHistoryLink( vsUrl.toString(  ) );
206         }
207 
208         if ( !info.isCurrentVersion(  ) )
209         {
210             UrlBuilder cvUrl = compileUrlBuilder( baseUrl, RESOURCE_ENTRY, info.getId(  ) );
211             cvUrl.addParameter( Constants.PARAM_RETURN_VERSION, ReturnVersion.LATEST );
212             entry.writeEditLink( cvUrl.toString(  ) );
213         }
214 
215         if ( info.getBaseType(  ) == BaseTypeId.CMIS_DOCUMENT )
216         {
217             entry.writeEditMediaLink( compileUrl( baseUrl, RESOURCE_CONTENT, info.getId(  ) ), info.getContentType(  ) );
218         }
219 
220         if ( info.getWorkingCopyId(  ) != null )
221         {
222             entry.writeWorkingCopyLink( compileUrl( baseUrl, RESOURCE_ENTRY, info.getWorkingCopyId(  ) ) );
223         }
224 
225         if ( info.getWorkingCopyOriginalId(  ) != null )
226         {
227             entry.writeViaLink( compileUrl( baseUrl, RESOURCE_ENTRY, info.getWorkingCopyOriginalId(  ) ) );
228         }
229 
230         if ( info.getRenditionInfos(  ) != null )
231         {
232             for ( RenditionInfo ri : info.getRenditionInfos(  ) )
233             {
234                 UrlBuilder rurl = compileUrlBuilder( baseUrl, RESOURCE_CONTENT, info.getId(  ) );
235                 rurl.addParameter( Constants.PARAM_STREAM_ID, ri.getId(  ) );
236                 entry.writeAlternateLink( rurl.toString(  ), ri.getContenType(  ), ri.getKind(  ), ri.getTitle(  ),
237                     ri.getLength(  ) );
238             }
239         }
240 
241         if ( info.hasAcl(  ) )
242         {
243             entry.writeAclLink( compileUrl( baseUrl, RESOURCE_ACL, info.getId(  ) ) );
244         }
245 
246         if ( info.supportsPolicies(  ) )
247         {
248             entry.writePoliciesLink( compileUrl( baseUrl, RESOURCE_POLICIES, info.getId(  ) ) );
249         }
250 
251         if ( info.supportsRelationships(  ) )
252         {
253             entry.writeRelationshipsLink( compileUrl( baseUrl, RESOURCE_RELATIONSHIPS, info.getId(  ) ) );
254         }
255 
256         if ( info.getRelationshipSourceIds(  ) != null )
257         {
258             for ( String id : info.getRelationshipSourceIds(  ) )
259             {
260                 entry.writeRelationshipSourceLink( compileUrl( baseUrl, RESOURCE_ENTRY, id ) );
261             }
262         }
263 
264         if ( info.getRelationshipTargetIds(  ) != null )
265         {
266             for ( String id : info.getRelationshipTargetIds(  ) )
267             {
268                 entry.writeRelationshipTargetLink( compileUrl( baseUrl, RESOURCE_ENTRY, id ) );
269             }
270         }
271 
272         // write children
273         if ( ( children != null ) && ( children.size(  ) > 0 ) )
274         {
275             writeObjectChildren( service, entry, info, children, repositoryId, baseUrl );
276         }
277 
278         // we are done
279         entry.endEntry(  );
280     }
281 
282     /**
283      * Writes the a object entry in a content changes list.
284      *
285      * Content changes objects need special treatment because some of them could
286      * have been deleted and an object info cannot be generated.
287      */
288     public static void writeContentChangesObjectEntry( CmisService service, AtomEntry entry, ObjectData object,
289         List<ObjectInFolderContainer> children, String repositoryId, String pathSegment, String relativePathSegment,
290         UrlBuilder baseUrl, boolean isRoot ) throws XMLStreamException, JAXBException
291     {
292         if ( object == null )
293         {
294             throw new CmisRuntimeException( "Object not set!" );
295         }
296 
297         ObjectInfo info = null;
298 
299         try
300         {
301             info = service.getObjectInfo( repositoryId, object.getId(  ) );
302         }
303         catch ( Exception e )
304         {
305             // ignore all exceptions
306         }
307 
308         if ( info != null )
309         {
310             writeObjectEntry( service, entry, object, children, repositoryId, pathSegment, relativePathSegment,
311                 baseUrl, isRoot );
312 
313             return;
314         }
315 
316         // start delete object entry
317         entry.startEntry( isRoot );
318 
319         // write object
320         entry.writeDeletedObject( object );
321 
322         // write links
323         entry.writeServiceLink( baseUrl.toString(  ), repositoryId );
324 
325         // we are done
326         entry.endEntry(  );
327     }
328 
329     /**
330      * Writes an objects entry children feed.
331      */
332     public static void writeObjectChildren( CmisService service, AtomEntry entry, ObjectInfo folderInfo,
333         List<ObjectInFolderContainer> children, String repositoryId, UrlBuilder baseUrl )
334         throws XMLStreamException, JAXBException
335     {
336         // start
337         AtomFeed feed = new AtomFeed( entry.getWriter(  ) );
338         feed.startChildren(  );
339         feed.startFeed( false );
340 
341         // write basic Atom feed elements
342         feed.writeFeedElements( folderInfo.getId(  ), folderInfo.getCreatedBy(  ), folderInfo.getName(  ),
343             folderInfo.getLastModificationDate(  ), null, null );
344 
345         // write links
346         feed.writeServiceLink( baseUrl.toString(  ), repositoryId );
347 
348         feed.writeSelfLink( compileUrl( baseUrl, RESOURCE_DESCENDANTS, folderInfo.getId(  ) ), null );
349 
350         feed.writeViaLink( compileUrl( baseUrl, RESOURCE_ENTRY, folderInfo.getId(  ) ) );
351 
352         feed.writeDownLink( compileUrl( baseUrl, RESOURCE_CHILDREN, folderInfo.getId(  ) ), Constants.MEDIATYPE_FEED );
353 
354         feed.writeDownLink( compileUrl( baseUrl, RESOURCE_FOLDERTREE, folderInfo.getId(  ) ),
355             Constants.MEDIATYPE_DESCENDANTS );
356 
357         feed.writeUpLink( compileUrl( baseUrl, RESOURCE_PARENTS, folderInfo.getId(  ) ), Constants.MEDIATYPE_FEED );
358 
359         for ( ObjectInFolderContainer container : children )
360         {
361             if ( ( container != null ) && ( container.getObject(  ) != null ) )
362             {
363                 writeObjectEntry( service, entry, container.getObject(  ).getObject(  ), container.getChildren(  ),
364                     repositoryId, container.getObject(  ).getPathSegment(  ), null, baseUrl, false );
365             }
366         }
367 
368         // we are done
369         feed.endFeed(  );
370         feed.endChildren(  );
371     }
372 
373     /**
374      * Writes the a type entry.
375      */
376     public static void writeTypeEntry( AtomEntry entry, TypeDefinition type, List<TypeDefinitionContainer> children,
377         String repositoryId, UrlBuilder baseUrl, boolean isRoot )
378         throws XMLStreamException, JAXBException
379     {
380         // start
381         entry.startEntry( isRoot );
382 
383         // write type
384         entry.writeType( type );
385 
386         // write links
387         entry.writeServiceLink( baseUrl.toString(  ), repositoryId );
388 
389         entry.writeSelfLink( compileUrl( baseUrl, RESOURCE_TYPE, type.getId(  ) ), type.getId(  ) );
390         entry.writeEnclosureLink( compileUrl( baseUrl, RESOURCE_TYPE, type.getId(  ) ) );
391 
392         if ( type.getParentTypeId(  ) != null )
393         {
394             entry.writeUpLink( compileUrl( baseUrl, RESOURCE_TYPE, type.getParentTypeId(  ) ), Constants.MEDIATYPE_ENTRY );
395         }
396 
397         UrlBuilder downLink = compileUrlBuilder( baseUrl, RESOURCE_TYPES, null );
398         downLink.addParameter( Constants.PARAM_TYPE_ID, type.getId(  ) );
399         entry.writeDownLink( downLink.toString(  ), Constants.MEDIATYPE_CHILDREN );
400 
401         UrlBuilder downLink2 = compileUrlBuilder( baseUrl, RESOURCE_TYPESDESC, null );
402         downLink2.addParameter( Constants.PARAM_TYPE_ID, type.getId(  ) );
403         entry.writeDownLink( downLink2.toString(  ), Constants.MEDIATYPE_DESCENDANTS );
404         entry.writeDescribedByLink( compileUrl( baseUrl, RESOURCE_TYPE, type.getBaseTypeId(  ).value(  ) ) );
405 
406         // write children
407         if ( ( children != null ) && ( children.size(  ) > 0 ) )
408         {
409             writeTypeChildren( entry, type, children, repositoryId, baseUrl );
410         }
411 
412         // we are done
413         entry.endEntry(  );
414     }
415 
416     /**
417      * Writes the a type entry children feed.
418      */
419     private static void writeTypeChildren( AtomEntry entry, TypeDefinition type,
420         List<TypeDefinitionContainer> children, String repositoryId, UrlBuilder baseUrl )
421         throws XMLStreamException, JAXBException
422     {
423         // start
424         AtomFeed feed = new AtomFeed( entry.getWriter(  ) );
425         feed.startChildren(  );
426         feed.startFeed( false );
427 
428         // write basic Atom feed elements
429         feed.writeFeedElements( type.getId(  ), TYPE_AUTHOR, type.getDisplayName(  ), new GregorianCalendar(  ), null,
430             null );
431 
432         feed.writeServiceLink( baseUrl.toString(  ), repositoryId );
433 
434         UrlBuilder selfLink = compileUrlBuilder( baseUrl, RESOURCE_TYPESDESC, null );
435         selfLink.addParameter( Constants.PARAM_TYPE_ID, type.getId(  ) );
436         feed.writeSelfLink( selfLink.toString(  ), type.getId(  ) );
437 
438         feed.writeViaLink( compileUrl( baseUrl, RESOURCE_TYPE, type.getId(  ) ) );
439 
440         UrlBuilder downLink = compileUrlBuilder( baseUrl, RESOURCE_TYPES, null );
441         downLink.addParameter( Constants.PARAM_TYPE_ID, type.getId(  ) );
442         feed.writeDownLink( downLink.toString(  ), Constants.MEDIATYPE_FEED );
443 
444         if ( type.getParentTypeId(  ) != null )
445         {
446             feed.writeUpLink( compileUrl( baseUrl, RESOURCE_TYPE, type.getParentTypeId(  ) ), Constants.MEDIATYPE_ENTRY );
447         }
448 
449         // write tree
450         for ( TypeDefinitionContainer container : children )
451         {
452             if ( ( container != null ) && ( container.getTypeDefinition(  ) != null ) )
453             {
454                 writeTypeEntry( entry, container.getTypeDefinition(  ), container.getChildren(  ), repositoryId,
455                     baseUrl, false );
456             }
457         }
458 
459         // we are done
460         feed.endFeed(  );
461         feed.endChildren(  );
462     }
463 }