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.runtime.repository;
20  
21  import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNotEmpty;
22  import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNullOrEmpty;
23  
24  import java.io.InputStream;
25  import java.io.Serializable;
26  import java.math.BigDecimal;
27  import java.math.BigInteger;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.Date;
32  import java.util.GregorianCalendar;
33  import java.util.HashMap;
34  import java.util.LinkedHashMap;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  
39  import org.apache.chemistry.opencmis.client.api.ChangeEvent;
40  import org.apache.chemistry.opencmis.client.api.ChangeEvents;
41  import org.apache.chemistry.opencmis.client.api.CmisObject;
42  import org.apache.chemistry.opencmis.client.api.ObjectFactory;
43  import org.apache.chemistry.opencmis.client.api.ObjectType;
44  import org.apache.chemistry.opencmis.client.api.OperationContext;
45  import org.apache.chemistry.opencmis.client.api.Policy;
46  import org.apache.chemistry.opencmis.client.api.Property;
47  import org.apache.chemistry.opencmis.client.api.QueryResult;
48  import org.apache.chemistry.opencmis.client.api.Rendition;
49  import org.apache.chemistry.opencmis.client.api.SecondaryType;
50  import org.apache.chemistry.opencmis.client.api.Session;
51  import org.apache.chemistry.opencmis.client.runtime.ChangeEventImpl;
52  import org.apache.chemistry.opencmis.client.runtime.ChangeEventsImpl;
53  import org.apache.chemistry.opencmis.client.runtime.DocumentImpl;
54  import org.apache.chemistry.opencmis.client.runtime.FolderImpl;
55  import org.apache.chemistry.opencmis.client.runtime.ItemImpl;
56  import org.apache.chemistry.opencmis.client.runtime.PolicyImpl;
57  import org.apache.chemistry.opencmis.client.runtime.PropertyImpl;
58  import org.apache.chemistry.opencmis.client.runtime.QueryResultImpl;
59  import org.apache.chemistry.opencmis.client.runtime.RelationshipImpl;
60  import org.apache.chemistry.opencmis.client.runtime.RenditionImpl;
61  import org.apache.chemistry.opencmis.client.runtime.SessionImpl;
62  import org.apache.chemistry.opencmis.client.runtime.objecttype.DocumentTypeImpl;
63  import org.apache.chemistry.opencmis.client.runtime.objecttype.FolderTypeImpl;
64  import org.apache.chemistry.opencmis.client.runtime.objecttype.ItemTypeImpl;
65  import org.apache.chemistry.opencmis.client.runtime.objecttype.PolicyTypeImpl;
66  import org.apache.chemistry.opencmis.client.runtime.objecttype.RelationshipTypeImpl;
67  import org.apache.chemistry.opencmis.client.runtime.objecttype.SecondaryTypeImpl;
68  import org.apache.chemistry.opencmis.commons.PropertyIds;
69  import org.apache.chemistry.opencmis.commons.data.Ace;
70  import org.apache.chemistry.opencmis.commons.data.Acl;
71  import org.apache.chemistry.opencmis.commons.data.ContentStream;
72  import org.apache.chemistry.opencmis.commons.data.ObjectData;
73  import org.apache.chemistry.opencmis.commons.data.ObjectList;
74  import org.apache.chemistry.opencmis.commons.data.Properties;
75  import org.apache.chemistry.opencmis.commons.data.PropertyData;
76  import org.apache.chemistry.opencmis.commons.data.PropertyId;
77  import org.apache.chemistry.opencmis.commons.data.RenditionData;
78  import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
79  import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition;
80  import org.apache.chemistry.opencmis.commons.definitions.FolderTypeDefinition;
81  import org.apache.chemistry.opencmis.commons.definitions.ItemTypeDefinition;
82  import org.apache.chemistry.opencmis.commons.definitions.PolicyTypeDefinition;
83  import org.apache.chemistry.opencmis.commons.definitions.PropertyBooleanDefinition;
84  import org.apache.chemistry.opencmis.commons.definitions.PropertyDateTimeDefinition;
85  import org.apache.chemistry.opencmis.commons.definitions.PropertyDecimalDefinition;
86  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
87  import org.apache.chemistry.opencmis.commons.definitions.PropertyHtmlDefinition;
88  import org.apache.chemistry.opencmis.commons.definitions.PropertyIdDefinition;
89  import org.apache.chemistry.opencmis.commons.definitions.PropertyIntegerDefinition;
90  import org.apache.chemistry.opencmis.commons.definitions.PropertyStringDefinition;
91  import org.apache.chemistry.opencmis.commons.definitions.PropertyUriDefinition;
92  import org.apache.chemistry.opencmis.commons.definitions.RelationshipTypeDefinition;
93  import org.apache.chemistry.opencmis.commons.definitions.SecondaryTypeDefinition;
94  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
95  import org.apache.chemistry.opencmis.commons.enums.Cardinality;
96  import org.apache.chemistry.opencmis.commons.enums.ChangeType;
97  import org.apache.chemistry.opencmis.commons.enums.Updatability;
98  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
99  import org.apache.chemistry.opencmis.commons.impl.DateTimeHelper;
100 import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
101 import org.apache.chemistry.opencmis.commons.impl.dataobjects.PartialContentStreamImpl;
102 import org.apache.chemistry.opencmis.commons.spi.BindingsObjectFactory;
103 
104 /**
105  * Persistent model object factory.
106  */
107 public class ObjectFactoryImpl implements ObjectFactory, Serializable {
108 
109     private static final long serialVersionUID = 1L;
110 
111     private Session session;
112 
113     /**
114      * Default constructor.
115      */
116     public ObjectFactoryImpl() {
117     }
118 
119     @Override
120     public void initialize(Session session, Map<String, String> parameters) {
121         assert session != null;
122 
123         this.session = session;
124     }
125 
126     /**
127      * Returns the bindings object factory.
128      */
129     protected BindingsObjectFactory getBindingsObjectFactory() {
130         return session.getBinding().getObjectFactory();
131     }
132 
133     // repository info
134 
135     @Override
136     public RepositoryInfo convertRepositoryInfo(RepositoryInfo repositoryInfo) {
137         return repositoryInfo;
138     }
139 
140     // ACL and ACE
141 
142     @Override
143     public Acl convertAces(List<Ace> aces) {
144         if (aces == null) {
145             return null;
146         }
147 
148         BindingsObjectFactory bof = getBindingsObjectFactory();
149 
150         List<Ace> bindingAces = new ArrayList<Ace>();
151         for (Ace ace : aces) {
152             bindingAces.add(bof.createAccessControlEntry(ace.getPrincipalId(), ace.getPermissions()));
153         }
154 
155         return bof.createAccessControlList(bindingAces);
156     }
157 
158     @Override
159     public Ace createAce(String principal, List<String> permissions) {
160         BindingsObjectFactory bof = getBindingsObjectFactory();
161 
162         Ace ace = bof.createAccessControlEntry(principal, permissions);
163 
164         return ace;
165     }
166 
167     @Override
168     public Acl createAcl(List<Ace> aces) {
169         BindingsObjectFactory bof = getBindingsObjectFactory();
170 
171         Acl acl = bof.createAccessControlList(aces);
172 
173         return acl;
174     }
175 
176     // policies
177 
178     @Override
179     public List<String> convertPolicies(List<Policy> policies) {
180         if (policies == null) {
181             return null;
182         }
183 
184         List<String> result = new ArrayList<String>();
185 
186         for (Policy policy : policies) {
187             if ((policy != null) && (policy.getId() != null)) {
188                 result.add(policy.getId());
189             }
190         }
191 
192         return result;
193     }
194 
195     // renditions
196 
197     @Override
198     public Rendition convertRendition(String objectId, RenditionData rendition) {
199         if (rendition == null) {
200             throw new IllegalArgumentException("Rendition must be set!");
201         }
202 
203         long length = (rendition.getBigLength() == null ? -1 : rendition.getBigLength().longValue());
204         int height = (rendition.getBigHeight() == null ? -1 : rendition.getBigHeight().intValue());
205         int width = (rendition.getBigWidth() == null ? -1 : rendition.getBigWidth().intValue());
206 
207         return new RenditionImpl(this.session, objectId, rendition.getStreamId(), rendition.getRenditionDocumentId(),
208                 rendition.getKind(), length, rendition.getMimeType(), rendition.getTitle(), height, width);
209     }
210 
211     // content stream
212 
213     @Override
214     public ContentStream createContentStream(String filename, long length, String mimetype, InputStream stream) {
215         return createContentStream(filename, length, mimetype, stream, false);
216     }
217 
218     @Override
219     public ContentStream createContentStream(String filename, long length, String mimetype, InputStream stream,
220             boolean partial) {
221         if (partial) {
222             return new PartialContentStreamImpl(filename, (length < 0 ? null : BigInteger.valueOf(length)), mimetype,
223                     stream);
224         } else {
225             return new ContentStreamImpl(filename, (length < 0 ? null : BigInteger.valueOf(length)), mimetype, stream);
226         }
227     }
228 
229     @Override
230     public ContentStream convertContentStream(ContentStream contentStream) {
231         if (contentStream == null) {
232             return null;
233         }
234 
235         BigInteger length = (contentStream.getLength() < 0 ? null : BigInteger.valueOf(contentStream.getLength()));
236 
237         return getBindingsObjectFactory().createContentStream(contentStream.getFileName(), length,
238                 contentStream.getMimeType(), contentStream.getStream());
239     }
240 
241     // types
242 
243     @Override
244     public ObjectType convertTypeDefinition(TypeDefinition typeDefinition) {
245         if (typeDefinition instanceof DocumentTypeDefinition) {
246             return new DocumentTypeImpl(this.session, (DocumentTypeDefinition) typeDefinition);
247         } else if (typeDefinition instanceof FolderTypeDefinition) {
248             return new FolderTypeImpl(this.session, (FolderTypeDefinition) typeDefinition);
249         } else if (typeDefinition instanceof RelationshipTypeDefinition) {
250             return new RelationshipTypeImpl(this.session, (RelationshipTypeDefinition) typeDefinition);
251         } else if (typeDefinition instanceof PolicyTypeDefinition) {
252             return new PolicyTypeImpl(this.session, (PolicyTypeDefinition) typeDefinition);
253         } else if (typeDefinition instanceof ItemTypeDefinition) {
254             return new ItemTypeImpl(this.session, (ItemTypeDefinition) typeDefinition);
255         } else if (typeDefinition instanceof SecondaryTypeDefinition) {
256             return new SecondaryTypeImpl(this.session, (SecondaryTypeDefinition) typeDefinition);
257         } else if (typeDefinition == null) {
258             throw new CmisRuntimeException("No base type supplied!");
259         } else {
260             throw new CmisRuntimeException("Unknown base type! Received " + typeDefinition.getClass().getName());
261         }
262     }
263 
264     @Override
265     public ObjectType getTypeFromObjectData(ObjectData objectData) {
266         if (objectData == null || objectData.getProperties() == null
267                 || objectData.getProperties().getProperties() == null) {
268             return null;
269         }
270 
271         PropertyData<?> typeProperty = objectData.getProperties().getProperties().get(PropertyIds.OBJECT_TYPE_ID);
272         if (!(typeProperty instanceof PropertyId)) {
273             return null;
274         }
275 
276         return this.session.getTypeDefinition((String) typeProperty.getFirstValue());
277     }
278 
279     // properties
280 
281     @Override
282     public <T> Property<T> createProperty(PropertyDefinition<T> type, List<T> values) {
283         return new PropertyImpl<T>(type, values);
284     }
285 
286     @SuppressWarnings("unchecked")
287     protected <T> Property<T> convertProperty(ObjectType objectType, Collection<SecondaryType> secondaryTypes,
288             PropertyData<T> pd) {
289 
290         // handle invalid property IDs
291         if (pd.getId() == null || pd.getId().length() == 0) {
292             StringBuilder sb = null;
293             if (isNotEmpty(secondaryTypes)) {
294                 sb = new StringBuilder(128);
295                 sb.append(" or a secondary type of the object (");
296                 addSecondaryTypeIds(secondaryTypes, sb);
297                 sb.append(')');
298             }
299 
300             throw new CmisRuntimeException(
301                     "Cannot convert a property because it has no ID! The property is supposed to be part of the type '"
302                             + objectType.getId() + "'" + (sb == null ? "" : sb.toString())
303                             + ". The value of this property is: " + pd.getValues());
304         }
305 
306         PropertyDefinition<T> definition = (PropertyDefinition<T>) objectType.getPropertyDefinitions().get(pd.getId());
307 
308         // search secondary types
309         if (definition == null && secondaryTypes != null) {
310             for (SecondaryType secondaryType : secondaryTypes) {
311                 if (secondaryType != null && secondaryType.getPropertyDefinitions() != null) {
312                     definition = (PropertyDefinition<T>) secondaryType.getPropertyDefinitions().get(pd.getId());
313                     if (definition != null) {
314                         break;
315                     }
316                 }
317             }
318         }
319 
320         // the type might have changed -> reload type definitions
321         if (definition == null) {
322             TypeDefinition reloadedObjectType = session.getTypeDefinition(objectType.getId(), false);
323             definition = (PropertyDefinition<T>) reloadedObjectType.getPropertyDefinitions().get(pd.getId());
324 
325             if (definition == null && secondaryTypes != null) {
326                 for (SecondaryType secondaryType : secondaryTypes) {
327                     if (secondaryType != null) {
328                         TypeDefinition reloadedSecondaryType = session.getTypeDefinition(secondaryType.getId(), false);
329                         if (reloadedSecondaryType.getPropertyDefinitions() != null) {
330                             definition = (PropertyDefinition<T>) reloadedSecondaryType.getPropertyDefinitions().get(
331                                     pd.getId());
332                             if (definition != null) {
333                                 break;
334                             }
335                         }
336                     }
337                 }
338             }
339         }
340 
341         if (definition == null) {
342             // property without definition
343 
344             StringBuilder sb = null;
345             if (isNotEmpty(secondaryTypes)) {
346                 sb = new StringBuilder(128);
347                 sb.append(" or any secondary type of the object (");
348                 addSecondaryTypeIds(secondaryTypes, sb);
349                 sb.append(')');
350             }
351 
352             throw new CmisRuntimeException(
353                     "Cannot convert property '"
354                             + pd.getId()
355                             + "' because it does not exist in the object type. The property is supposed to be part of the type '"
356                             + objectType.getId() + "'" + (sb == null ? "" : sb.toString())
357                             + ". The value of this property is: " + pd.getValues());
358         }
359 
360         return createProperty(definition, pd.getValues());
361     }
362 
363     private void addSecondaryTypeIds(Collection<SecondaryType> secondaryTypes, StringBuilder sb) {
364         boolean first = true;
365         for (SecondaryType secondaryType : secondaryTypes) {
366             if (first) {
367                 first = false;
368             } else {
369                 sb.append(", ");
370             }
371 
372             sb.append('\'');
373             sb.append(secondaryType.getId());
374             sb.append('\'');
375         }
376     }
377 
378     @Override
379     public Map<String, Property<?>> convertProperties(ObjectType objectType, Collection<SecondaryType> secondaryTypes,
380             Properties properties) {
381         // check input
382         if (objectType == null) {
383             throw new IllegalArgumentException("Object type must set!");
384         }
385 
386         if (objectType.getPropertyDefinitions() == null) {
387             throw new IllegalArgumentException("Object type has no property defintions!");
388         }
389 
390         if (properties == null || properties.getProperties() == null) {
391             throw new IllegalArgumentException("Properties must be set!");
392         }
393 
394         // iterate through properties and convert them
395         Map<String, Property<?>> result = new LinkedHashMap<String, Property<?>>();
396         for (Map.Entry<String, PropertyData<?>> entry : properties.getProperties().entrySet()) {
397             // find property definition
398             Property<?> apiProperty = convertProperty(objectType, secondaryTypes, entry.getValue());
399             result.put(entry.getKey(), apiProperty);
400         }
401 
402         return result;
403     }
404 
405     @Override
406     @SuppressWarnings({ "unchecked", "rawtypes" })
407     public Properties convertProperties(Map<String, ?> properties, ObjectType type,
408             Collection<SecondaryType> secondaryTypes, Set<Updatability> updatabilityFilter) {
409         // check input
410         if (properties == null) {
411             return null;
412         }
413 
414         // get the type
415         if (type == null) {
416             Object typeId = properties.get(PropertyIds.OBJECT_TYPE_ID);
417 
418             if (typeId instanceof String) {
419                 type = session.getTypeDefinition(typeId.toString());
420             } else if (typeId instanceof List && !((List) typeId).isEmpty() && ((List) typeId).get(0) instanceof String) {
421                 type = session.getTypeDefinition(((List) typeId).get(0).toString());
422             } else {
423                 throw new IllegalArgumentException("Type or type property must be set!");
424             }
425         }
426 
427         // get secondary types
428         Collection<SecondaryType> allSecondaryTypes = null;
429         Object secondaryTypeIds = properties.get(PropertyIds.SECONDARY_OBJECT_TYPE_IDS);
430         if (secondaryTypeIds instanceof List) {
431             allSecondaryTypes = new ArrayList<SecondaryType>();
432 
433             for (Object secondaryTypeId : (List<?>) secondaryTypeIds) {
434                 if (!(secondaryTypeId instanceof String)) {
435                     throw new IllegalArgumentException("Secondary types property contains an invalid entry: "
436                             + secondaryTypeId);
437                 }
438 
439                 ObjectType secondaryType = session.getTypeDefinition(secondaryTypeId.toString());
440                 if (!(secondaryType instanceof SecondaryType)) {
441                     throw new IllegalArgumentException(
442                             "Secondary types property contains a type that is not a secondary type: " + secondaryTypeId);
443                 }
444 
445                 allSecondaryTypes.add((SecondaryType) secondaryType);
446             }
447         }
448 
449         if (secondaryTypes != null && allSecondaryTypes == null) {
450             allSecondaryTypes = secondaryTypes;
451         }
452 
453         // some preparation
454         BindingsObjectFactory bof = getBindingsObjectFactory();
455         List<PropertyData<?>> propertyList = new ArrayList<PropertyData<?>>();
456 
457         // the big loop
458         for (Map.Entry<String, ?> property : properties.entrySet()) {
459             if ((property == null) || (property.getKey() == null)) {
460                 continue;
461             }
462 
463             String id = property.getKey();
464             Object value = property.getValue();
465 
466             if (value instanceof Property<?>) {
467                 Property<?> p = (Property<?>) value;
468                 if (!id.equals(p.getId())) {
469                     throw new IllegalArgumentException("Property id mismatch: '" + id + "' != '" + p.getId() + "'!");
470                 }
471                 value = (p.getDefinition().getCardinality() == Cardinality.SINGLE ? p.getFirstValue() : p.getValues());
472             }
473 
474             // get the property definition
475             PropertyDefinition<?> definition = type.getPropertyDefinitions().get(id);
476 
477             if (definition == null && allSecondaryTypes != null) {
478                 for (SecondaryType secondaryType : allSecondaryTypes) {
479                     if (secondaryType != null && secondaryType.getPropertyDefinitions() != null) {
480                         definition = secondaryType.getPropertyDefinitions().get(id);
481                         if (definition != null) {
482                             break;
483                         }
484                     }
485                 }
486             }
487 
488             if (definition == null) {
489                 throw new IllegalArgumentException("Property '" + id
490                         + "' is not valid for this type or one of the secondary types!");
491             }
492 
493             // check updatability
494             if (updatabilityFilter != null) {
495                 if (!updatabilityFilter.contains(definition.getUpdatability())) {
496                     continue;
497                 }
498             }
499 
500             // single and multi value check
501             List<?> values;
502             if (value == null) {
503                 values = null;
504             } else if (value instanceof List<?>) {
505                 if (definition.getCardinality() != Cardinality.MULTI) {
506                     throw new IllegalArgumentException("Property '" + id + "' is not a multi value property!");
507                 }
508                 values = (List<?>) value;
509 
510                 // check if the list is homogeneous and does not contain null
511                 // values
512                 Class<?> valueClazz = null;
513                 for (Object o : values) {
514                     if (o == null) {
515                         throw new IllegalArgumentException("Property '" + id + "' contains null values!");
516                     }
517                     if (valueClazz == null) {
518                         valueClazz = o.getClass();
519                     } else {
520                         if (!valueClazz.isInstance(o)) {
521                             throw new IllegalArgumentException("Property '" + id + "' is inhomogeneous!");
522                         }
523                     }
524                 }
525             } else {
526                 if (definition.getCardinality() != Cardinality.SINGLE) {
527                     throw new IllegalArgumentException("Property '" + id + "' is not a single value property!");
528                 }
529                 values = Collections.singletonList(value);
530             }
531 
532             // assemble property
533             PropertyData<?> propertyData = null;
534             Object firstValue = (isNullOrEmpty(values) ? null : values.get(0));
535 
536             if (definition instanceof PropertyStringDefinition) {
537                 if (firstValue == null) {
538                     propertyData = bof.createPropertyStringData(id, (List<String>) null);
539                 } else if (firstValue instanceof String) {
540                     propertyData = bof.createPropertyStringData(id, (List<String>) values);
541                 } else {
542                     throwWrongTypeError(firstValue, "string", String.class, id);
543                 }
544             } else if (definition instanceof PropertyIdDefinition) {
545                 if (firstValue == null) {
546                     propertyData = bof.createPropertyIdData(id, (List<String>) null);
547                 } else if (firstValue instanceof String) {
548                     propertyData = bof.createPropertyIdData(id, (List<String>) values);
549                 } else {
550                     throwWrongTypeError(firstValue, "string", String.class, id);
551                 }
552             } else if (definition instanceof PropertyHtmlDefinition) {
553                 if (firstValue == null) {
554                     propertyData = bof.createPropertyHtmlData(id, (List<String>) values);
555                 } else if (firstValue instanceof String) {
556                     propertyData = bof.createPropertyHtmlData(id, (List<String>) values);
557                 } else {
558                     throwWrongTypeError(firstValue, "html", String.class, id);
559                 }
560             } else if (definition instanceof PropertyUriDefinition) {
561                 if (firstValue == null) {
562                     propertyData = bof.createPropertyUriData(id, (List<String>) null);
563                 } else if (firstValue instanceof String) {
564                     propertyData = bof.createPropertyUriData(id, (List<String>) values);
565                 } else {
566                     throwWrongTypeError(firstValue, "uri", String.class, id);
567                 }
568             } else if (definition instanceof PropertyIntegerDefinition) {
569                 if (firstValue == null) {
570                     propertyData = bof.createPropertyIntegerData(id, (List<BigInteger>) null);
571                 } else if (firstValue instanceof BigInteger) {
572                     propertyData = bof.createPropertyIntegerData(id, (List<BigInteger>) values);
573                 } else if ((firstValue instanceof Byte) || (firstValue instanceof Short)
574                         || (firstValue instanceof Integer) || (firstValue instanceof Long)) {
575                     // we accept all kinds of integers
576                     List<BigInteger> list = new ArrayList<BigInteger>(values.size());
577                     for (Object v : values) {
578                         list.add(BigInteger.valueOf(((Number) v).longValue()));
579                     }
580 
581                     propertyData = bof.createPropertyIntegerData(id, list);
582                 } else {
583                     throwWrongTypeError(firstValue, "integer", BigInteger.class, id);
584                 }
585             } else if (definition instanceof PropertyBooleanDefinition) {
586                 if (firstValue == null) {
587                     propertyData = bof.createPropertyBooleanData(id, (List<Boolean>) null);
588                 } else if (firstValue instanceof Boolean) {
589                     propertyData = bof.createPropertyBooleanData(id, (List<Boolean>) values);
590                 } else {
591                     throwWrongTypeError(firstValue, "boolean", Boolean.class, id);
592                 }
593             } else if (definition instanceof PropertyDecimalDefinition) {
594                 if (firstValue == null) {
595                     propertyData = bof.createPropertyDecimalData(id, (List<BigDecimal>) null);
596                 } else if (firstValue instanceof BigDecimal) {
597                     propertyData = bof.createPropertyDecimalData(id, (List<BigDecimal>) values);
598                 } else if ((firstValue instanceof Float) || (firstValue instanceof Double)
599                         || (firstValue instanceof Byte) || (firstValue instanceof Short)
600                         || (firstValue instanceof Integer) || (firstValue instanceof Long)) {
601                     // we accept all kinds of integers
602                     // as well as floats and doubles
603                     List<BigDecimal> list = new ArrayList<BigDecimal>(values.size());
604                     for (Object v : values) {
605                         list.add(new BigDecimal(v.toString()));
606                     }
607 
608                     propertyData = bof.createPropertyDecimalData(id, list);
609                 } else {
610                     throwWrongTypeError(firstValue, "decimal", BigDecimal.class, id);
611                 }
612             } else if (definition instanceof PropertyDateTimeDefinition) {
613                 if (firstValue == null) {
614                     propertyData = bof.createPropertyDateTimeData(id, (List<GregorianCalendar>) null);
615                 } else if (firstValue instanceof GregorianCalendar) {
616                     propertyData = bof.createPropertyDateTimeData(id, (List<GregorianCalendar>) values);
617                 } else if (firstValue instanceof Date) {
618                     List<GregorianCalendar> list = new ArrayList<GregorianCalendar>(values.size());
619                     for (Object d : values) {
620                         GregorianCalendar cal = new GregorianCalendar();
621                         cal.setTimeZone(DateTimeHelper.GMT);
622                         cal.setTime((Date) d);
623                         list.add(cal);
624                     }
625                     propertyData = bof.createPropertyDateTimeData(id, list);
626                 } else {
627                     throwWrongTypeError(firstValue, "datetime", GregorianCalendar.class, id);
628                 }
629             }
630 
631             // do we have something?
632             if (propertyData == null) {
633                 throw new IllegalArgumentException("Property '" + id + "' doesn't match the property defintion!");
634             }
635 
636             propertyList.add(propertyData);
637         }
638 
639         return bof.createPropertiesData(propertyList);
640     }
641 
642     @Override
643     public List<PropertyData<?>> convertQueryProperties(Properties properties) {
644         // check input
645         if ((properties == null) || (properties.getProperties() == null)) {
646             throw new IllegalArgumentException("Properties must be set!");
647         }
648         return new ArrayList<PropertyData<?>>(properties.getPropertyList());
649     }
650 
651     // objects
652 
653     @Override
654     public CmisObject convertObject(ObjectData objectData, OperationContext context) {
655         if (objectData == null) {
656             throw new IllegalArgumentException("Object data is null!");
657         }
658 
659         if (objectData.getId() == null) {
660             throw new IllegalArgumentException("Object ID property not set!");
661         }
662 
663         if (objectData.getBaseTypeId() == null) {
664             throw new IllegalArgumentException("Base type ID property not set!");
665         }
666 
667         ObjectType type = getTypeFromObjectData(objectData);
668 
669         /* determine type */
670         switch (objectData.getBaseTypeId()) {
671         case CMIS_DOCUMENT:
672             return new DocumentImpl((SessionImpl) session, type, objectData, context);
673         case CMIS_FOLDER:
674             return new FolderImpl((SessionImpl) session, type, objectData, context);
675         case CMIS_POLICY:
676             return new PolicyImpl((SessionImpl) session, type, objectData, context);
677         case CMIS_RELATIONSHIP:
678             return new RelationshipImpl((SessionImpl) session, type, objectData, context);
679         case CMIS_ITEM:
680             return new ItemImpl((SessionImpl) session, type, objectData, context);
681         case CMIS_SECONDARY:
682             throw new CmisRuntimeException("Secondary type is used as object type: " + objectData.getBaseTypeId());
683         default:
684             throw new CmisRuntimeException("Unsupported base type: " + objectData.getBaseTypeId());
685         }
686     }
687 
688     @Override
689     public QueryResult convertQueryResult(ObjectData objectData) {
690         if (objectData == null) {
691             throw new IllegalArgumentException("Object data is null!");
692         }
693 
694         return new QueryResultImpl(session, objectData);
695     }
696 
697     @Override
698     public ChangeEvent convertChangeEvent(ObjectData objectData) {
699         ChangeType changeType = null;
700         GregorianCalendar changeTime = null;
701         String objectId = null;
702         Map<String, List<?>> properties = null;
703         List<String> policyIds = null;
704         Acl acl = null;
705 
706         if (objectData.getChangeEventInfo() != null) {
707             changeType = objectData.getChangeEventInfo().getChangeType();
708             changeTime = objectData.getChangeEventInfo().getChangeTime();
709         }
710 
711         if ((objectData.getProperties() != null) && (objectData.getProperties().getPropertyList() != null)) {
712             properties = new HashMap<String, List<?>>(objectData.getProperties().getPropertyList().size());
713 
714             for (PropertyData<?> property : objectData.getProperties().getPropertyList()) {
715                 properties.put(property.getId(), property.getValues());
716             }
717 
718             if (properties.containsKey(PropertyIds.OBJECT_ID)) {
719                 List<?> objectIdList = properties.get(PropertyIds.OBJECT_ID);
720                 if (isNotEmpty(objectIdList)) {
721                     objectId = objectIdList.get(0).toString();
722                 }
723             }
724 
725             if ((objectData.getPolicyIds() != null) && (objectData.getPolicyIds().getPolicyIds() != null)) {
726                 policyIds = objectData.getPolicyIds().getPolicyIds();
727             }
728 
729             if (objectData.getAcl() != null) {
730                 acl = objectData.getAcl();
731             }
732         }
733 
734         return new ChangeEventImpl(changeType, changeTime, objectId, properties, policyIds, acl);
735     }
736 
737     @Override
738     public ChangeEvents convertChangeEvents(String changeLogToken, ObjectList objectList) {
739         if (objectList == null) {
740             return null;
741         }
742 
743         List<ChangeEvent> events = new ArrayList<ChangeEvent>();
744         if (objectList.getObjects() != null) {
745             for (ObjectData objectData : objectList.getObjects()) {
746                 if (objectData == null) {
747                     continue;
748                 }
749 
750                 events.add(convertChangeEvent(objectData));
751             }
752         }
753 
754         boolean hasMoreItems = objectList.hasMoreItems() == null ? false : objectList.hasMoreItems().booleanValue();
755         long totalNumItems = objectList.getNumItems() == null ? -1 : objectList.getNumItems().longValue();
756 
757         return new ChangeEventsImpl(changeLogToken, events, hasMoreItems, totalNumItems);
758     }
759 
760     private void throwWrongTypeError(Object obj, String type, Class<?> clazz, String id) {
761         String expectedTypes;
762         if (BigInteger.class.isAssignableFrom(clazz)) {
763             expectedTypes = "<BigInteger, Byte, Short, Integer, Long>";
764         } else if (BigDecimal.class.isAssignableFrom(clazz)) {
765             expectedTypes = "<BigDecimal, Double, Float, Byte, Short, Integer, Long>";
766         } else if (GregorianCalendar.class.isAssignableFrom(clazz)) {
767             expectedTypes = "<java.util.GregorianCalendar, java.util.Date>";
768         } else {
769             expectedTypes = clazz.getName();
770         }
771 
772         String message = "Property '" + id + "' is a " + type + " property. Expected type '" + expectedTypes
773                 + "' but received a '" + obj.getClass().getName() + "' property.";
774 
775         throw new IllegalArgumentException(message);
776     }
777 }