This project has retired. For details please refer to its Attic page.
ObjectServiceImpl 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  package org.apache.chemistry.opencmis.client.bindings.spi.atompub;
20  
21  import static org.apache.chemistry.opencmis.commons.impl.Converter.convert;
22  import static org.apache.chemistry.opencmis.commons.impl.Converter.convertPolicyIds;
23  
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.math.BigInteger;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.List;
30  import java.util.Map;
31  
32  import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
33  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomAllowableActions;
34  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomElement;
35  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomEntry;
36  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomFeed;
37  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomLink;
38  import org.apache.chemistry.opencmis.client.bindings.spi.http.HttpUtils;
39  import org.apache.chemistry.opencmis.commons.PropertyIds;
40  import org.apache.chemistry.opencmis.commons.data.Acl;
41  import org.apache.chemistry.opencmis.commons.data.AllowableActions;
42  import org.apache.chemistry.opencmis.commons.data.ContentStream;
43  import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
44  import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
45  import org.apache.chemistry.opencmis.commons.data.ObjectData;
46  import org.apache.chemistry.opencmis.commons.data.Properties;
47  import org.apache.chemistry.opencmis.commons.data.PropertyData;
48  import org.apache.chemistry.opencmis.commons.data.PropertyId;
49  import org.apache.chemistry.opencmis.commons.data.RenditionData;
50  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
51  import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
52  import org.apache.chemistry.opencmis.commons.enums.VersioningState;
53  import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
54  import org.apache.chemistry.opencmis.commons.exceptions.CmisConstraintException;
55  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
56  import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
57  import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
58  import org.apache.chemistry.opencmis.commons.impl.Constants;
59  import org.apache.chemistry.opencmis.commons.impl.MimeHelper;
60  import org.apache.chemistry.opencmis.commons.impl.ReturnVersion;
61  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
62  import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
63  import org.apache.chemistry.opencmis.commons.impl.dataobjects.FailedToDeleteDataImpl;
64  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisObjectType;
65  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisProperty;
66  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisPropertyString;
67  import org.apache.chemistry.opencmis.commons.spi.Holder;
68  import org.apache.chemistry.opencmis.commons.spi.ObjectService;
69  
70  /**
71   * Object Service AtomPub client.
72   */
73  public class ObjectServiceImpl extends AbstractAtomPubService implements ObjectService {
74  
75      /**
76       * Constructor.
77       */
78      public ObjectServiceImpl(BindingSession session) {
79          setSession(session);
80      }
81  
82      public String createDocument(String repositoryId, Properties properties, String folderId,
83              ContentStream contentStream, VersioningState versioningState, List<String> policies, Acl addAces,
84              Acl removeAces, ExtensionsData extension) {
85          checkCreateProperties(properties);
86  
87          // find the link
88          String link = null;
89  
90          if (folderId == null) {
91              // Creation of unfiled objects via AtomPub is not defined in the
92              // CMIS 1.0 specification. This implementation follow the CMIS 1.1
93              // draft and POSTs the document to the Unfiled collection.
94  
95              link = loadCollection(repositoryId, Constants.COLLECTION_UNFILED);
96  
97              if (link == null) {
98                  throw new CmisObjectNotFoundException("Unknown repository or unfiling not supported!");
99              }
100         } else {
101             link = loadLink(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
102 
103             if (link == null) {
104                 throwLinkException(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
105             }
106         }
107 
108         UrlBuilder url = new UrlBuilder(link);
109         url.addParameter(Constants.PARAM_VERSIONIG_STATE, versioningState);
110 
111         // set up object and writer
112         CmisObjectType object = new CmisObjectType();
113         object.setProperties(convert(properties));
114         object.setPolicyIds(convertPolicyIds(policies));
115 
116         String mediaType = null;
117         InputStream stream = null;
118 
119         if (contentStream != null) {
120             mediaType = contentStream.getMimeType();
121             stream = contentStream.getStream();
122         }
123 
124         final AtomEntryWriter entryWriter = new AtomEntryWriter(object, mediaType, stream);
125 
126         // post the new folder object
127         HttpUtils.Response resp = post(url, Constants.MEDIATYPE_ENTRY, new HttpUtils.Output() {
128             public void write(OutputStream out) throws Exception {
129                 entryWriter.write(out);
130             }
131         });
132 
133         // parse the response
134         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
135 
136         // handle ACL modifications
137         handleAclModifications(repositoryId, entry, addAces, removeAces);
138 
139         return entry.getId();
140     }
141 
142     public String createDocumentFromSource(String repositoryId, String sourceId, Properties properties,
143             String folderId, VersioningState versioningState, List<String> policies, Acl addACEs, Acl removeACEs,
144             ExtensionsData extension) {
145         throw new CmisNotSupportedException("createDocumentFromSource is not supported by the AtomPub binding!");
146     }
147 
148     public String createFolder(String repositoryId, Properties properties, String folderId, List<String> policies,
149             Acl addAces, Acl removeAces, ExtensionsData extension) {
150         checkCreateProperties(properties);
151 
152         // find the link
153         String link = loadLink(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
154 
155         if (link == null) {
156             throwLinkException(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
157         }
158 
159         UrlBuilder url = new UrlBuilder(link);
160 
161         // set up object and writer
162         CmisObjectType object = new CmisObjectType();
163         object.setProperties(convert(properties));
164         object.setPolicyIds(convertPolicyIds(policies));
165 
166         final AtomEntryWriter entryWriter = new AtomEntryWriter(object);
167 
168         // post the new folder object
169         HttpUtils.Response resp = post(url, Constants.MEDIATYPE_ENTRY, new HttpUtils.Output() {
170             public void write(OutputStream out) throws Exception {
171                 entryWriter.write(out);
172             }
173         });
174 
175         // parse the response
176         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
177 
178         // handle ACL modifications
179         handleAclModifications(repositoryId, entry, addAces, removeAces);
180 
181         return entry.getId();
182     }
183 
184     public String createPolicy(String repositoryId, Properties properties, String folderId, List<String> policies,
185             Acl addAces, Acl removeAces, ExtensionsData extension) {
186         checkCreateProperties(properties);
187 
188         // find the link
189         String link = null;
190 
191         if (folderId == null) {
192             // Creation of unfiled objects via AtomPub is not defined in the
193             // CMIS 1.0 specification. This implementation follow the CMIS 1.1
194             // draft and POSTs the policy to the Unfiled collection.
195 
196             link = loadCollection(repositoryId, Constants.COLLECTION_UNFILED);
197 
198             if (link == null) {
199                 throw new CmisObjectNotFoundException("Unknown repository or unfiling not supported!");
200             }
201         } else {
202             link = loadLink(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
203 
204             if (link == null) {
205                 throwLinkException(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
206             }
207         }
208 
209         UrlBuilder url = new UrlBuilder(link);
210 
211         // set up object and writer
212         CmisObjectType object = new CmisObjectType();
213         object.setProperties(convert(properties));
214         object.setPolicyIds(convertPolicyIds(policies));
215 
216         final AtomEntryWriter entryWriter = new AtomEntryWriter(object);
217 
218         // post the new folder object
219         HttpUtils.Response resp = post(url, Constants.MEDIATYPE_ENTRY, new HttpUtils.Output() {
220             public void write(OutputStream out) throws Exception {
221                 entryWriter.write(out);
222             }
223         });
224 
225         // parse the response
226         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
227 
228         // handle ACL modifications
229         handleAclModifications(repositoryId, entry, addAces, removeAces);
230 
231         return entry.getId();
232     }
233 
234     public String createRelationship(String repositoryId, Properties properties, List<String> policies, Acl addAces,
235             Acl removeAces, ExtensionsData extension) {
236         checkCreateProperties(properties);
237 
238         // find source id
239         PropertyData<?> sourceIdProperty = properties.getProperties().get(PropertyIds.SOURCE_ID);
240         if (!(sourceIdProperty instanceof PropertyId)) {
241             throw new CmisInvalidArgumentException("Source Id is not set!");
242         }
243 
244         String sourceId = ((PropertyId) sourceIdProperty).getFirstValue();
245         if (sourceId == null) {
246             throw new CmisInvalidArgumentException("Source Id is not set!");
247         }
248 
249         // find the link
250         String link = loadLink(repositoryId, sourceId, Constants.REL_RELATIONSHIPS, Constants.MEDIATYPE_FEED);
251 
252         if (link == null) {
253             throwLinkException(repositoryId, sourceId, Constants.REL_RELATIONSHIPS, Constants.MEDIATYPE_FEED);
254         }
255 
256         UrlBuilder url = new UrlBuilder(link);
257 
258         // set up object and writer
259         CmisObjectType object = new CmisObjectType();
260         object.setProperties(convert(properties));
261         object.setPolicyIds(convertPolicyIds(policies));
262 
263         final AtomEntryWriter entryWriter = new AtomEntryWriter(object);
264 
265         // post the new folder object
266         HttpUtils.Response resp = post(url, Constants.MEDIATYPE_ENTRY, new HttpUtils.Output() {
267             public void write(OutputStream out) throws Exception {
268                 entryWriter.write(out);
269             }
270         });
271 
272         // parse the response
273         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
274 
275         // handle ACL modifications
276         handleAclModifications(repositoryId, entry, addAces, removeAces);
277 
278         return entry.getId();
279     }
280 
281     public void updateProperties(String repositoryId, Holder<String> objectId, Holder<String> changeToken,
282             Properties properties, ExtensionsData extension) {
283         // we need an object id
284         if ((objectId == null) || (objectId.getValue() == null) || (objectId.getValue().length() == 0)) {
285             throw new CmisInvalidArgumentException("Object id must be set!");
286         }
287 
288         // find the link
289         String link = loadLink(repositoryId, objectId.getValue(), Constants.REL_SELF, Constants.MEDIATYPE_ENTRY);
290 
291         if (link == null) {
292             throwLinkException(repositoryId, objectId.getValue(), Constants.REL_SELF, Constants.MEDIATYPE_ENTRY);
293         }
294 
295         UrlBuilder url = new UrlBuilder(link);
296         if (changeToken != null) {
297             url.addParameter(Constants.PARAM_CHANGE_TOKEN, changeToken.getValue());
298         }
299 
300         // set up object and writer
301         CmisObjectType object = new CmisObjectType();
302         object.setProperties(convert(properties));
303 
304         final AtomEntryWriter entryWriter = new AtomEntryWriter(object);
305 
306         // update
307         HttpUtils.Response resp = put(url, Constants.MEDIATYPE_ENTRY, new HttpUtils.Output() {
308             public void write(OutputStream out) throws Exception {
309                 entryWriter.write(out);
310             }
311         });
312 
313         // parse new entry
314         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
315 
316         // we expect a CMIS entry
317         if (entry.getId() == null) {
318             throw new CmisConnectionException("Received Atom entry is not a CMIS entry!");
319         }
320 
321         // set object id
322         objectId.setValue(entry.getId());
323 
324         if (changeToken != null) {
325             changeToken.setValue(null); // just in case
326         }
327 
328         lockLinks();
329         try {
330             // clean up cache
331             removeLinks(repositoryId, entry.getId());
332 
333             // walk through the entry
334             for (AtomElement element : entry.getElements()) {
335                 if (element.getObject() instanceof AtomLink) {
336                     addLink(repositoryId, entry.getId(), (AtomLink) element.getObject());
337                 } else if (element.getObject() instanceof CmisObjectType) {
338                     // extract new change token
339                     if (changeToken != null) {
340                         object = (CmisObjectType) element.getObject();
341 
342                         if (object.getProperties() != null) {
343                             for (CmisProperty property : object.getProperties().getProperty()) {
344                                 if (PropertyIds.CHANGE_TOKEN.equals(property.getPropertyDefinitionId())
345                                         && (property instanceof CmisPropertyString)) {
346 
347                                     CmisPropertyString changeTokenProperty = (CmisPropertyString) property;
348                                     if (!changeTokenProperty.getValue().isEmpty()) {
349                                         changeToken.setValue(changeTokenProperty.getValue().get(0));
350                                     }
351 
352                                     break;
353                                 }
354                             }
355                         }
356                     }
357                 }
358             }
359         } finally {
360             unlockLinks();
361         }
362     }
363 
364     public void deleteObject(String repositoryId, String objectId, Boolean allVersions, ExtensionsData extension) {
365 
366         // find the link
367         String link = loadLink(repositoryId, objectId, Constants.REL_SELF, Constants.MEDIATYPE_ENTRY);
368 
369         if (link == null) {
370             throwLinkException(repositoryId, objectId, Constants.REL_SELF, Constants.MEDIATYPE_ENTRY);
371         }
372 
373         UrlBuilder url = new UrlBuilder(link);
374         url.addParameter(Constants.PARAM_ALL_VERSIONS, allVersions);
375 
376         delete(url);
377     }
378 
379     public FailedToDeleteData deleteTree(String repositoryId, String folderId, Boolean allVersions,
380             UnfileObject unfileObjects, Boolean continueOnFailure, ExtensionsData extension) {
381 
382         // find the link
383         String link = loadLink(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_DESCENDANTS);
384 
385         if (link == null) {
386             link = loadLink(repositoryId, folderId, Constants.REL_FOLDERTREE, Constants.MEDIATYPE_DESCENDANTS);
387         }
388 
389         if (link == null) {
390             throwLinkException(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_DESCENDANTS);
391         }
392 
393         UrlBuilder url = new UrlBuilder(link);
394         url.addParameter(Constants.PARAM_ALL_VERSIONS, allVersions);
395         url.addParameter(Constants.PARAM_UNFILE_OBJECTS, unfileObjects);
396         url.addParameter(Constants.PARAM_CONTINUE_ON_FAILURE, continueOnFailure);
397 
398         // make the call
399         HttpUtils.Response resp = HttpUtils.invokeDELETE(url, getSession());
400 
401         // check response code
402         if (resp.getResponseCode() == 200 || resp.getResponseCode() == 202 || resp.getResponseCode() == 204) {
403             return new FailedToDeleteDataImpl();
404         }
405 
406         // If the server returned an internal server error, get the remaining
407         // children of the folder. We only retrieve the first level, since
408         // getDescendants() is not supported by all repositories.
409         if (resp.getResponseCode() == 500) {
410             link = loadLink(repositoryId, folderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
411 
412             if (link != null) {
413                 url = new UrlBuilder(link);
414                 // we only want the object ids
415                 url.addParameter(Constants.PARAM_FILTER, "cmis:objectId");
416                 url.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, false);
417                 url.addParameter(Constants.PARAM_RELATIONSHIPS, IncludeRelationships.NONE);
418                 url.addParameter(Constants.PARAM_RENDITION_FILTER, "cmis:none");
419                 url.addParameter(Constants.PARAM_PATH_SEGMENT, false);
420                 // 1000 children should be enough to indicate a problem
421                 url.addParameter(Constants.PARAM_MAX_ITEMS, 1000);
422                 url.addParameter(Constants.PARAM_SKIP_COUNT, 0);
423 
424                 // read and parse
425                 resp = read(url);
426                 AtomFeed feed = parse(resp.getStream(), AtomFeed.class);
427 
428                 // prepare result
429                 FailedToDeleteDataImpl result = new FailedToDeleteDataImpl();
430                 List<String> ids = new ArrayList<String>();
431                 result.setIds(ids);
432 
433                 // get the children ids
434                 for (AtomEntry entry : feed.getEntries()) {
435                     ids.add(entry.getId());
436                 }
437 
438                 return result;
439             }
440         }
441 
442         throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
443     }
444 
445     public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension) {
446         // find the link
447         String link = loadLink(repositoryId, objectId, Constants.REL_ALLOWABLEACTIONS,
448                 Constants.MEDIATYPE_ALLOWABLEACTION);
449 
450         if (link == null) {
451             throwLinkException(repositoryId, objectId, Constants.REL_ALLOWABLEACTIONS,
452                     Constants.MEDIATYPE_ALLOWABLEACTION);
453         }
454 
455         UrlBuilder url = new UrlBuilder(link);
456 
457         // read and parse
458         HttpUtils.Response resp = read(url);
459         AtomAllowableActions allowableActions = parse(resp.getStream(), AtomAllowableActions.class);
460 
461         return convert(allowableActions.getAllowableActions());
462     }
463 
464     public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset,
465             BigInteger length, ExtensionsData extension) {
466         ContentStreamImpl result = new ContentStreamImpl();
467 
468         // find the link
469         String link = null;
470         if (streamId != null) {
471             // use the alternate link per spec
472             link = loadLink(repositoryId, objectId, Constants.REL_ALTERNATE, streamId);
473             if (link != null) {
474                 streamId = null; // we have a full URL now
475             }
476         }
477         if (link == null) {
478             link = loadLink(repositoryId, objectId, AtomPubParser.LINK_REL_CONTENT, null);
479         }
480 
481         if (link == null) {
482             throw new CmisConstraintException("No content stream");
483         }
484 
485         UrlBuilder url = new UrlBuilder(link);
486         // using the content URL and adding a streamId param
487         // is not spec-compliant
488         url.addParameter(Constants.PARAM_STREAM_ID, streamId);
489 
490         // get the content
491         HttpUtils.Response resp = HttpUtils.invokeGET(url, getSession(), offset, length);
492 
493         // check response code
494         if ((resp.getResponseCode() != 200) && (resp.getResponseCode() != 206)) {
495             throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
496         }
497 
498         result.setFileName(null);
499         result.setLength(resp.getContentLength());
500         result.setMimeType(resp.getContentTypeHeader());
501         result.setStream(resp.getStream());
502 
503         return result;
504     }
505 
506     public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions,
507             IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
508             Boolean includeACL, ExtensionsData extension) {
509 
510         return getObjectInternal(repositoryId, IdentifierType.ID, objectId, ReturnVersion.THIS, filter,
511                 includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeACL, extension);
512     }
513 
514     public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean includeAllowableActions,
515             IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
516             Boolean includeACL, ExtensionsData extension) {
517 
518         return getObjectInternal(repositoryId, IdentifierType.PATH, path, ReturnVersion.THIS, filter,
519                 includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeACL, extension);
520     }
521 
522     public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension) {
523         ObjectData object = getObjectInternal(repositoryId, IdentifierType.ID, objectId, ReturnVersion.THIS, filter,
524                 Boolean.FALSE, IncludeRelationships.NONE, "cmis:none", Boolean.FALSE, Boolean.FALSE, extension);
525 
526         return object.getProperties();
527     }
528 
529     public List<RenditionData> getRenditions(String repositoryId, String objectId, String renditionFilter,
530             BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
531         ObjectData object = getObjectInternal(repositoryId, IdentifierType.ID, objectId, ReturnVersion.THIS,
532                 PropertyIds.OBJECT_ID, Boolean.FALSE, IncludeRelationships.NONE, renditionFilter, Boolean.FALSE,
533                 Boolean.FALSE, extension);
534 
535         List<RenditionData> result = object.getRenditions();
536         if (result == null) {
537             result = Collections.emptyList();
538         }
539 
540         return result;
541     }
542 
543     public void moveObject(String repositoryId, Holder<String> objectId, String targetFolderId, String sourceFolderId,
544             ExtensionsData extension) {
545         if ((objectId == null) || (objectId.getValue() == null) || (objectId.getValue().length() == 0)) {
546             throw new CmisInvalidArgumentException("Object id must be set!");
547         }
548 
549         if ((targetFolderId == null) || (targetFolderId.length() == 0) || (sourceFolderId == null)
550                 || (sourceFolderId.length() == 0)) {
551             throw new CmisInvalidArgumentException("Source and target folder must be set!");
552         }
553 
554         // find the link
555         String link = loadLink(repositoryId, targetFolderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
556 
557         if (link == null) {
558             throwLinkException(repositoryId, targetFolderId, Constants.REL_DOWN, Constants.MEDIATYPE_CHILDREN);
559         }
560 
561         UrlBuilder url = new UrlBuilder(link);
562         url.addParameter(Constants.PARAM_SOURCE_FOLDER_ID, sourceFolderId);
563 
564         // set up object and writer
565         final AtomEntryWriter entryWriter = new AtomEntryWriter(createIdObject(objectId.getValue()));
566 
567         // post move request
568         HttpUtils.Response resp = post(url, Constants.MEDIATYPE_ENTRY, new HttpUtils.Output() {
569             public void write(OutputStream out) throws Exception {
570                 entryWriter.write(out);
571             }
572         });
573 
574         // parse the response
575         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
576 
577         objectId.setValue(entry.getId());
578     }
579 
580     public void setContentStream(String repositoryId, Holder<String> objectId, Boolean overwriteFlag,
581             Holder<String> changeToken, ContentStream contentStream, ExtensionsData extension) {
582         // we need an object id
583         if ((objectId == null) || (objectId.getValue() == null)) {
584             throw new CmisInvalidArgumentException("Object ID must be set!");
585         }
586 
587         // we need content
588         if ((contentStream == null) || (contentStream.getStream() == null) || (contentStream.getMimeType() == null)) {
589             throw new CmisInvalidArgumentException("Content must be set!");
590         }
591 
592         // find the link
593         String link = loadLink(repositoryId, objectId.getValue(), Constants.REL_EDITMEDIA, null);
594 
595         if (link == null) {
596             throwLinkException(repositoryId, objectId.getValue(), Constants.REL_EDITMEDIA, null);
597         }
598 
599         UrlBuilder url = new UrlBuilder(link);
600         if (changeToken != null) {
601             url.addParameter(Constants.PARAM_CHANGE_TOKEN, changeToken.getValue());
602         }
603         url.addParameter(Constants.PARAM_OVERWRITE_FLAG, overwriteFlag);
604 
605         final InputStream stream = contentStream.getStream();
606 
607         // Content-Disposition header for the filename
608         Map<String, String> headers = null;
609         if (contentStream.getFileName() != null) {
610             headers = Collections
611                     .singletonMap(
612                             MimeHelper.CONTENT_DISPOSITION,
613                             MimeHelper.encodeContentDisposition(MimeHelper.DISPOSITION_ATTACHMENT,
614                                     contentStream.getFileName()));
615         }
616 
617         // send content
618         HttpUtils.Response resp = put(url, contentStream.getMimeType(), headers, new HttpUtils.Output() {
619             public void write(OutputStream out) throws Exception {
620                 int b;
621                 byte[] buffer = new byte[4096];
622 
623                 while ((b = stream.read(buffer)) > -1) {
624                     out.write(buffer, 0, b);
625                 }
626 
627                 stream.close();
628             }
629         });
630 
631         // check response code further
632         if ((resp.getResponseCode() != 200) && (resp.getResponseCode() != 201) && (resp.getResponseCode() != 204)) {
633             throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
634         }
635 
636         objectId.setValue(null);
637         if (changeToken != null) {
638             changeToken.setValue(null);
639         }
640     }
641 
642     public void deleteContentStream(String repositoryId, Holder<String> objectId, Holder<String> changeToken,
643             ExtensionsData extension) {
644         // we need an object id
645         if ((objectId == null) || (objectId.getValue() == null)) {
646             throw new CmisInvalidArgumentException("Object ID must be set!");
647         }
648 
649         // find the link
650         String link = loadLink(repositoryId, objectId.getValue(), Constants.REL_EDITMEDIA, null);
651 
652         if (link == null) {
653             throwLinkException(repositoryId, objectId.getValue(), Constants.REL_EDITMEDIA, null);
654         }
655 
656         UrlBuilder url = new UrlBuilder(link);
657         if (changeToken != null) {
658             url.addParameter(Constants.PARAM_CHANGE_TOKEN, changeToken.getValue());
659         }
660 
661         delete(url);
662 
663         objectId.setValue(null);
664         if (changeToken != null) {
665             changeToken.setValue(null);
666         }
667     }
668 
669     // ---- internal ----
670 
671     private static void checkCreateProperties(Properties properties) {
672         if ((properties == null) || (properties.getProperties() == null)) {
673             throw new CmisInvalidArgumentException("Properties must be set!");
674         }
675 
676         if (!properties.getProperties().containsKey(PropertyIds.OBJECT_TYPE_ID)) {
677             throw new CmisInvalidArgumentException("Property " + PropertyIds.OBJECT_TYPE_ID + " must be set!");
678         }
679 
680         if (properties.getProperties().containsKey(PropertyIds.OBJECT_ID)) {
681             throw new CmisInvalidArgumentException("Property " + PropertyIds.OBJECT_ID + " must not be set!");
682         }
683     }
684 
685     /**
686      * Handles ACL modifications of newly created objects.
687      */
688     private void handleAclModifications(String repositoryId, AtomEntry entry, Acl addAces, Acl removeAces) {
689         if (!isAclMergeRequired(addAces, removeAces)) {
690             return;
691         }
692 
693         Acl originalAces = null;
694 
695         // walk through the entry and find the current ACL
696         for (AtomElement element : entry.getElements()) {
697             if (element.getObject() instanceof CmisObjectType) {
698                 // extract current ACL
699                 CmisObjectType object = (CmisObjectType) element.getObject();
700                 originalAces = convert(object.getAcl(), object.isExactACL());
701 
702                 break;
703             }
704         }
705 
706         if (originalAces != null) {
707             // merge and update ACL
708             Acl newACL = mergeAcls(originalAces, addAces, removeAces);
709             if (newACL != null) {
710                 updateAcl(repositoryId, entry.getId(), newACL, null);
711             }
712         }
713     }
714 }