This project has retired. For details please refer to its Attic page.
AbstractTransientCmisObject 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.runtime;
20  
21  import java.io.Serializable;
22  import java.math.BigDecimal;
23  import java.math.BigInteger;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.GregorianCalendar;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.Iterator;
30  import java.util.LinkedHashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  
35  import org.apache.chemistry.opencmis.client.api.CmisObject;
36  import org.apache.chemistry.opencmis.client.api.ObjectFactory;
37  import org.apache.chemistry.opencmis.client.api.ObjectId;
38  import org.apache.chemistry.opencmis.client.api.ObjectType;
39  import org.apache.chemistry.opencmis.client.api.Policy;
40  import org.apache.chemistry.opencmis.client.api.Property;
41  import org.apache.chemistry.opencmis.client.api.Relationship;
42  import org.apache.chemistry.opencmis.client.api.Rendition;
43  import org.apache.chemistry.opencmis.client.api.Session;
44  import org.apache.chemistry.opencmis.client.api.TransientCmisObject;
45  import org.apache.chemistry.opencmis.commons.PropertyIds;
46  import org.apache.chemistry.opencmis.commons.data.Ace;
47  import org.apache.chemistry.opencmis.commons.data.Acl;
48  import org.apache.chemistry.opencmis.commons.data.AllowableActions;
49  import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement;
50  import org.apache.chemistry.opencmis.commons.data.Properties;
51  import org.apache.chemistry.opencmis.commons.data.PropertyData;
52  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
53  import org.apache.chemistry.opencmis.commons.enums.AclPropagation;
54  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
55  import org.apache.chemistry.opencmis.commons.enums.Cardinality;
56  import org.apache.chemistry.opencmis.commons.enums.ExtensionLevel;
57  import org.apache.chemistry.opencmis.commons.enums.Updatability;
58  import org.apache.chemistry.opencmis.commons.spi.CmisBinding;
59  import org.apache.chemistry.opencmis.commons.spi.Holder;
60  
61  public abstract class AbstractTransientCmisObject implements TransientCmisObject {
62  
63      protected Session session;
64      protected CmisObject object;
65  
66      protected Map<String, Property<?>> properties;
67      protected AllowableActions allowableActions;
68      protected List<Rendition> renditions;
69      protected Acl acl;
70      protected Map<AclPropagation, List<AceChangeHolder>> addAces;
71      protected Map<AclPropagation, List<AceChangeHolder>> removeAces;
72      protected List<Policy> policies;
73      protected Set<String> addPolicies;
74      protected Set<String> removePolicies;
75      protected List<Relationship> relationships;
76      protected Map<ExtensionLevel, List<CmisExtensionElement>> inputExtensions;
77      protected Map<ExtensionLevel, List<CmisExtensionElement>> ouputExtensions;
78  
79      protected boolean isModified;
80      protected boolean isPropertyUpdateRequired;
81      protected boolean isMarkedForDelete;
82      protected boolean deleteAllVersions;
83  
84      @SuppressWarnings({ "rawtypes", "unchecked" })
85      protected void initialize(Session session, CmisObject object) {
86          this.session = session;
87          this.object = object;
88  
89          ObjectFactory of = getObjectFactory();
90  
91          // --- create snapshot ---
92  
93          // properties (modifiable)
94          properties = new LinkedHashMap<String, Property<?>>();
95          for (Property<?> property : object.getProperties()) {
96              properties.put(property.getId(),
97                      of.createProperty(property.getDefinition(), new ArrayList(property.getValues())));
98          }
99          isPropertyUpdateRequired = false;
100 
101         // allowable actions (unmodifiable)
102         allowableActions = object.getAllowableActions();
103 
104         // policies (modifiable)
105         policies = new ArrayList<Policy>();
106         if (object.getPolicies() != null) {
107             policies.addAll(object.getPolicies());
108         }
109         addPolicies = new HashSet<String>();
110         removePolicies = new HashSet<String>();
111 
112         // ACL (unmodifiable)
113         acl = object.getAcl();
114         addAces = new HashMap<AclPropagation, List<AceChangeHolder>>();
115         removeAces = new HashMap<AclPropagation, List<AceChangeHolder>>();
116 
117         // relationships (unmodifiable)
118         relationships = object.getRelationships();
119 
120         // renditions (unmodifiable)
121         renditions = object.getRenditions();
122 
123         // input extensions (unmodifiable)
124         inputExtensions = new HashMap<ExtensionLevel, List<CmisExtensionElement>>();
125         for (ExtensionLevel level : ExtensionLevel.values()) {
126             List<CmisExtensionElement> extension = object.getExtensions(level);
127             if (extension != null) {
128                 inputExtensions.put(level, extension);
129             }
130         }
131 
132         // output extensions (modifiable)
133         ouputExtensions = new HashMap<ExtensionLevel, List<CmisExtensionElement>>();
134 
135         isModified = false;
136         deleteAllVersions = true;
137         isMarkedForDelete = false;
138     }
139 
140     public CmisObject getCmisObject() {
141         return object;
142     }
143 
144     /**
145      * Returns the session object.
146      */
147     protected Session getSession() {
148         return this.session;
149     }
150 
151     /**
152      * Returns the repository id.
153      */
154     protected String getRepositoryId() {
155         return getSession().getRepositoryInfo().getId();
156     }
157 
158     /**
159      * Returns the binding object.
160      */
161     protected CmisBinding getBinding() {
162         return getSession().getBinding();
163     }
164 
165     /**
166      * Returns the object factory.
167      */
168     protected ObjectFactory getObjectFactory() {
169         return getSession().getObjectFactory();
170     }
171 
172     protected ObjectId getObjectId() {
173         return getSession().createObjectId(getId());
174     }
175 
176     public ObjectType getBaseType() {
177         return object.getBaseType();
178     }
179 
180     public BaseTypeId getBaseTypeId() {
181         return object.getBaseTypeId();
182     }
183 
184     public ObjectType getType() {
185         return object.getType();
186     }
187 
188     public String getChangeToken() {
189         return getPropertyValue(PropertyIds.CHANGE_TOKEN);
190     }
191 
192     public String getCreatedBy() {
193         return getPropertyValue(PropertyIds.CREATED_BY);
194     }
195 
196     public GregorianCalendar getCreationDate() {
197         return getPropertyValue(PropertyIds.CREATION_DATE);
198     }
199 
200     public String getId() {
201         return getPropertyValue(PropertyIds.OBJECT_ID);
202     }
203 
204     public GregorianCalendar getLastModificationDate() {
205         return getPropertyValue(PropertyIds.LAST_MODIFICATION_DATE);
206     }
207 
208     public String getLastModifiedBy() {
209         return getPropertyValue(PropertyIds.LAST_MODIFIED_BY);
210     }
211 
212     public String getName() {
213         return getPropertyValue(PropertyIds.NAME);
214     }
215 
216     public void setName(String name) {
217         setPropertyValue(PropertyIds.NAME, name);
218     }
219 
220     public List<Property<?>> getProperties() {
221         return Collections.unmodifiableList(new ArrayList<Property<?>>(this.properties.values()));
222     }
223 
224     @SuppressWarnings("unchecked")
225     public <T> Property<T> getProperty(String id) {
226         return (Property<T>) this.properties.get(id);
227     }
228 
229     @SuppressWarnings("unchecked")
230     public <T> T getPropertyValue(String id) {
231         Property<T> property = getProperty(id);
232         if (property == null) {
233             return null;
234         }
235 
236         return (T) property.getValue();
237     }
238 
239     @SuppressWarnings("unchecked")
240     public <T> void setPropertyValue(String id, Object value) {
241         PropertyDefinition<T> propertyDefinition = (PropertyDefinition<T>) getType().getPropertyDefinitions().get(id);
242         if (propertyDefinition == null) {
243             throw new IllegalArgumentException("Unknown property '" + id + "'!");
244         }
245         // check updatability
246         if (propertyDefinition.getUpdatability() == Updatability.READONLY) {
247             throw new IllegalArgumentException("Property is read-only!");
248         }
249 
250         List<T> values = checkProperty(propertyDefinition, value);
251 
252         // create and set property
253         Property<T> newProperty = getObjectFactory().createProperty(propertyDefinition, values);
254         properties.put(id, newProperty);
255 
256         isPropertyUpdateRequired = true;
257         isModified = true;
258     }
259 
260     public AllowableActions getAllowableActions() {
261         return allowableActions;
262     }
263 
264     public List<Relationship> getRelationships() {
265         return relationships;
266     }
267 
268     public List<Rendition> getRenditions() {
269         return renditions;
270     }
271 
272     public void delete(boolean allVersions) {
273         deleteAllVersions = allVersions;
274         isMarkedForDelete = true;
275         isModified = true;
276     }
277 
278     public void applyPolicy(Policy... policyIds) {
279         for (Policy policy : policyIds) {
280             if ((policy != null) && (policy.getId() != null)) {
281                 addPolicies.add(policy.getId());
282                 addPolicyToPolicyList(policy);
283             }
284         }
285     }
286 
287     public void removePolicy(Policy... policyIds) {
288         for (Policy policy : policyIds) {
289             if ((policy != null) && (policy.getId() != null)) {
290                 removePolicies.add(policy.getId());
291                 removePolicyFromPolicyList(policy);
292             }
293         }
294     }
295 
296     public List<Policy> getPolicies() {
297         return policies;
298     }
299 
300     private void addPolicyToPolicyList(Policy policy) {
301         for (Policy p : policies) {
302             if (policy.getId().equals(p.getId())) {
303                 return;
304             }
305         }
306 
307         policies.add(policy);
308     }
309 
310     private void removePolicyFromPolicyList(Policy policy) {
311         Iterator<Policy> iter = policies.iterator();
312         while (iter.hasNext()) {
313             Policy p = iter.next();
314             if (policy.getId().equals(p.getId())) {
315                 iter.remove();
316             }
317         }
318     }
319 
320     public Acl getOriginalAcl() {
321         return acl;
322     }
323 
324     public void addAce(String principalId, List<String> permissions, AclPropagation aclPropagation) {
325         AceChangeHolder ach = new AceChangeHolder(principalId, permissions, aclPropagation);
326 
327         List<AceChangeHolder> list = addAces.get(aclPropagation);
328         if (list == null) {
329             list = new ArrayList<AbstractTransientCmisObject.AceChangeHolder>();
330             addAces.put(aclPropagation, list);
331         }
332 
333         list.add(ach);
334     }
335 
336     public void removeAce(String principalId, List<String> permissions, AclPropagation aclPropagation) {
337         AceChangeHolder ach = new AceChangeHolder(principalId, permissions, aclPropagation);
338 
339         List<AceChangeHolder> list = removeAces.get(aclPropagation);
340         if (list == null) {
341             list = new ArrayList<AbstractTransientCmisObject.AceChangeHolder>();
342             removeAces.put(aclPropagation, list);
343         }
344 
345         list.add(ach);
346     }
347 
348     public List<CmisExtensionElement> getInputExtensions(ExtensionLevel level) {
349         return inputExtensions.get(level);
350     }
351 
352     public List<CmisExtensionElement> getOutputExtensions(ExtensionLevel level) {
353         return ouputExtensions.get(level);
354     }
355 
356     public void setOutputExtensions(ExtensionLevel level, List<CmisExtensionElement> extensions) {
357         ouputExtensions.put(level, extensions);
358     }
359 
360     public boolean isMarkedForDelete() {
361         return isMarkedForDelete;
362     }
363 
364     public boolean isModified() {
365         return isModified;
366     }
367 
368     public void reset() {
369         initialize(session, object);
370     }
371 
372     public void refreshAndReset() {
373         object.refresh();
374         reset();
375     }
376 
377     public ObjectId save() {
378         if (!isModified()) {
379             // nothing has change, so there is nothing to do
380             return getObjectId();
381         }
382 
383         String objectId = getId();
384 
385         if (saveDelete(objectId)) {
386             // object has been deleted, there is nothing else to do
387             // ... and there is no object id anymore
388             return null;
389         }
390         String newObjectId = saveProperties(getId(), getChangeToken());
391         saveACL(newObjectId);
392         savePolicies(newObjectId);
393 
394         return getSession().createObjectId(newObjectId);
395     }
396 
397     /**
398      * Fetches the latest change token of this object from the repository.
399      */
400     protected String getLatestChangeToken(String objectId) {
401         // determine the object id query name
402         PropertyDefinition<?> objectIdPropDef = getCmisObject().getType().getPropertyDefinitions()
403                 .get(PropertyIds.OBJECT_ID);
404         if (objectIdPropDef == null) {
405             return null;
406         }
407 
408         String objectIdQueryName = objectIdPropDef.getQueryName();
409         if (objectIdQueryName == null) {
410             return null;
411         }
412 
413         // determine the change token query name
414         PropertyDefinition<?> changeTokenPropDef = getCmisObject().getType().getPropertyDefinitions()
415                 .get(PropertyIds.CHANGE_TOKEN);
416         if (changeTokenPropDef == null) {
417             return null;
418         }
419 
420         String changeTokenQueryName = changeTokenPropDef.getQueryName();
421         if (changeTokenQueryName == null) {
422             return null;
423         }
424 
425         // get the change token property
426         Properties properties = getBinding().getObjectService().getProperties(getRepositoryId(), objectId,
427                 objectIdQueryName + "," + changeTokenQueryName, null);
428 
429         // if a change token is set, return it
430         PropertyData<?> changeToken = properties.getProperties().get(PropertyIds.CHANGE_TOKEN);
431 
432         if ((changeToken == null) || (changeToken.getFirstValue() == null)) {
433             return null;
434         }
435 
436         return changeToken.getFirstValue().toString();
437     }
438 
439     protected boolean saveDelete(String objectId) {
440         if (isMarkedForDelete) {
441             getBinding().getObjectService().deleteObject(getRepositoryId(), objectId, deleteAllVersions, null);
442             return true;
443         }
444 
445         return false;
446     }
447 
448     protected Properties prepareProperties() {
449         Set<Updatability> updatebility = new HashSet<Updatability>();
450         updatebility.add(Updatability.READWRITE);
451 
452         // check if checked out
453         Boolean isCheckedOut = getPropertyValue(PropertyIds.IS_VERSION_SERIES_CHECKED_OUT);
454         if ((isCheckedOut != null) && isCheckedOut.booleanValue()) {
455             updatebility.add(Updatability.WHENCHECKEDOUT);
456         }
457 
458         // convert properties
459         Properties result = getObjectFactory().convertProperties(properties, getType(), updatebility);
460 
461         // extensions
462         List<CmisExtensionElement> extensions = ouputExtensions.get(ExtensionLevel.PROPERTIES);
463         if (extensions != null) {
464             result.setExtensions(extensions);
465         }
466 
467         return result;
468     }
469 
470     protected String saveProperties(String objectId, String changeToken) {
471         if (isPropertyUpdateRequired) {
472             Holder<String> objectIdHolder = new Holder<String>(objectId);
473             Holder<String> changeTokenHolder = new Holder<String>(changeToken);
474 
475             // convert properties
476             Properties props = prepareProperties();
477 
478             // it's time to update
479             getBinding().getObjectService().updateProperties(getRepositoryId(), objectIdHolder, changeTokenHolder,
480                     props, null);
481 
482             if (objectIdHolder.getValue() != null) {
483                 return objectIdHolder.getValue();
484             }
485         }
486 
487         return objectId;
488     }
489 
490     protected void savePolicies(String objectId) {
491         // add policies
492         for (String policyId : addPolicies) {
493             getBinding().getPolicyService().applyPolicy(getRepositoryId(), policyId, objectId, null);
494         }
495 
496         // remove policies
497         for (String policyId : removePolicies) {
498             getBinding().getPolicyService().removePolicy(getRepositoryId(), policyId, objectId, null);
499         }
500     }
501 
502     protected Acl prepareAcl(List<AceChangeHolder> achList) {
503         if ((achList == null) || (achList.isEmpty())) {
504             return null;
505         }
506 
507         ObjectFactory of = getObjectFactory();
508 
509         List<Ace> aces = new ArrayList<Ace>();
510         for (AceChangeHolder ach : achList) {
511             aces.add(of.createAce(ach.getPrincipalId(), ach.getPermissions()));
512         }
513 
514         return of.createAcl(aces);
515     }
516 
517     protected void saveACL(String objectId) {
518         for (AclPropagation ap : AclPropagation.values()) {
519             if (!addAces.containsKey(ap) && !removeAces.containsKey(ap)) {
520                 continue;
521             }
522 
523             getBinding().getAclService().applyAcl(getRepositoryId(), objectId, prepareAcl(addAces.get(ap)),
524                     prepareAcl(removeAces.get(ap)), ap, null);
525         }
526 
527         if (addAces.containsKey(null) || removeAces.containsKey(null)) {
528             getBinding().getAclService().applyAcl(getRepositoryId(), objectId, prepareAcl(addAces.get(null)),
529                     prepareAcl(removeAces.get(null)), null, null);
530         }
531     }
532 
533     // --- internal ---
534 
535     /**
536      * Checks if a value matches a property definition.
537      * <p>
538      * Returns a list of values.
539      */
540     @SuppressWarnings("unchecked")
541     private static <T> List<T> checkProperty(PropertyDefinition<T> propertyDefinition, Object value) {
542 
543         // null values are ok for updates
544         if (value == null) {
545             return null;
546         }
547 
548         // single and multi value check
549         List<T> values = null;
550         if (value instanceof List<?>) {
551             if (propertyDefinition.getCardinality() != Cardinality.MULTI) {
552                 throw new IllegalArgumentException("Property '" + propertyDefinition.getId()
553                         + "' is not a multi value property!");
554             }
555 
556             values = (List<T>) value;
557             if (values.isEmpty()) {
558                 return values;
559             }
560         } else {
561             if (propertyDefinition.getCardinality() != Cardinality.SINGLE) {
562                 throw new IllegalArgumentException("Property '" + propertyDefinition.getId()
563                         + "' is not a single value property!");
564             }
565 
566             values = Collections.singletonList((T) value);
567         }
568 
569         // check if list contains null values
570         for (Object o : values) {
571             if (o == null) {
572                 throw new IllegalArgumentException("Property '" + propertyDefinition.getId()
573                         + "' contains null values!");
574             }
575         }
576 
577         // take a sample and test the data type
578         boolean typeMatch = false;
579         Object firstValue = values.get(0);
580 
581         switch (propertyDefinition.getPropertyType()) {
582         case STRING:
583         case ID:
584         case URI:
585         case HTML:
586             typeMatch = (firstValue instanceof String);
587             break;
588         case INTEGER:
589             typeMatch = (firstValue instanceof BigInteger) || (firstValue instanceof Byte)
590                     || (firstValue instanceof Short) || (firstValue instanceof Integer) || (firstValue instanceof Long);
591             break;
592         case DECIMAL:
593             typeMatch = (firstValue instanceof BigDecimal);
594             break;
595         case BOOLEAN:
596             typeMatch = (firstValue instanceof Boolean);
597             break;
598         case DATETIME:
599             typeMatch = (firstValue instanceof GregorianCalendar);
600             break;
601         }
602 
603         if (!typeMatch) {
604             throw new IllegalArgumentException("Value of property '" + propertyDefinition.getId()
605                     + "' does not match property type!");
606         }
607 
608         return values;
609     }
610 
611     // --- ACE helper class ---
612 
613     public static class AceChangeHolder implements Serializable {
614         private static final long serialVersionUID = 1L;
615 
616         private final String principalId;
617         private final List<String> permissions;
618         private final AclPropagation aclPropagation;
619 
620         public AceChangeHolder(String principalId, List<String> permissions, AclPropagation aclPropagation) {
621             if ((principalId == null) || (principalId.length() == 0)) {
622                 throw new IllegalArgumentException("Principal id must be set!");
623             }
624 
625             if ((permissions == null) || (permissions.size() == 0)) {
626                 throw new IllegalArgumentException("Permissions id must be set!");
627             }
628 
629             this.principalId = principalId;
630             this.permissions = permissions;
631             this.aclPropagation = aclPropagation;
632         }
633 
634         public String getPrincipalId() {
635             return principalId;
636         }
637 
638         public List<String> getPermissions() {
639             return permissions;
640         }
641 
642         public AclPropagation getAclPropagation() {
643             return aclPropagation;
644         }
645     }
646 }