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.util;
20  
21  import java.io.BufferedWriter;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.OutputStream;
26  import java.io.OutputStreamWriter;
27  import java.io.Writer;
28  import java.util.ArrayList;
29  import java.util.List;
30  import java.util.Map;
31  
32  import javax.xml.stream.XMLStreamException;
33  import javax.xml.stream.XMLStreamReader;
34  import javax.xml.stream.XMLStreamWriter;
35  
36  import org.apache.chemistry.opencmis.commons.definitions.DocumentTypeDefinition;
37  import org.apache.chemistry.opencmis.commons.definitions.FolderTypeDefinition;
38  import org.apache.chemistry.opencmis.commons.definitions.ItemTypeDefinition;
39  import org.apache.chemistry.opencmis.commons.definitions.PolicyTypeDefinition;
40  import org.apache.chemistry.opencmis.commons.definitions.PropertyBooleanDefinition;
41  import org.apache.chemistry.opencmis.commons.definitions.PropertyDateTimeDefinition;
42  import org.apache.chemistry.opencmis.commons.definitions.PropertyDecimalDefinition;
43  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
44  import org.apache.chemistry.opencmis.commons.definitions.PropertyHtmlDefinition;
45  import org.apache.chemistry.opencmis.commons.definitions.PropertyIdDefinition;
46  import org.apache.chemistry.opencmis.commons.definitions.PropertyIntegerDefinition;
47  import org.apache.chemistry.opencmis.commons.definitions.PropertyStringDefinition;
48  import org.apache.chemistry.opencmis.commons.definitions.PropertyUriDefinition;
49  import org.apache.chemistry.opencmis.commons.definitions.RelationshipTypeDefinition;
50  import org.apache.chemistry.opencmis.commons.definitions.SecondaryTypeDefinition;
51  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
52  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
53  import org.apache.chemistry.opencmis.commons.enums.Cardinality;
54  import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
55  import org.apache.chemistry.opencmis.commons.enums.DateTimeFormat;
56  import org.apache.chemistry.opencmis.commons.enums.PropertyType;
57  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
58  import org.apache.chemistry.opencmis.commons.impl.IOUtils;
59  import org.apache.chemistry.opencmis.commons.impl.JSONConverter;
60  import org.apache.chemistry.opencmis.commons.impl.XMLConstants;
61  import org.apache.chemistry.opencmis.commons.impl.XMLConverter;
62  import org.apache.chemistry.opencmis.commons.impl.XMLUtils;
63  import org.apache.chemistry.opencmis.commons.impl.json.parser.JSONParseException;
64  import org.apache.chemistry.opencmis.commons.impl.json.parser.JSONParser;
65  
66  public final class TypeUtils {
67  
68      private TypeUtils() {
69      }
70  
71      /**
72       * Serializes the type definition to XML, using the format defined in the
73       * CMIS specification.
74       * 
75       * The XML is UTF-8 encoded and the stream is not closed.
76       */
77      public static void writeToXML(TypeDefinition type, OutputStream stream) throws XMLStreamException {
78          if (type == null) {
79              throw new IllegalArgumentException("Type must be set!");
80          }
81          if (stream == null) {
82              throw new IllegalArgumentException("Output stream must be set!");
83          }
84  
85          XMLStreamWriter writer = XMLUtils.createWriter(stream);
86          XMLUtils.startXmlDocument(writer);
87          XMLConverter.writeTypeDefinition(writer, CmisVersion.CMIS_1_1, XMLConstants.NAMESPACE_CMIS, type);
88          XMLUtils.endXmlDocument(writer);
89          writer.close();
90      }
91  
92      /**
93       * Serializes the type definition to JSON, using the format defined in the
94       * CMIS specification.
95       * 
96       * The JSON is UTF-8 encoded and the stream is not closed.
97       */
98      public static void writeToJSON(TypeDefinition type, OutputStream stream) throws IOException {
99          if (type == null) {
100             throw new IllegalArgumentException("Type must be set!");
101         }
102         if (stream == null) {
103             throw new IllegalArgumentException("Output stream must be set!");
104         }
105 
106         Writer writer = new BufferedWriter(new OutputStreamWriter(stream, IOUtils.UTF8));
107         JSONConverter.convert(type, DateTimeFormat.SIMPLE).writeJSONString(writer);
108         writer.flush();
109     }
110 
111     /**
112      * Reads a type definition from a XML stream.
113      * 
114      * The stream must be UTF-8 encoded.
115      */
116     public static TypeDefinition readFromXML(InputStream stream) throws XMLStreamException {
117         if (stream == null) {
118             throw new IllegalArgumentException("Input stream must be set!");
119         }
120 
121         XMLStreamReader parser = XMLUtils.createParser(stream);
122         if (!XMLUtils.findNextStartElemenet(parser)) {
123             return null;
124         }
125 
126         TypeDefinition typeDef = XMLConverter.convertTypeDefinition(parser);
127 
128         parser.close();
129 
130         return typeDef;
131     }
132 
133     /**
134      * Reads a type definition from a JSON stream.
135      * 
136      * The stream must be UTF-8 encoded.
137      */
138     @SuppressWarnings("unchecked")
139     public static TypeDefinition readFromJSON(InputStream stream) throws IOException, JSONParseException {
140         if (stream == null) {
141             throw new IllegalArgumentException("Input stream must be set!");
142         }
143 
144         JSONParser parser = new JSONParser();
145         Object json = parser.parse(new InputStreamReader(stream, IOUtils.UTF8));
146 
147         if (!(json instanceof Map)) {
148             throw new CmisRuntimeException("Invalid stream! Not a type definition!");
149         }
150 
151         return JSONConverter.convertTypeDefinition((Map<String, Object>) json);
152     }
153 
154     /**
155      * Checks if a property query name is valid.
156      * 
157      * @param queryName
158      *            the query name
159      * @return {@code true} if the query name is valid, {@code false} otherwise
160      */
161     public static boolean checkQueryName(String queryName) {
162         return queryName != null && queryName.length() > 0 && queryName.indexOf(' ') < 0 && queryName.indexOf('\t') < 0
163                 && queryName.indexOf('\n') < 0 && queryName.indexOf('\r') < 0 && queryName.indexOf('\f') < 0
164                 && queryName.indexOf(',') < 0 && queryName.indexOf('"') < 0 && queryName.indexOf('\'') < 0
165                 && queryName.indexOf('\\') < 0 && queryName.indexOf('.') < 0 && queryName.indexOf('(') < 0
166                 && queryName.indexOf(')') < 0;
167     }
168 
169     /**
170      * Validates a type definition.
171      * 
172      * @return the list of validation errors
173      */
174     public static List<ValidationError> validateTypeDefinition(TypeDefinition type) {
175         if (type == null) {
176             throw new IllegalArgumentException("Type is null!");
177         }
178 
179         List<ValidationError> errors = new ArrayList<TypeUtils.ValidationError>();
180 
181         if (type.getId() == null || type.getId().length() == 0) {
182             errors.add(new ValidationError("id", "Type id must be set."));
183         }
184 
185         if (type.getLocalName() == null || type.getLocalName().length() == 0) {
186             errors.add(new ValidationError("localName", "Local name must be set."));
187         }
188 
189         if (type.getQueryName() != null) {
190             if (type.getQueryName().length() == 0) {
191                 errors.add(new ValidationError("queryName", "Query name must not be empty."));
192             } else if (!checkQueryName(type.getQueryName())) {
193                 errors.add(new ValidationError("queryName", "Query name contains invalid characters."));
194             }
195         }
196 
197         if (type.isCreatable() == null) {
198             errors.add(new ValidationError("creatable", "Creatable flag must be set."));
199         }
200 
201         if (type.isFileable() == null) {
202             errors.add(new ValidationError("fileable", "Fileable flag must be set."));
203         }
204 
205         if (type.isQueryable() == null) {
206             errors.add(new ValidationError("queryable", "Queryable flag must be set."));
207         } else if (type.isQueryable().booleanValue()) {
208             if (type.getQueryName() == null || type.getQueryName().length() == 0) {
209                 errors.add(new ValidationError("queryable",
210                         "Queryable flag is set to TRUE, but the query name is not set."));
211             }
212         }
213 
214         if (type.isControllablePolicy() == null) {
215             errors.add(new ValidationError("controllablePolicy", "ControllablePolicy flag must be set."));
216         } else if (type.getBaseTypeId() == BaseTypeId.CMIS_SECONDARY
217                 && Boolean.TRUE.equals(type.isControllablePolicy())) {
218             errors.add(new ValidationError("controllablePolicy",
219                     "ControllablePolicy flag must be FALSE for secondary types."));
220         }
221 
222         if (type.isControllableAcl() == null) {
223             errors.add(new ValidationError("controllableACL", "ControllableACL flag must be set."));
224         } else if (type.getBaseTypeId() == BaseTypeId.CMIS_SECONDARY && Boolean.TRUE.equals(type.isControllableAcl())) {
225             errors.add(
226                     new ValidationError("controllableACL", "ControllableACL flag must be FALSE for secondary types."));
227         }
228 
229         if (type.isFulltextIndexed() == null) {
230             errors.add(new ValidationError("fulltextIndexed", "FulltextIndexed flag must be set."));
231         }
232 
233         if (type.isIncludedInSupertypeQuery() == null) {
234             errors.add(new ValidationError("includedInSupertypeQuery", "IncludedInSupertypeQuery flag must be set."));
235         }
236 
237         if (type.getBaseTypeId() == null) {
238             errors.add(new ValidationError("baseId", "Base type id must be set."));
239         } else if (!type.getBaseTypeId().value().equals(type.getParentTypeId())) {
240             if (type.getParentTypeId() == null || type.getParentTypeId().length() == 0) {
241                 errors.add(new ValidationError("parentId", "Parent type id must be set."));
242             }
243         }
244 
245         if (type instanceof DocumentTypeDefinition) {
246             if (type.getBaseTypeId() != BaseTypeId.CMIS_DOCUMENT) {
247                 errors.add(new ValidationError("baseId", "Base type id does not match the type."));
248             }
249 
250             DocumentTypeDefinition docType = (DocumentTypeDefinition) type;
251 
252             if (docType.isVersionable() == null) {
253                 errors.add(new ValidationError("versionable", "Versionable flag must be set."));
254             }
255 
256             if (docType.getContentStreamAllowed() == null) {
257                 errors.add(new ValidationError("contentStreamAllowed", "ContentStreamAllowed flag must be set."));
258             }
259 
260         } else if (type instanceof FolderTypeDefinition) {
261             if (type.getBaseTypeId() != BaseTypeId.CMIS_FOLDER) {
262                 errors.add(new ValidationError("baseId", "Base type id does not match the type."));
263             }
264 
265         } else if (type instanceof RelationshipTypeDefinition) {
266             if (type.getBaseTypeId() != BaseTypeId.CMIS_RELATIONSHIP) {
267                 errors.add(new ValidationError("baseId", "Base type id does not match the type."));
268             }
269 
270         } else if (type instanceof PolicyTypeDefinition) {
271             if (type.getBaseTypeId() != BaseTypeId.CMIS_POLICY) {
272                 errors.add(new ValidationError("baseId", "Base type id does not match the type."));
273             }
274 
275         } else if (type instanceof ItemTypeDefinition) {
276             if (type.getBaseTypeId() != BaseTypeId.CMIS_ITEM) {
277                 errors.add(new ValidationError("baseId", "Base type id does not match the type."));
278             }
279 
280         } else if (type instanceof SecondaryTypeDefinition) {
281             if (type.getBaseTypeId() != BaseTypeId.CMIS_SECONDARY) {
282                 errors.add(new ValidationError("baseId", "Base type id does not match the type."));
283             }
284 
285         } else {
286             errors.add(new ValidationError("baseId", "Unknown base interface."));
287         }
288 
289         return errors;
290     }
291 
292     /**
293      * Validates a property definition.
294      * 
295      * @return the list of validation errors
296      */
297     public static List<ValidationError> validatePropertyDefinition(PropertyDefinition<?> propDef) {
298         if (propDef == null) {
299             throw new IllegalArgumentException("Type is null!");
300         }
301 
302         List<ValidationError> errors = new ArrayList<TypeUtils.ValidationError>();
303 
304         if (propDef.getId() == null || propDef.getId().length() == 0) {
305             errors.add(new ValidationError("id", "Type id must be set."));
306         }
307 
308         if (propDef.getQueryName() != null) {
309             if (propDef.getQueryName().length() == 0) {
310                 errors.add(new ValidationError("queryName", "Query name must not be empty."));
311             } else if (!checkQueryName(propDef.getQueryName())) {
312                 errors.add(new ValidationError("queryName", "Query name contains invalid characters."));
313             }
314         }
315 
316         if (propDef.getCardinality() == null) {
317             errors.add(new ValidationError("cardinality", "Cardinality must be set."));
318         }
319 
320         if (propDef.getUpdatability() == null) {
321             errors.add(new ValidationError("updatability", "Updatability must be set."));
322         }
323 
324         if (propDef.isInherited() == null) {
325             errors.add(new ValidationError("inherited", "Inherited flag must be set."));
326         }
327 
328         if (propDef.isRequired() == null) {
329             errors.add(new ValidationError("required", "Required flag must be set."));
330         }
331 
332         if (propDef.isQueryable() == null) {
333             errors.add(new ValidationError("queryable", "Queryable flag must be set."));
334         } else if (propDef.isQueryable().booleanValue()) {
335             if (propDef.getQueryName() == null || propDef.getQueryName().length() == 0) {
336                 errors.add(new ValidationError("queryable",
337                         "Queryable flag is set to TRUE, but the query name is not set."));
338             }
339         }
340 
341         if (propDef.isOrderable() == null) {
342             errors.add(new ValidationError("orderable", "Orderable flag must be set."));
343         } else if (propDef.isOrderable().booleanValue()) {
344             if (propDef.getCardinality() == Cardinality.MULTI) {
345                 errors.add(
346                         new ValidationError("orderable", "Orderable flag is set to TRUE for a multi-value property."));
347             }
348         }
349 
350         if (propDef.getPropertyType() == null) {
351             errors.add(new ValidationError("propertyType", "Property type id must be set."));
352         }
353 
354         if (propDef instanceof PropertyIdDefinition) {
355             if (propDef.getPropertyType() != PropertyType.ID) {
356                 errors.add(
357                         new ValidationError("propertyType", "Property type does not match the property definition."));
358             }
359         } else if (propDef instanceof PropertyStringDefinition) {
360             if (propDef.getPropertyType() != PropertyType.STRING) {
361                 errors.add(
362                         new ValidationError("propertyType", "Property type does not match the property definition."));
363             }
364         } else if (propDef instanceof PropertyIntegerDefinition) {
365             if (propDef.getPropertyType() != PropertyType.INTEGER) {
366                 errors.add(
367                         new ValidationError("propertyType", "Property type does not match the property definition."));
368             }
369         } else if (propDef instanceof PropertyDecimalDefinition) {
370             if (propDef.getPropertyType() != PropertyType.DECIMAL) {
371                 errors.add(
372                         new ValidationError("propertyType", "Property type does not match the property definition."));
373             }
374         } else if (propDef instanceof PropertyBooleanDefinition) {
375             if (propDef.getPropertyType() != PropertyType.BOOLEAN) {
376                 errors.add(
377                         new ValidationError("propertyType", "Property type does not match the property definition."));
378             }
379         } else if (propDef instanceof PropertyDateTimeDefinition) {
380             if (propDef.getPropertyType() != PropertyType.DATETIME) {
381                 errors.add(
382                         new ValidationError("propertyType", "Property type does not match the property definition."));
383             }
384         } else if (propDef instanceof PropertyHtmlDefinition) {
385             if (propDef.getPropertyType() != PropertyType.HTML) {
386                 errors.add(
387                         new ValidationError("propertyType", "Property type does not match the property definition."));
388             }
389         } else if (propDef instanceof PropertyUriDefinition) {
390             if (propDef.getPropertyType() != PropertyType.URI) {
391                 errors.add(
392                         new ValidationError("propertyType", "Property type does not match the property definition."));
393             }
394         }
395 
396         return errors;
397     }
398 
399     public static class ValidationError {
400         private final String attribute;
401         private final String error;
402 
403         public ValidationError(String attribute, String error) {
404             this.attribute = attribute;
405             this.error = error;
406         }
407 
408         public String getAttribute() {
409             return attribute;
410         }
411 
412         public String getError() {
413             return error;
414         }
415 
416         @Override
417         public String toString() {
418             return attribute + ": " + error;
419         }
420     }
421 }