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  package org.apache.chemistry.opencmis.server.impl.atompub;
20  
21  import org.apache.chemistry.opencmis.commons.PropertyIds;
22  import org.apache.chemistry.opencmis.commons.data.AllowableActions;
23  import org.apache.chemistry.opencmis.commons.data.ContentStream;
24  import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
25  import org.apache.chemistry.opencmis.commons.data.ObjectData;
26  import org.apache.chemistry.opencmis.commons.data.Properties;
27  import org.apache.chemistry.opencmis.commons.data.PropertyData;
28  import org.apache.chemistry.opencmis.commons.data.PropertyString;
29  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
30  import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
31  import org.apache.chemistry.opencmis.commons.enums.VersioningState;
32  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
33  import org.apache.chemistry.opencmis.commons.impl.Constants;
34  import org.apache.chemistry.opencmis.commons.impl.MimeHelper;
35  import org.apache.chemistry.opencmis.commons.impl.ReturnVersion;
36  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
37  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
38  import org.apache.chemistry.opencmis.commons.server.CallContext;
39  import org.apache.chemistry.opencmis.commons.server.CmisService;
40  import org.apache.chemistry.opencmis.commons.server.ObjectInfo;
41  import org.apache.chemistry.opencmis.commons.spi.Holder;
42  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_CONTENT;
43  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.RESOURCE_ENTRY;
44  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.compileBaseUrl;
45  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.compileUrl;
46  import static org.apache.chemistry.opencmis.server.impl.atompub.AtomPubUtils.writeObjectEntry;
47  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getBooleanParameter;
48  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getEnumParameter;
49  import static org.apache.chemistry.opencmis.server.shared.HttpUtils.getStringParameter;
50  
51  import java.io.BufferedInputStream;
52  import java.io.BufferedOutputStream;
53  import java.io.InputStream;
54  import java.io.OutputStream;
55  import java.io.PrintWriter;
56  
57  import java.math.BigInteger;
58  
59  import java.util.Map;
60  
61  import javax.servlet.http.HttpServletRequest;
62  import javax.servlet.http.HttpServletResponse;
63  
64  
65  /**
66   * Object Service operations.
67   */
68  public final class ObjectService
69  {
70      private static final int BUFFER_SIZE = 64 * 1024;
71  
72      private ObjectService(  )
73      {
74      }
75  
76      /**
77       * Create.
78       */
79      public static void create( CallContext context, CmisService service, String repositoryId,
80          HttpServletRequest request, HttpServletResponse response )
81          throws Exception
82      {
83          // get parameters
84          String folderId = getStringParameter( request, Constants.PARAM_ID );
85          String sourceFolderId = getStringParameter( request, Constants.PARAM_SOURCE_FOLDER_ID );
86          VersioningState versioningState = getEnumParameter( request, Constants.PARAM_VERSIONIG_STATE,
87                  VersioningState.class );
88  
89          AtomEntryParser parser = new AtomEntryParser( context.getTempDirectory(  ), context.getMemoryThreshold(  ) );
90          parser.setIgnoreAtomContentSrc( true ); // needed for some clients
91          parser.parse( request.getInputStream(  ) );
92  
93          String objectId = parser.getId(  );
94  
95          // execute
96          String newObjectId = null;
97  
98          if ( objectId == null )
99          {
100             // create
101             newObjectId = service.create( repositoryId, parser.getProperties(  ), folderId,
102                     parser.getContentStream(  ), versioningState, parser.getPolicyIds(  ), null );
103         }
104         else
105         {
106             if ( ( sourceFolderId == null ) || ( sourceFolderId.trim(  ).length(  ) == 0 ) )
107             {
108                 // addObjectToFolder
109                 service.addObjectToFolder( repositoryId, objectId, folderId, null, null );
110                 newObjectId = objectId;
111             }
112             else
113             {
114                 // move
115                 Holder<String> objectIdHolder = new Holder<String>( objectId );
116                 service.moveObject( repositoryId, objectIdHolder, folderId, sourceFolderId, null );
117                 newObjectId = objectIdHolder.getValue(  );
118             }
119         }
120 
121         ObjectInfo objectInfo = service.getObjectInfo( repositoryId, newObjectId );
122 
123         if ( objectInfo == null )
124         {
125             throw new CmisRuntimeException( "Object Info is missing!" );
126         }
127 
128         ObjectData object = objectInfo.getObject(  );
129 
130         if ( object == null )
131         {
132             throw new CmisRuntimeException( "Object is null!" );
133         }
134 
135         // set headers
136         UrlBuilder baseUrl = compileBaseUrl( request, repositoryId );
137 
138         response.setStatus( HttpServletResponse.SC_CREATED );
139         response.setContentType( Constants.MEDIATYPE_ENTRY );
140         response.setHeader( "Location", compileUrl( baseUrl, RESOURCE_ENTRY, newObjectId ) );
141 
142         // write XML
143         AtomEntry entry = new AtomEntry(  );
144         entry.startDocument( response.getOutputStream(  ) );
145         writeObjectEntry( service, entry, object, null, repositoryId, null, null, baseUrl, true );
146         entry.endDocument(  );
147     }
148 
149     /**
150      * Create relationship.
151      */
152     public static void createRelationship( CallContext context, CmisService service, String repositoryId,
153         HttpServletRequest request, HttpServletResponse response )
154         throws Exception
155     {
156         // get parameters
157         AtomEntryParser parser = new AtomEntryParser( request.getInputStream(  ), context.getTempDirectory(  ),
158                 context.getMemoryThreshold(  ) );
159 
160         // execute
161         String newObjectId = service.createRelationship( repositoryId, parser.getProperties(  ),
162                 parser.getPolicyIds(  ), null, null, null );
163 
164         ObjectInfo objectInfo = service.getObjectInfo( repositoryId, newObjectId );
165 
166         if ( objectInfo == null )
167         {
168             throw new CmisRuntimeException( "Object Info is missing!" );
169         }
170 
171         ObjectData object = objectInfo.getObject(  );
172 
173         if ( object == null )
174         {
175             throw new CmisRuntimeException( "Object is null!" );
176         }
177 
178         // set headers
179         UrlBuilder baseUrl = compileBaseUrl( request, repositoryId );
180 
181         response.setStatus( HttpServletResponse.SC_CREATED );
182         response.setContentType( Constants.MEDIATYPE_ENTRY );
183         response.setHeader( "Location", compileUrl( baseUrl, RESOURCE_ENTRY, newObjectId ) );
184 
185         // write XML
186         AtomEntry entry = new AtomEntry(  );
187         entry.startDocument( response.getOutputStream(  ) );
188         writeObjectEntry( service, entry, object, null, repositoryId, null, null, baseUrl, true );
189         entry.endDocument(  );
190     }
191 
192     /**
193      * Delete object.
194      */
195     public static void deleteObject( CallContext context, CmisService service, String repositoryId,
196         HttpServletRequest request, HttpServletResponse response )
197     {
198         // get parameters
199         String objectId = getStringParameter( request, Constants.PARAM_ID );
200         Boolean allVersions = getBooleanParameter( request, Constants.PARAM_ALL_VERSIONS );
201 
202         // execute
203         service.deleteObjectOrCancelCheckOut( repositoryId, objectId, allVersions, null );
204 
205         // set headers
206         response.setStatus( HttpServletResponse.SC_NO_CONTENT );
207     }
208 
209     /**
210      * Delete content stream.
211      */
212     public static void deleteContentStream( CallContext context, CmisService service, String repositoryId,
213         HttpServletRequest request, HttpServletResponse response )
214     {
215         // get parameters
216         String objectId = getStringParameter( request, Constants.PARAM_ID );
217         String changeToken = getStringParameter( request, Constants.PARAM_CHANGE_TOKEN );
218 
219         // execute
220         service.deleteContentStream( repositoryId, new Holder<String>( objectId ),
221             ( changeToken == null ) ? null : new Holder<String>( changeToken ), null );
222 
223         // set headers
224         response.setStatus( HttpServletResponse.SC_NO_CONTENT );
225     }
226 
227     /**
228      * Set content stream.
229      */
230     public static void setContentStream( CallContext context, CmisService service, String repositoryId,
231         HttpServletRequest request, HttpServletResponse response )
232         throws Exception
233     {
234         // get parameters
235         String objectId = getStringParameter( request, Constants.PARAM_ID );
236         String changeToken = getStringParameter( request, Constants.PARAM_CHANGE_TOKEN );
237         Boolean overwriteFlag = getBooleanParameter( request, Constants.PARAM_OVERWRITE_FLAG );
238 
239         ContentStreamImpl contentStream = new ContentStreamImpl(  );
240         contentStream.setStream( request.getInputStream(  ) );
241         contentStream.setMimeType( request.getHeader( "Content-Type" ) );
242 
243         String lengthStr = request.getHeader( "Content-Length" );
244 
245         if ( lengthStr != null )
246         {
247             try
248             {
249                 contentStream.setLength( new BigInteger( lengthStr ) );
250             }
251             catch ( NumberFormatException e )
252             {
253             }
254         }
255 
256         String contentDisposition = request.getHeader( MimeHelper.CONTENT_DISPOSITION );
257 
258         if ( contentDisposition != null )
259         {
260             contentStream.setFileName( MimeHelper.decodeContentDispositionFilename( contentDisposition ) );
261         }
262 
263         // execute
264         Holder<String> objectIdHolder = new Holder<String>( objectId );
265         service.setContentStream( repositoryId, objectIdHolder, overwriteFlag,
266             ( changeToken == null ) ? null : new Holder<String>( changeToken ), contentStream, null );
267 
268         // set headers
269         String newObjectId = ( ( objectIdHolder.getValue(  ) == null ) ? objectId : objectIdHolder.getValue(  ) );
270         String location = compileUrl( compileBaseUrl( request, repositoryId ), RESOURCE_CONTENT, newObjectId );
271 
272         response.setStatus( HttpServletResponse.SC_CREATED );
273         response.setHeader( "Content-Location", location );
274         response.setHeader( "Location", location );
275     }
276 
277     /**
278      * Delete tree.
279      */
280     public static void deleteTree( CallContext context, CmisService service, String repositoryId,
281         HttpServletRequest request, HttpServletResponse response )
282         throws Exception
283     {
284         // get parameters
285         String folderId = getStringParameter( request, Constants.PARAM_ID );
286         Boolean allVersions = getBooleanParameter( request, Constants.PARAM_ALL_VERSIONS );
287         UnfileObject unfileObjects = getEnumParameter( request, Constants.PARAM_UNFILE_OBJECTS, UnfileObject.class );
288         Boolean continueOnFailure = getBooleanParameter( request, Constants.PARAM_CONTINUE_ON_FAILURE );
289 
290         // execute
291         FailedToDeleteData ftd = service.deleteTree( repositoryId, folderId, allVersions, unfileObjects,
292                 continueOnFailure, null );
293 
294         if ( ( ftd != null ) && ( ftd.getIds(  ) != null ) && ( ftd.getIds(  ).size(  ) > 0 ) )
295         {
296             // print ids that could not be deleted
297             response.setStatus( HttpServletResponse.SC_INTERNAL_SERVER_ERROR );
298             response.setContentType( "text/plain" );
299 
300             PrintWriter pw = response.getWriter(  );
301 
302             pw.println( "Failed to delete the following objects:" );
303 
304             for ( String id : ftd.getIds(  ) )
305             {
306                 pw.println( id );
307             }
308 
309             pw.flush(  );
310 
311             return;
312         }
313 
314         // set headers
315         response.setStatus( HttpServletResponse.SC_NO_CONTENT );
316     }
317 
318     /**
319      * getObject.
320      */
321     public static void getObject( CallContext context, CmisService service, String repositoryId,
322         HttpServletRequest request, HttpServletResponse response )
323         throws Exception
324     {
325         // get parameters
326         String objectId = getStringParameter( request, Constants.PARAM_ID );
327         ReturnVersion returnVersion = getEnumParameter( request, Constants.PARAM_RETURN_VERSION, ReturnVersion.class );
328         String filter = getStringParameter( request, Constants.PARAM_FILTER );
329         Boolean includeAllowableActions = getBooleanParameter( request, Constants.PARAM_ALLOWABLE_ACTIONS );
330         IncludeRelationships includeRelationships = getEnumParameter( request, Constants.PARAM_RELATIONSHIPS,
331                 IncludeRelationships.class );
332         String renditionFilter = getStringParameter( request, Constants.PARAM_RENDITION_FILTER );
333         Boolean includePolicyIds = getBooleanParameter( request, Constants.PARAM_POLICY_IDS );
334         Boolean includeAcl = getBooleanParameter( request, Constants.PARAM_ACL );
335 
336         // execute
337         ObjectData object = null;
338 
339         if ( ( returnVersion == ReturnVersion.LATEST ) || ( returnVersion == ReturnVersion.LASTESTMAJOR ) )
340         {
341             object = service.getObjectOfLatestVersion( repositoryId, objectId, null,
342                     returnVersion == ReturnVersion.LASTESTMAJOR, filter, includeAllowableActions, includeRelationships,
343                     renditionFilter, includePolicyIds, includeAcl, null );
344         }
345         else
346         {
347             object = service.getObject( repositoryId, objectId, filter, includeAllowableActions, includeRelationships,
348                     renditionFilter, includePolicyIds, includeAcl, null );
349         }
350 
351         if ( object == null )
352         {
353             throw new CmisRuntimeException( "Object is null!" );
354         }
355 
356         ObjectInfo objectInfo = service.getObjectInfo( repositoryId, objectId );
357 
358         if ( objectInfo == null )
359         {
360             throw new CmisRuntimeException( "Object Info is missing!" );
361         }
362 
363         // set headers
364         response.setStatus( HttpServletResponse.SC_OK );
365         response.setContentType( Constants.MEDIATYPE_ENTRY );
366 
367         // write XML
368         UrlBuilder baseUrl = compileBaseUrl( request, repositoryId );
369 
370         AtomEntry entry = new AtomEntry(  );
371         entry.startDocument( response.getOutputStream(  ) );
372         writeObjectEntry( service, entry, object, null, repositoryId, null, null, baseUrl, true );
373         entry.endDocument(  );
374     }
375 
376     /**
377      * objectByPath URI template.
378      */
379     public static void getObjectByPath( CallContext context, CmisService service, String repositoryId,
380         HttpServletRequest request, HttpServletResponse response )
381         throws Exception
382     {
383         // get parameters
384         String path = getStringParameter( request, Constants.PARAM_PATH );
385         String filter = getStringParameter( request, Constants.PARAM_FILTER );
386         Boolean includeAllowableActions = getBooleanParameter( request, Constants.PARAM_ALLOWABLE_ACTIONS );
387         IncludeRelationships includeRelationships = getEnumParameter( request, Constants.PARAM_RELATIONSHIPS,
388                 IncludeRelationships.class );
389         String renditionFilter = getStringParameter( request, Constants.PARAM_RENDITION_FILTER );
390         Boolean includePolicyIds = getBooleanParameter( request, Constants.PARAM_POLICY_IDS );
391         Boolean includeAcl = getBooleanParameter( request, Constants.PARAM_ACL );
392 
393         // execute
394         ObjectData object = service.getObjectByPath( repositoryId, path, filter, includeAllowableActions,
395                 includeRelationships, renditionFilter, includePolicyIds, includeAcl, null );
396 
397         if ( object == null )
398         {
399             throw new CmisRuntimeException( "Object is null!" );
400         }
401 
402         ObjectInfo objectInfo = service.getObjectInfo( repositoryId, object.getId(  ) );
403 
404         if ( objectInfo == null )
405         {
406             throw new CmisRuntimeException( "Object Info is missing!" );
407         }
408 
409         // set headers
410         response.setStatus( HttpServletResponse.SC_OK );
411         response.setContentType( Constants.MEDIATYPE_ENTRY );
412 
413         // write XML
414         UrlBuilder baseUrl = compileBaseUrl( request, repositoryId );
415 
416         AtomEntry entry = new AtomEntry(  );
417         entry.startDocument( response.getOutputStream(  ) );
418         writeObjectEntry( service, entry, object, null, repositoryId, null, null, baseUrl, true );
419         entry.endDocument(  );
420     }
421 
422     /**
423      * Allowable Actions.
424      */
425     public static void getAllowableActions( CallContext context, CmisService service, String repositoryId,
426         HttpServletRequest request, HttpServletResponse response )
427         throws Exception
428     {
429         // get parameters
430         String objectId = getStringParameter( request, Constants.PARAM_ID );
431 
432         // execute
433         AllowableActions allowableActions = service.getAllowableActions( repositoryId, objectId, null );
434 
435         if ( allowableActions == null )
436         {
437             throw new CmisRuntimeException( "Allowable Actions is null!" );
438         }
439 
440         // set headers
441         response.setStatus( HttpServletResponse.SC_OK );
442         response.setContentType( Constants.MEDIATYPE_ALLOWABLEACTION );
443 
444         // write XML
445         AllowableActionsDocument allowableActionsDocument = new AllowableActionsDocument(  );
446         allowableActionsDocument.writeAllowableActions( allowableActions, response.getOutputStream(  ) );
447     }
448 
449     /**
450      * getContentStream.
451      */
452     public static void getContentStream( CallContext context, CmisService service, String repositoryId,
453         HttpServletRequest request, HttpServletResponse response )
454         throws Exception
455     {
456         // get parameters
457         String objectId = getStringParameter( request, Constants.PARAM_ID );
458         String streamId = getStringParameter( request, Constants.PARAM_STREAM_ID );
459 
460         BigInteger offset = context.getOffset(  );
461         BigInteger length = context.getLength(  );
462 
463         // execute
464         ContentStream content = service.getContentStream( repositoryId, objectId, streamId, offset, length, null );
465 
466         if ( ( content == null ) || ( content.getStream(  ) == null ) )
467         {
468             throw new CmisRuntimeException( "Content stream is null!" );
469         }
470 
471         String contentType = content.getMimeType(  );
472 
473         if ( contentType == null )
474         {
475             contentType = Constants.MEDIATYPE_OCTETSTREAM;
476         }
477 
478         // set headers
479         if ( ( offset == null ) && ( length == null ) )
480         {
481             response.setStatus( HttpServletResponse.SC_OK );
482         }
483         else
484         {
485             response.setStatus( HttpServletResponse.SC_PARTIAL_CONTENT );
486         }
487 
488         response.setContentType( contentType );
489 
490         // send content
491         InputStream in = new BufferedInputStream( content.getStream(  ), BUFFER_SIZE );
492         OutputStream out = new BufferedOutputStream( response.getOutputStream(  ) );
493 
494         byte[] buffer = new byte[BUFFER_SIZE];
495         int b;
496 
497         while ( ( b = in.read( buffer ) ) > -1 )
498         {
499             out.write( buffer, 0, b );
500         }
501 
502         in.close(  );
503         out.flush(  );
504     }
505 
506     /**
507      * UpdateProperties.
508      */
509     public static void updateProperties( CallContext context, CmisService service, String repositoryId,
510         HttpServletRequest request, HttpServletResponse response )
511         throws Exception
512     {
513         // get parameters
514         String objectId = getStringParameter( request, Constants.PARAM_ID );
515         Boolean checkin = getBooleanParameter( request, Constants.PARAM_CHECK_IN );
516         String checkinComment = getStringParameter( request, Constants.PARAM_CHECKIN_COMMENT );
517         Boolean major = getBooleanParameter( request, Constants.PARAM_MAJOR );
518 
519         AtomEntryParser parser = new AtomEntryParser( request.getInputStream(  ), context.getTempDirectory(  ),
520                 context.getMemoryThreshold(  ) );
521 
522         // execute
523         Holder<String> objectIdHolder = new Holder<String>( objectId );
524 
525         if ( ( checkin != null ) && ( checkin.booleanValue(  ) ) )
526         {
527             service.checkIn( repositoryId, objectIdHolder, major, parser.getProperties(  ),
528                 parser.getContentStream(  ), checkinComment, parser.getPolicyIds(  ), null, null, null );
529         }
530         else
531         {
532             String changeToken = extractChangeToken( parser.getProperties(  ) );
533 
534             service.updateProperties( repositoryId, objectIdHolder,
535                 ( changeToken == null ) ? null : new Holder<String>( changeToken ), parser.getProperties(  ), null );
536         }
537 
538         ObjectInfo objectInfo = service.getObjectInfo( repositoryId, objectIdHolder.getValue(  ) );
539 
540         if ( objectInfo == null )
541         {
542             throw new CmisRuntimeException( "Object Info is missing!" );
543         }
544 
545         ObjectData object = objectInfo.getObject(  );
546 
547         if ( object == null )
548         {
549             throw new CmisRuntimeException( "Object is null!" );
550         }
551 
552         // set headers
553         UrlBuilder baseUrl = compileBaseUrl( request, repositoryId );
554         String location = compileUrl( baseUrl, RESOURCE_ENTRY, objectIdHolder.getValue(  ) );
555 
556         response.setStatus( HttpServletResponse.SC_CREATED );
557         response.setContentType( Constants.MEDIATYPE_ENTRY );
558         response.setHeader( "Content-Location", location );
559         response.setHeader( "Location", location );
560 
561         // write XML
562         AtomEntry entry = new AtomEntry(  );
563         entry.startDocument( response.getOutputStream(  ) );
564         writeObjectEntry( service, entry, object, null, repositoryId, null, null, baseUrl, true );
565         entry.endDocument(  );
566     }
567 
568     /**
569      * Gets the change token from a property set.
570      */
571     private static String extractChangeToken( Properties properties )
572     {
573         if ( properties == null )
574         {
575             return null;
576         }
577 
578         Map<String, PropertyData<?>> propertiesMap = properties.getProperties(  );
579 
580         if ( propertiesMap == null )
581         {
582             return null;
583         }
584 
585         PropertyData<?> changeLogProperty = propertiesMap.get( PropertyIds.CHANGE_TOKEN );
586 
587         if ( !( changeLogProperty instanceof PropertyString ) )
588         {
589             return null;
590         }
591 
592         return ( (PropertyString) changeLogProperty ).getFirstValue(  );
593     }
594 }