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.client.bindings.spi.atompub;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import javax.xml.stream.XMLStreamException;
27  
28  import org.apache.chemistry.opencmis.client.bindings.spi.BindingSession;
29  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomElement;
30  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomEntry;
31  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomFeed;
32  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomLink;
33  import org.apache.chemistry.opencmis.client.bindings.spi.http.Output;
34  import org.apache.chemistry.opencmis.client.bindings.spi.http.Response;
35  import org.apache.chemistry.opencmis.commons.PropertyIds;
36  import org.apache.chemistry.opencmis.commons.SessionParameter;
37  import org.apache.chemistry.opencmis.commons.data.Acl;
38  import org.apache.chemistry.opencmis.commons.data.ContentStream;
39  import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
40  import org.apache.chemistry.opencmis.commons.data.ObjectData;
41  import org.apache.chemistry.opencmis.commons.data.Properties;
42  import org.apache.chemistry.opencmis.commons.data.PropertyData;
43  import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
44  import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException;
45  import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
46  import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
47  import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
48  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
49  import org.apache.chemistry.opencmis.commons.impl.Constants;
50  import org.apache.chemistry.opencmis.commons.impl.ReturnVersion;
51  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
52  import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl;
53  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl;
54  import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringImpl;
55  import org.apache.chemistry.opencmis.commons.spi.Holder;
56  import org.apache.chemistry.opencmis.commons.spi.VersioningService;
57  
58  /**
59   * Versioning Service AtomPub client.
60   */
61  public class VersioningServiceImpl extends AbstractAtomPubService implements VersioningService {
62  
63      /**
64       * Constructor.
65       */
66      public VersioningServiceImpl(BindingSession session) {
67          setSession(session);
68      }
69  
70      @Override
71      public void checkOut(String repositoryId, Holder<String> objectId, ExtensionsData extension,
72              Holder<Boolean> contentCopied) {
73          if ((objectId == null) || (objectId.getValue() == null) || (objectId.getValue().length() == 0)) {
74              throw new CmisInvalidArgumentException("Object id must be set!");
75          }
76  
77          // find the link
78          String link = loadCollection(repositoryId, Constants.COLLECTION_CHECKEDOUT);
79  
80          if (link == null) {
81              throw new CmisObjectNotFoundException("Unknown repository or checkedout collection not supported!");
82          }
83  
84          UrlBuilder url = new UrlBuilder(link);
85  
86          // workaround for SharePoint 2010 - see CMIS-362
87          if (getSession().get(SessionParameter.INCLUDE_OBJECTID_URL_PARAM_ON_CHECKOUT, false)) {
88              url.addParameter("objectId", objectId.getValue());
89          }
90  
91          // set up object and writer
92          final AtomEntryWriter entryWriter = new AtomEntryWriter(createIdObject(objectId.getValue()),
93                  getCmisVersion(repositoryId));
94  
95          // post move request
96          Response resp = post(url, Constants.MEDIATYPE_ENTRY, new Output() {
97              @Override
98              public void write(OutputStream out) throws XMLStreamException, IOException {
99                  entryWriter.write(out);
100             }
101         });
102 
103         // parse the response
104         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
105 
106         objectId.setValue(entry.getId());
107 
108         lockLinks();
109         try {
110             // clean up cache
111             removeLinks(repositoryId, entry.getId());
112 
113             // walk through the entry
114             for (AtomElement element : entry.getElements()) {
115                 if (element.getObject() instanceof AtomLink) {
116                     addLink(repositoryId, entry.getId(), (AtomLink) element.getObject());
117                 }
118             }
119         } finally {
120             unlockLinks();
121         }
122 
123         if (contentCopied != null) {
124             contentCopied.setValue(null);
125         }
126     }
127 
128     @Override
129     public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension) {
130         // find the link
131         String link = loadLink(repositoryId, objectId, Constants.REL_SELF, Constants.MEDIATYPE_ENTRY);
132 
133         if (link == null) {
134             throwLinkException(repositoryId, objectId, Constants.REL_SELF, Constants.MEDIATYPE_ENTRY);
135         }
136 
137         // prefer working copy link if available
138         // (workaround for non-compliant repositories)
139         String wcLink = getLink(repositoryId, objectId, Constants.REL_WORKINGCOPY, Constants.MEDIATYPE_ENTRY);
140         if (wcLink != null) {
141             link = wcLink;
142         }
143 
144         delete(new UrlBuilder(link));
145     }
146 
147     @Override
148     public void checkIn(String repositoryId, Holder<String> objectId, Boolean major, Properties properties,
149             ContentStream contentStream, String checkinComment, List<String> policies, Acl addAces, Acl removeAces,
150             ExtensionsData extension) {
151         // we need an object id
152         if ((objectId == null) || (objectId.getValue() == null) || (objectId.getValue().length() == 0)) {
153             throw new CmisInvalidArgumentException("Object id must be set!");
154         }
155 
156         // find the link
157         String link = loadLink(repositoryId, objectId.getValue(), Constants.REL_SELF, Constants.MEDIATYPE_ENTRY);
158 
159         if (link == null) {
160             throwLinkException(repositoryId, objectId.getValue(), Constants.REL_SELF, Constants.MEDIATYPE_ENTRY);
161         }
162 
163         // prefer working copy link if available
164         // (workaround for non-compliant repositories)
165         String wcLink = getLink(repositoryId, objectId.getValue(), Constants.REL_WORKINGCOPY, Constants.MEDIATYPE_ENTRY);
166         if (wcLink != null) {
167             link = wcLink;
168         }
169 
170         UrlBuilder url = new UrlBuilder(link);
171         url.addParameter(Constants.PARAM_CHECKIN_COMMENT, checkinComment);
172         url.addParameter(Constants.PARAM_MAJOR, major);
173         url.addParameter(Constants.PARAM_CHECK_IN, "true");
174 
175         // workaround for SharePoint - check in without property change
176         if (getSession().get(SessionParameter.ADD_NAME_ON_CHECK_IN, false)) {
177             if (properties == null || properties.getPropertyList().isEmpty()) {
178                 properties = new PropertiesImpl();
179 
180                 try {
181                     String name = null;
182 
183                     // fetch the current name
184                     ObjectData obj = getObjectInternal(repositoryId, IdentifierType.ID, objectId.getValue(),
185                             ReturnVersion.THIS, "cmis:objectId,cmis:name", Boolean.FALSE, IncludeRelationships.NONE,
186                             "cmis:none", Boolean.FALSE, Boolean.FALSE, null);
187 
188                     if (obj != null && obj.getProperties() != null && obj.getProperties().getProperties() != null
189                             && obj.getProperties().getProperties().get(PropertyIds.NAME) != null) {
190                         PropertyData<?> nameProp = obj.getProperties().getProperties().get(PropertyIds.NAME);
191                         if (nameProp.getFirstValue() instanceof String) {
192                             name = (String) nameProp.getFirstValue();
193                         }
194                     }
195 
196                     if (name == null) {
197                         throw new CmisRuntimeException("Could not determine the name of the PWC!");
198                     }
199 
200                     // set the document name to the same value - silly, but
201                     // SharePoint requires that at least one property value has
202                     // to be changed and the name is the only reliable property
203                     ((PropertiesImpl) properties).addProperty(new PropertyStringImpl(PropertyIds.NAME, name));
204                 } catch (CmisBaseException e) {
205                     throw new CmisRuntimeException("Could not determine the name of the PWC: " + e.toString(), e);
206                 }
207             }
208         }
209 
210         // set up writer
211         final AtomEntryWriter entryWriter = new AtomEntryWriter(createObject(properties, null, policies),
212                 getCmisVersion(repositoryId), contentStream);
213 
214         // update
215         Response resp = put(url, Constants.MEDIATYPE_ENTRY, new Output() {
216             @Override
217             public void write(OutputStream out) throws XMLStreamException, IOException {
218                 entryWriter.write(out);
219             }
220         });
221 
222         // parse new entry
223         AtomEntry entry = parse(resp.getStream(), AtomEntry.class);
224 
225         // we expect a CMIS entry
226         if (entry.getId() == null) {
227             throw new CmisConnectionException("Received Atom entry is not a CMIS entry!");
228         }
229 
230         // set object id
231         objectId.setValue(entry.getId());
232 
233         AccessControlListImpl originalAces = null;
234 
235         lockLinks();
236         try {
237             // clean up cache
238             removeLinks(repositoryId, entry.getId());
239 
240             // walk through the entry
241             for (AtomElement element : entry.getElements()) {
242                 if (element.getObject() instanceof AtomLink) {
243                     addLink(repositoryId, entry.getId(), (AtomLink) element.getObject());
244                 } else if (element.getObject() instanceof ObjectData) {
245                     // extract current ACL
246                     ObjectData object = (ObjectData) element.getObject();
247                     if (object.getAcl() != null) {
248                         originalAces = new AccessControlListImpl(object.getAcl().getAces());
249                         originalAces.setExact(object.isExactAcl());
250                     }
251                 }
252             }
253         } finally {
254             unlockLinks();
255         }
256 
257         // handle ACL modifications
258         if ((originalAces != null) && (isAclMergeRequired(addAces, removeAces))) {
259             // merge and update ACL
260             Acl newACL = mergeAcls(originalAces, addAces, removeAces);
261             if (newACL != null) {
262                 updateAcl(repositoryId, entry.getId(), newACL, null);
263             }
264         }
265     }
266 
267     @Override
268     public List<ObjectData> getAllVersions(String repositoryId, String objectId, String versionSeriesId, String filter,
269             Boolean includeAllowableActions, ExtensionsData extension) {
270         List<ObjectData> result = new ArrayList<ObjectData>();
271 
272         // find the link
273         String link = loadLink(repositoryId, objectId, Constants.REL_VERSIONHISTORY, Constants.MEDIATYPE_FEED);
274 
275         if (link == null) {
276             throwLinkException(repositoryId, objectId, Constants.REL_VERSIONHISTORY, Constants.MEDIATYPE_FEED);
277         }
278 
279         UrlBuilder url = new UrlBuilder(link);
280         url.addParameter(Constants.PARAM_FILTER, filter);
281         url.addParameter(Constants.PARAM_ALLOWABLE_ACTIONS, includeAllowableActions);
282 
283         // read and parse
284         Response resp = read(url);
285         AtomFeed feed = parse(resp.getStream(), AtomFeed.class);
286 
287         // get the versions
288         if (!feed.getEntries().isEmpty()) {
289             for (AtomEntry entry : feed.getEntries()) {
290                 ObjectData version = null;
291 
292                 lockLinks();
293                 try {
294                     // clean up cache
295                     removeLinks(repositoryId, entry.getId());
296 
297                     // walk through the entry
298                     for (AtomElement element : entry.getElements()) {
299                         if (element.getObject() instanceof AtomLink) {
300                             addLink(repositoryId, entry.getId(), (AtomLink) element.getObject());
301                         } else if (element.getObject() instanceof ObjectData) {
302                             version = (ObjectData) element.getObject();
303                         }
304                     }
305                 } finally {
306                     unlockLinks();
307                 }
308 
309                 if (version != null) {
310                     result.add(version);
311                 }
312             }
313         }
314 
315         return result;
316     }
317 
318     @Override
319     public ObjectData getObjectOfLatestVersion(String repositoryId, String objectId, String versionSeriesId,
320             Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
321             String renditionFilter, Boolean includePolicyIds, Boolean includeACL, ExtensionsData extension) {
322 
323         ReturnVersion returnVersion = ReturnVersion.LATEST;
324         if ((major != null) && (major.booleanValue())) {
325             returnVersion = ReturnVersion.LASTESTMAJOR;
326         }
327 
328         // workaround for SharePoint - use the version series ID instead of the
329         // object ID
330         if (getSession().get(SessionParameter.LATEST_VERSION_WITH_VERSION_SERIES_ID, false)) {
331             if (versionSeriesId != null) {
332                 objectId = versionSeriesId;
333             } else {
334                 ObjectData obj = getObjectInternal(repositoryId, IdentifierType.ID, objectId, null,
335                         PropertyIds.OBJECT_ID + "," + PropertyIds.VERSION_SERIES_ID, Boolean.FALSE,
336                         IncludeRelationships.NONE, "cmis:none", Boolean.FALSE, Boolean.FALSE, extension);
337 
338                 if (obj.getProperties() != null && obj.getProperties().getProperties() != null) {
339                     PropertyData<?> versionSeriesProp = obj.getProperties().getProperties()
340                             .get(PropertyIds.VERSION_SERIES_ID);
341                     if (versionSeriesProp != null && versionSeriesProp.getFirstValue() instanceof String) {
342                         objectId = (String) versionSeriesProp.getFirstValue();
343                     }
344                 }
345             }
346         }
347 
348         return getObjectInternal(repositoryId, IdentifierType.ID, objectId, returnVersion, filter,
349                 includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeACL, extension);
350     }
351 
352     @Override
353     public Properties getPropertiesOfLatestVersion(String repositoryId, String objectId, String versionSeriesId,
354             Boolean major, String filter, ExtensionsData extension) {
355         return getObjectOfLatestVersion(repositoryId, objectId, versionSeriesId, major, filter, Boolean.FALSE,
356                 IncludeRelationships.NONE, "cmis:none", Boolean.FALSE, Boolean.FALSE, extension).getProperties();
357     }
358 }