001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.opencms.cmis;
021
022import org.opencms.file.CmsObject;
023import org.opencms.file.CmsPropertyDefinition;
024import org.opencms.main.CmsException;
025import org.opencms.main.CmsLog;
026import org.opencms.relations.CmsRelationType;
027
028import java.math.BigInteger;
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034
035import org.apache.chemistry.opencmis.commons.PropertyIds;
036import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
037import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
038import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
039import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
040import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
041import org.apache.chemistry.opencmis.commons.enums.Cardinality;
042import org.apache.chemistry.opencmis.commons.enums.ContentStreamAllowed;
043import org.apache.chemistry.opencmis.commons.enums.PropertyType;
044import org.apache.chemistry.opencmis.commons.enums.Updatability;
045import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
046import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
047import org.apache.chemistry.opencmis.commons.impl.WSConverter;
048import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyDefinition;
049import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractTypeDefinition;
050import org.apache.chemistry.opencmis.commons.impl.dataobjects.DocumentTypeDefinitionImpl;
051import org.apache.chemistry.opencmis.commons.impl.dataobjects.FolderTypeDefinitionImpl;
052import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyBooleanDefinitionImpl;
053import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDateTimeDefinitionImpl;
054import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyDecimalDefinitionImpl;
055import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyHtmlDefinitionImpl;
056import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIdDefinitionImpl;
057import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyIntegerDefinitionImpl;
058import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyStringDefinitionImpl;
059import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertyUriDefinitionImpl;
060import org.apache.chemistry.opencmis.commons.impl.dataobjects.RelationshipTypeDefinitionImpl;
061import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl;
062import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl;
063import org.apache.commons.logging.Log;
064
065/**
066 * This class keeps track of all the types which should be available for a {@link I_CmsCmisRepository}.
067 */
068public class CmsCmisTypeManager {
069
070    /** CMIS type id for documents. */
071    public static final String DOCUMENT_TYPE_ID = BaseTypeId.CMIS_DOCUMENT.value();
072
073    /** CMIS type id for folders. */
074    public static final String FOLDER_TYPE_ID = BaseTypeId.CMIS_FOLDER.value();
075
076    /** Prefix for inherited properties. */
077    public static final String INHERITED_PREFIX = "opencms-inherited:";
078
079    /** CMIS type id for policies. */
080    public static final String POLICY_TYPE_ID = BaseTypeId.CMIS_POLICY.value();
081
082    /** The prefix used for normal OpenCms resource properties. */
083    public static final String PROPERTY_PREFIX = "opencms:";
084
085    /** Prefix for dynamic properties. */
086    public static final String PROPERTY_PREFIX_DYNAMIC = "opencms-dynamic:";
087
088    /** The prefix for special properties. */
089    public static final String PROPERTY_PREFIX_SPECIAL = "opencms-special:";
090
091    /** The name of the propery containing the resource type name. */
092    public static final String PROPERTY_RESOURCE_TYPE = PROPERTY_PREFIX_SPECIAL + "resource-type";
093
094    /** CMIS type id for relationships. */
095    public static final String RELATIONSHIP_TYPE_ID = BaseTypeId.CMIS_RELATIONSHIP.value();
096
097    /** Need to refresh property data after this time. */
098    public static final long UPDATE_INTERVAL = 1000 * 60 * 5;
099
100    /** The logger instance for this class. */
101    static final Log LOG = CmsLog.getLog(CmsCmisTypeManager.class);
102
103    /** The namespace used for properties. */
104    private static final String NAMESPACE = "http://opencms.org/opencms-cmis";
105
106    /** The admin CMS context. */
107    private CmsObject m_adminCms;
108
109    /** The list of OpenCms property definitions. */
110    private List<CmsPropertyDefinition> m_cmsPropertyDefinitions;
111
112    /** The last update time. */
113    private long m_lastUpdate;
114
115    /** List of dynamic property providers. */
116    private List<I_CmsPropertyProvider> m_propertyProviders = new ArrayList<I_CmsPropertyProvider>();
117
118    /** The internal list of type definitions. */
119    private List<TypeDefinitionContainer> m_typeList;
120
121    /** The internal map of type definitions. */
122    private Map<String, TypeDefinitionContainerImpl> m_types;
123
124    /**
125     * Creates a new type manager instance.<p>
126     *
127     * @param adminCms a CMS context with admin privileges
128     * @param propertyProviders list which will be filled with property providers
129     *
130     * @throws CmsException if something goes wrong
131     */
132    public CmsCmisTypeManager(CmsObject adminCms, List<I_CmsPropertyProvider> propertyProviders)
133    throws CmsException {
134
135        m_adminCms = adminCms;
136        m_propertyProviders = propertyProviders;
137        setup();
138    }
139
140    /**
141     * Adds the base CMIS property definitions common to folders and documents.<p>
142     *
143     * @param type the type definition to which the property definitions should be added
144     */
145    private static void addBasePropertyDefinitions(AbstractTypeDefinition type) {
146
147        type.addPropertyDefinition(
148            createPropDef(
149                PropertyIds.BASE_TYPE_ID,
150                "Base Type Id",
151                "Base Type Id",
152                PropertyType.ID,
153                Cardinality.SINGLE,
154                Updatability.READONLY,
155                false,
156                false));
157
158        type.addPropertyDefinition(
159            createPropDef(
160                PropertyIds.OBJECT_ID,
161                "Object Id",
162                "Object Id",
163                PropertyType.ID,
164                Cardinality.SINGLE,
165                Updatability.READONLY,
166                false,
167                false));
168
169        type.addPropertyDefinition(
170            createPropDef(
171                PropertyIds.OBJECT_TYPE_ID,
172                "Type Id",
173                "Type Id",
174                PropertyType.ID,
175                Cardinality.SINGLE,
176                Updatability.ONCREATE,
177                false,
178                true));
179
180        type.addPropertyDefinition(
181            createPropDef(
182                PropertyIds.NAME,
183                "Name",
184                "Name",
185                PropertyType.STRING,
186                Cardinality.SINGLE,
187                Updatability.READWRITE,
188                false,
189                true));
190
191        type.addPropertyDefinition(
192            queryableAndOrderable(
193                createPropDef(
194                    PropertyIds.CREATED_BY,
195                    "Created By",
196                    "Created By",
197                    PropertyType.STRING,
198                    Cardinality.SINGLE,
199                    Updatability.READONLY,
200                    false,
201                    false)));
202
203        type.addPropertyDefinition(
204            queryableAndOrderable(
205                createPropDef(
206                    PropertyIds.CREATION_DATE,
207                    "Creation Date",
208                    "Creation Date",
209                    PropertyType.DATETIME,
210                    Cardinality.SINGLE,
211                    Updatability.READONLY,
212                    false,
213                    false)));
214
215        type.addPropertyDefinition(
216            queryableAndOrderable(
217                createPropDef(
218                    PropertyIds.LAST_MODIFIED_BY,
219                    "Last Modified By",
220                    "Last Modified By",
221                    PropertyType.STRING,
222                    Cardinality.SINGLE,
223                    Updatability.READONLY,
224                    false,
225                    false)));
226
227        type.addPropertyDefinition(
228            queryableAndOrderable(
229                createPropDef(
230                    PropertyIds.LAST_MODIFICATION_DATE,
231                    "Last Modification Date",
232                    "Last Modification Date",
233                    PropertyType.DATETIME,
234                    Cardinality.SINGLE,
235                    Updatability.READONLY,
236                    false,
237                    false)));
238
239        type.addPropertyDefinition(
240            createPropDef(
241                PropertyIds.CHANGE_TOKEN,
242                "Change Token",
243                "Change Token",
244                PropertyType.STRING,
245                Cardinality.SINGLE,
246                Updatability.READONLY,
247                false,
248                false));
249
250    }
251
252    /**
253     * Adds CMIS property definitions for documents.<p>
254     *
255     * @param type the document type
256     */
257    private static void addDocumentPropertyDefinitions(DocumentTypeDefinitionImpl type) {
258
259        type.addPropertyDefinition(
260            createPropDef(
261                PropertyIds.IS_IMMUTABLE,
262                "Is Immutable",
263                "Is Immutable",
264                PropertyType.BOOLEAN,
265                Cardinality.SINGLE,
266                Updatability.READONLY,
267                false,
268                false));
269
270        type.addPropertyDefinition(
271            createPropDef(
272                PropertyIds.IS_LATEST_VERSION,
273                "Is Latest Version",
274                "Is Latest Version",
275                PropertyType.BOOLEAN,
276                Cardinality.SINGLE,
277                Updatability.READONLY,
278                false,
279                false));
280
281        type.addPropertyDefinition(
282            createPropDef(
283                PropertyIds.IS_MAJOR_VERSION,
284                "Is Major Version",
285                "Is Major Version",
286                PropertyType.BOOLEAN,
287                Cardinality.SINGLE,
288                Updatability.READONLY,
289                false,
290                false));
291
292        type.addPropertyDefinition(
293            createPropDef(
294                PropertyIds.IS_LATEST_MAJOR_VERSION,
295                "Is Latest Major Version",
296                "Is Latest Major Version",
297                PropertyType.BOOLEAN,
298                Cardinality.SINGLE,
299                Updatability.READONLY,
300                false,
301                false));
302
303        type.addPropertyDefinition(
304            createPropDef(
305                PropertyIds.VERSION_LABEL,
306                "Version Label",
307                "Version Label",
308                PropertyType.STRING,
309                Cardinality.SINGLE,
310                Updatability.READONLY,
311                false,
312                false));
313
314        type.addPropertyDefinition(
315            createPropDef(
316                PropertyIds.VERSION_SERIES_ID,
317                "Version Series Id",
318                "Version Series Id",
319                PropertyType.ID,
320                Cardinality.SINGLE,
321                Updatability.READONLY,
322                false,
323                false));
324
325        type.addPropertyDefinition(
326            createPropDef(
327                PropertyIds.IS_VERSION_SERIES_CHECKED_OUT,
328                "Is Verison Series Checked Out",
329                "Is Verison Series Checked Out",
330                PropertyType.BOOLEAN,
331                Cardinality.SINGLE,
332                Updatability.READONLY,
333                false,
334                false));
335
336        type.addPropertyDefinition(
337            createPropDef(
338                PropertyIds.VERSION_SERIES_CHECKED_OUT_ID,
339                "Version Series Checked Out Id",
340                "Version Series Checked Out Id",
341                PropertyType.ID,
342                Cardinality.SINGLE,
343                Updatability.READONLY,
344                false,
345                false));
346
347        type.addPropertyDefinition(
348            createPropDef(
349                PropertyIds.VERSION_SERIES_CHECKED_OUT_BY,
350                "Version Series Checked Out By",
351                "Version Series Checked Out By",
352                PropertyType.STRING,
353                Cardinality.SINGLE,
354                Updatability.READONLY,
355                false,
356                false));
357
358        type.addPropertyDefinition(
359            createPropDef(
360                PropertyIds.CHECKIN_COMMENT,
361                "Checkin Comment",
362                "Checkin Comment",
363                PropertyType.STRING,
364                Cardinality.SINGLE,
365                Updatability.READONLY,
366                false,
367                false));
368
369        type.addPropertyDefinition(
370            createPropDef(
371                PropertyIds.CONTENT_STREAM_LENGTH,
372                "Content Stream Length",
373                "Content Stream Length",
374                PropertyType.INTEGER,
375                Cardinality.SINGLE,
376                Updatability.READONLY,
377                false,
378                false));
379
380        type.addPropertyDefinition(
381            createPropDef(
382                PropertyIds.CONTENT_STREAM_MIME_TYPE,
383                "MIME Type",
384                "MIME Type",
385                PropertyType.STRING,
386                Cardinality.SINGLE,
387                Updatability.READONLY,
388                false,
389                false));
390
391        type.addPropertyDefinition(
392            createPropDef(
393                PropertyIds.CONTENT_STREAM_FILE_NAME,
394                "Filename",
395                "Filename",
396                PropertyType.STRING,
397                Cardinality.SINGLE,
398                Updatability.READONLY,
399                false,
400                false));
401
402        type.addPropertyDefinition(
403            createPropDef(
404                PropertyIds.CONTENT_STREAM_ID,
405                "Content Stream Id",
406                "Content Stream Id",
407                PropertyType.ID,
408                Cardinality.SINGLE,
409                Updatability.READONLY,
410                false,
411                false));
412    }
413
414    /**
415     * Adds folder specific CMIS property definitions.<p>
416     *
417     * @param type the folder type
418     */
419    private static void addFolderPropertyDefinitions(FolderTypeDefinitionImpl type) {
420
421        type.addPropertyDefinition(
422            createPropDef(
423                PropertyIds.PARENT_ID,
424                "Parent Id",
425                "Parent Id",
426                PropertyType.ID,
427                Cardinality.SINGLE,
428                Updatability.READONLY,
429                false,
430                false));
431
432        type.addPropertyDefinition(
433            createPropDef(
434                PropertyIds.ALLOWED_CHILD_OBJECT_TYPE_IDS,
435                "Allowed Child Object Type Ids",
436                "Allowed Child Object Type Ids",
437                PropertyType.ID,
438                Cardinality.MULTI,
439                Updatability.READONLY,
440                false,
441                false));
442
443        type.addPropertyDefinition(
444            createPropDef(
445                PropertyIds.PATH,
446                "Path",
447                "Path",
448                PropertyType.STRING,
449                Cardinality.SINGLE,
450                Updatability.READONLY,
451                false,
452                false));
453    }
454
455    /**
456     * Helper method to add the property definitions specific to relationship types.<p>
457     *
458     * @param type the type definition to which the property definitions should be added
459     */
460    private static void addRelationPropertyDefinitions(RelationshipTypeDefinitionImpl type) {
461
462        type.addPropertyDefinition(
463            createPropDef(
464                PropertyIds.SOURCE_ID,
465                "Source",
466                "Source",
467                PropertyType.ID,
468                Cardinality.SINGLE,
469                Updatability.ONCREATE,
470                false,
471                true));
472
473        type.addPropertyDefinition(
474            createPropDef(
475                PropertyIds.TARGET_ID,
476                "Target",
477                "Target",
478                PropertyType.ID,
479                Cardinality.SINGLE,
480                Updatability.ONCREATE,
481                false,
482                true));
483
484    }
485
486    /**
487     * Copies a type definition.<p>
488     *
489     * @param type the type definition to copy
490     *
491     * @return the copied type definition
492     */
493    private static TypeDefinition copyTypeDefintion(TypeDefinition type) {
494
495        return WSConverter.convert(WSConverter.convert(type));
496    }
497
498    /**
499     * Creates a property definition.<p>
500     *
501     * @param id the property definition id
502     * @param displayName the property display name
503     * @param description the property description
504     * @param datatype the property type
505     * @param cardinality the property cardinality
506     * @param updateability the property updatability
507     * @param inherited the property inheritance status
508     * @param required true true if the property is required
509     *
510     * @return the property definition
511     */
512    private static AbstractPropertyDefinition<?> createPropDef(
513        String id,
514        String displayName,
515        String description,
516        PropertyType datatype,
517        Cardinality cardinality,
518        Updatability updateability,
519        boolean inherited,
520        boolean required) {
521
522        AbstractPropertyDefinition<?> result = null;
523
524        switch (datatype) {
525            case BOOLEAN:
526                result = new PropertyBooleanDefinitionImpl();
527                break;
528            case DATETIME:
529                result = new PropertyDateTimeDefinitionImpl();
530                break;
531            case DECIMAL:
532                result = new PropertyDecimalDefinitionImpl();
533                break;
534            case HTML:
535                result = new PropertyHtmlDefinitionImpl();
536                break;
537            case ID:
538                result = new PropertyIdDefinitionImpl();
539                break;
540            case INTEGER:
541                result = new PropertyIntegerDefinitionImpl();
542                break;
543            case STRING:
544                result = new PropertyStringDefinitionImpl();
545                break;
546            case URI:
547                result = new PropertyUriDefinitionImpl();
548                break;
549            default:
550                throw new RuntimeException("Unknown datatype! Spec change?");
551        }
552
553        result.setId(id);
554        result.setLocalName(id);
555        result.setDisplayName(displayName);
556        result.setDescription(description);
557        result.setPropertyType(datatype);
558        result.setCardinality(cardinality);
559        result.setUpdatability(updateability);
560        result.setIsInherited(Boolean.valueOf(inherited));
561        result.setIsRequired(Boolean.valueOf(required));
562        result.setIsQueryable(Boolean.FALSE);
563        result.setIsOrderable(Boolean.FALSE);
564        result.setQueryName(id);
565        return result;
566    }
567
568    /**
569     * Helper method to make a property definition queryable and orderable.<p>
570     *
571     * @param propDef the property definition
572     *
573     * @return the modified property definition
574     */
575    private static AbstractPropertyDefinition<?> queryableAndOrderable(AbstractPropertyDefinition<?> propDef) {
576
577        propDef.setIsQueryable(Boolean.TRUE);
578        propDef.setIsOrderable(Boolean.TRUE);
579        return propDef;
580    }
581
582    /**
583     * Gets a list of names of OpenCms property definitions.<p>
584     *
585     * @return the list of OpenCms property names
586     */
587    public List<String> getCmsPropertyNames() {
588
589        refresh();
590        List<String> result = new ArrayList<String>();
591        for (CmsPropertyDefinition propDef : m_cmsPropertyDefinitions) {
592            result.add(propDef.getName());
593        }
594        return result;
595    }
596
597    /**
598     * Gets the property provider for a given key.<p>
599     *
600     * @param key the property nme
601     *
602     * @return the property provider for the given name, or null if there isn't any
603     */
604    public I_CmsPropertyProvider getPropertyProvider(String key) {
605
606        if (key.startsWith(PROPERTY_PREFIX_DYNAMIC)) {
607            key = key.substring(PROPERTY_PREFIX_DYNAMIC.length());
608        }
609        for (I_CmsPropertyProvider provider : m_propertyProviders) {
610            if (provider.getName().equals(key)) {
611                return provider;
612            }
613        }
614        return null;
615    }
616
617    /**
618     * Gets the list of all property providers.<p>
619     *
620     * @return the list of property providers
621     */
622    public List<I_CmsPropertyProvider> getPropertyProviders() {
623
624        return Collections.unmodifiableList(m_propertyProviders);
625    }
626
627    /**
628     * Gets a type definition by id.<p>
629     *
630     * @param typeId the type id
631     * @return the type definition
632     */
633    public TypeDefinition getType(String typeId) {
634
635        refresh();
636        TypeDefinitionContainer tc = m_types.get(typeId);
637        if (tc == null) {
638            return null;
639        }
640
641        return tc.getTypeDefinition();
642    }
643
644    /**
645     * Collects the children of a type.<p>
646     *
647     * @param typeId the id of the type
648     * @param includePropertyDefinitions true if the property definitions should be included
649     * @param maxItems the maximum number of items to return
650     * @param skipCount the number of items to skip
651     *
652     * @return the children of the type
653     */
654    public TypeDefinitionList getTypeChildren(
655
656        String typeId,
657        boolean includePropertyDefinitions,
658        BigInteger maxItems,
659        BigInteger skipCount) {
660
661        refresh();
662        TypeDefinitionListImpl result = new TypeDefinitionListImpl(new ArrayList<TypeDefinition>());
663
664        int skip = (skipCount == null ? 0 : skipCount.intValue());
665        if (skip < 0) {
666            skip = 0;
667        }
668
669        int max = (maxItems == null ? Integer.MAX_VALUE : maxItems.intValue());
670        if (max < 1) {
671            return result;
672        }
673
674        if (typeId == null) {
675            if (skip < 1) {
676                result.getList().add(copyTypeDefintion(m_types.get(FOLDER_TYPE_ID).getTypeDefinition()));
677                max--;
678            }
679            if ((skip < 2) && (max > 0)) {
680                result.getList().add(copyTypeDefintion(m_types.get(DOCUMENT_TYPE_ID).getTypeDefinition()));
681                max--;
682            }
683
684            result.setHasMoreItems(Boolean.valueOf((result.getList().size() + skip) < 2));
685            result.setNumItems(BigInteger.valueOf(2));
686        } else {
687            TypeDefinitionContainer tc = m_types.get(typeId);
688            if ((tc == null) || (tc.getChildren() == null)) {
689                return result;
690            }
691
692            for (TypeDefinitionContainer child : tc.getChildren()) {
693                if (skip > 0) {
694                    skip--;
695                    continue;
696                }
697
698                result.getList().add(copyTypeDefintion(child.getTypeDefinition()));
699
700                max--;
701                if (max == 0) {
702                    break;
703                }
704            }
705
706            result.setHasMoreItems(Boolean.valueOf((result.getList().size() + skip) < tc.getChildren().size()));
707            result.setNumItems(BigInteger.valueOf(tc.getChildren().size()));
708        }
709
710        if (!includePropertyDefinitions) {
711            for (TypeDefinition type : result.getList()) {
712                type.getPropertyDefinitions().clear();
713            }
714        }
715
716        return result;
717    }
718
719    /**
720     * Gets the type definition for a given id in the given call context.<p>
721     *
722     * @param typeId the type id
723     *
724     * @return the matching type definition
725     */
726    public TypeDefinition getTypeDefinition(String typeId) {
727
728        refresh();
729        TypeDefinitionContainer tc = m_types.get(typeId);
730        if (tc == null) {
731            throw new CmisObjectNotFoundException("Type '" + typeId + "' is unknown!");
732        }
733
734        return copyTypeDefintion(tc.getTypeDefinition());
735    }
736
737    /**
738     * Gets the descendants of a type.<p>
739     *
740     * @param typeId the parent type id
741     * @param depth the depth up to which the descendant types should be collected
742     * @param includePropertyDefinitions true if the property definitions should be included
743     *
744     * @return the descendants of the type
745     */
746    public List<TypeDefinitionContainer> getTypeDescendants(
747
748        String typeId,
749        BigInteger depth,
750        boolean includePropertyDefinitions) {
751
752        refresh();
753        List<TypeDefinitionContainer> result = new ArrayList<TypeDefinitionContainer>();
754
755        // check depth
756        int d = (depth == null ? -1 : depth.intValue());
757        if (d == 0) {
758            throw new CmisInvalidArgumentException("Depth must not be 0!");
759        }
760
761        if (typeId == null) {
762            result.add(getTypeDescendants(d, m_types.get(FOLDER_TYPE_ID), includePropertyDefinitions));
763            result.add(getTypeDescendants(d, m_types.get(DOCUMENT_TYPE_ID), includePropertyDefinitions));
764            result.add(getTypeDescendants(d, m_types.get(RELATIONSHIP_TYPE_ID), includePropertyDefinitions));
765        } else {
766            TypeDefinitionContainer tc = m_types.get(typeId);
767            if (tc != null) {
768                result.add(getTypeDescendants(d, tc, includePropertyDefinitions));
769            }
770        }
771
772        return result;
773    }
774
775    /**
776     * Creates the CMIS property definition for an OpenCms resource property definition.<p>
777     *
778     * @param cmsDef the OpenCms property definition
779     *
780     * @return the CMIS property definition
781     */
782    PropertyDefinition<?> createOpenCmsPropertyDefinition(CmsPropertyDefinition cmsDef) {
783
784        return createPropDef(
785            PROPERTY_PREFIX + cmsDef.getName(),
786            cmsDef.getName(),
787            cmsDef.getName(),
788            PropertyType.STRING,
789            Cardinality.SINGLE,
790            Updatability.READWRITE,
791            false,
792            false);
793    }
794
795    /**
796     * Creates the base types.
797     *
798     * @throws CmsException if something goes wrong
799     */
800    void setup() throws CmsException {
801
802        m_types = new HashMap<String, TypeDefinitionContainerImpl>();
803        m_typeList = new ArrayList<TypeDefinitionContainer>();
804        m_cmsPropertyDefinitions = m_adminCms.readAllPropertyDefinitions();
805
806        // folder type
807        FolderTypeDefinitionImpl folderType = new FolderTypeDefinitionImpl();
808        folderType.setBaseTypeId(BaseTypeId.CMIS_FOLDER);
809        folderType.setIsControllableAcl(Boolean.TRUE);
810        folderType.setIsControllablePolicy(Boolean.FALSE);
811        folderType.setIsCreatable(Boolean.TRUE);
812        folderType.setDescription("Folder");
813        folderType.setDisplayName("Folder");
814        folderType.setIsFileable(Boolean.TRUE);
815        folderType.setIsFulltextIndexed(Boolean.FALSE);
816        folderType.setIsIncludedInSupertypeQuery(Boolean.TRUE);
817        folderType.setLocalName("Folder");
818        folderType.setLocalNamespace(NAMESPACE);
819        folderType.setIsQueryable(Boolean.TRUE);
820        folderType.setQueryName("cmis:folder");
821        folderType.setId(FOLDER_TYPE_ID);
822
823        addBasePropertyDefinitions(folderType);
824        addFolderPropertyDefinitions(folderType);
825        addCmsPropertyDefinitions(folderType);
826        addProviderPropertyDefinitions(folderType);
827
828        addTypeInternal(folderType);
829
830        // document type
831        DocumentTypeDefinitionImpl documentType = new DocumentTypeDefinitionImpl();
832        documentType.setBaseTypeId(BaseTypeId.CMIS_DOCUMENT);
833        documentType.setIsControllableAcl(Boolean.TRUE);
834        documentType.setIsControllablePolicy(Boolean.FALSE);
835        documentType.setIsCreatable(Boolean.TRUE);
836        documentType.setDescription("Document");
837        documentType.setDisplayName("Document");
838        documentType.setIsFileable(Boolean.TRUE);
839        documentType.setIsFulltextIndexed(Boolean.FALSE);
840        documentType.setIsIncludedInSupertypeQuery(Boolean.TRUE);
841        documentType.setLocalName("Document");
842        documentType.setLocalNamespace(NAMESPACE);
843        documentType.setIsQueryable(Boolean.TRUE);
844        documentType.setQueryName("cmis:document");
845        documentType.setId(DOCUMENT_TYPE_ID);
846
847        documentType.setIsVersionable(Boolean.FALSE);
848        documentType.setContentStreamAllowed(ContentStreamAllowed.REQUIRED);
849
850        addBasePropertyDefinitions(documentType);
851        addDocumentPropertyDefinitions(documentType);
852        addCmsPropertyDefinitions(documentType);
853        addProviderPropertyDefinitions(documentType);
854
855        addTypeInternal(documentType);
856
857        // relationship types
858        RelationshipTypeDefinitionImpl relationshipType = new RelationshipTypeDefinitionImpl();
859        relationshipType.setBaseTypeId(BaseTypeId.CMIS_RELATIONSHIP);
860        relationshipType.setIsControllableAcl(Boolean.FALSE);
861        relationshipType.setIsControllablePolicy(Boolean.FALSE);
862        relationshipType.setIsCreatable(Boolean.FALSE);
863        relationshipType.setDescription("Relationship");
864        relationshipType.setDisplayName("Relationship");
865        relationshipType.setIsFileable(Boolean.FALSE);
866        relationshipType.setIsIncludedInSupertypeQuery(Boolean.TRUE);
867        relationshipType.setLocalName("Relationship");
868        relationshipType.setLocalNamespace(NAMESPACE);
869        relationshipType.setIsQueryable(Boolean.FALSE);
870        relationshipType.setQueryName("cmis:relationship");
871        relationshipType.setId(RELATIONSHIP_TYPE_ID);
872        List<String> typeList = new ArrayList<String>();
873        typeList.add("cmis:document");
874        typeList.add("cmis:folder");
875        relationshipType.setAllowedSourceTypes(typeList);
876        relationshipType.setAllowedTargetTypes(typeList);
877        addBasePropertyDefinitions(relationshipType);
878        addRelationPropertyDefinitions(relationshipType);
879        addTypeInternal(relationshipType);
880
881        for (CmsRelationType relType : CmsRelationType.getAll()) {
882            createRelationshipType(relType);
883        }
884        m_lastUpdate = System.currentTimeMillis();
885    }
886
887    /**
888     * Adds the CMIS property definitions corresponding to the OpenCms property definitions to a CMIS type definition.<p>
889     *
890     * @param type the type to which the property definitions should be added
891     */
892    private void addCmsPropertyDefinitions(AbstractTypeDefinition type) {
893
894        for (CmsPropertyDefinition propDef : m_cmsPropertyDefinitions) {
895            type.addPropertyDefinition(createOpenCmsPropertyDefinition(propDef));
896            type.addPropertyDefinition(
897                createPropDef(
898                    INHERITED_PREFIX + propDef.getName(),
899                    propDef.getName(),
900                    propDef.getName(),
901                    PropertyType.STRING,
902                    Cardinality.SINGLE,
903                    Updatability.READONLY,
904                    false,
905                    false));
906
907        }
908        type.addPropertyDefinition(
909            createPropDef(
910                PROPERTY_RESOURCE_TYPE,
911                "Resource type",
912                "Resource type",
913                PropertyType.STRING,
914                Cardinality.SINGLE,
915                Updatability.ONCREATE,
916                false,
917                true));
918    }
919
920    /**
921     * Helper method for adding property definitions for the dynamic properties.<p>
922     *
923     * @param type the type definition to which the properties should be added
924     */
925    private void addProviderPropertyDefinitions(AbstractTypeDefinition type) {
926
927        for (I_CmsPropertyProvider provider : m_propertyProviders) {
928            type.addPropertyDefinition(
929                createPropDef(
930                    PROPERTY_PREFIX_DYNAMIC + provider.getName(),
931                    provider.getName(),
932                    provider.getName(),
933                    PropertyType.STRING,
934                    Cardinality.SINGLE,
935                    provider.isWritable() ? Updatability.READWRITE : Updatability.READONLY,
936                    false,
937                    false));
938        }
939    }
940
941    /**
942     * Adds a type to collection with inheriting base type properties.
943     *
944     * @param type the type definition to add
945     *
946     * @return true if the type definition was added
947     */
948    private boolean addType(TypeDefinition type) {
949
950        if (type == null) {
951            return false;
952        }
953
954        if (type.getBaseTypeId() == null) {
955            return false;
956        }
957
958        // find base type
959        TypeDefinition baseType = null;
960        if (type.getBaseTypeId() == BaseTypeId.CMIS_DOCUMENT) {
961            baseType = copyTypeDefintion(m_types.get(DOCUMENT_TYPE_ID).getTypeDefinition());
962        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_FOLDER) {
963            baseType = copyTypeDefintion(m_types.get(FOLDER_TYPE_ID).getTypeDefinition());
964        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_RELATIONSHIP) {
965            baseType = copyTypeDefintion(m_types.get(RELATIONSHIP_TYPE_ID).getTypeDefinition());
966        } else if (type.getBaseTypeId() == BaseTypeId.CMIS_POLICY) {
967            baseType = copyTypeDefintion(m_types.get(POLICY_TYPE_ID).getTypeDefinition());
968        } else {
969            return false;
970        }
971
972        AbstractTypeDefinition newType = (AbstractTypeDefinition)copyTypeDefintion(type);
973
974        // copy property definition
975        for (PropertyDefinition<?> propDef : baseType.getPropertyDefinitions().values()) {
976            ((AbstractPropertyDefinition<?>)propDef).setIsInherited(Boolean.TRUE);
977            newType.addPropertyDefinition(propDef);
978        }
979
980        // add it
981        addTypeInternal(newType);
982        return true;
983    }
984
985    /**
986     * Internal method which adds a new type, without adding any property definitions automatically.<p>
987     *
988     * @param type the type to add
989     */
990    private void addTypeInternal(AbstractTypeDefinition type) {
991
992        if (type == null) {
993            return;
994        }
995
996        if (m_types.containsKey(type.getId())) {
997            // can't overwrite a type
998            return;
999        }
1000
1001        TypeDefinitionContainerImpl tc = new TypeDefinitionContainerImpl();
1002        tc.setTypeDefinition(type);
1003
1004        // add to parent
1005        if (type.getParentTypeId() != null) {
1006            TypeDefinitionContainerImpl tdc = m_types.get(type.getParentTypeId());
1007            if (tdc != null) {
1008                if (tdc.getChildren() == null) {
1009                    tdc.setChildren(new ArrayList<TypeDefinitionContainer>());
1010                }
1011                tdc.getChildren().add(tc);
1012            }
1013        }
1014
1015        m_types.put(type.getId(), tc);
1016        m_typeList.add(tc);
1017    }
1018
1019    /**
1020     * Creates a CMIS relationship subtype for a given OpenCms relation type.<p>
1021     *
1022     * @param relType the OpenCms relation type
1023     */
1024    private void createRelationshipType(CmsRelationType relType) {
1025
1026        // relationship types
1027        RelationshipTypeDefinitionImpl relationshipType = new RelationshipTypeDefinitionImpl();
1028        relationshipType.setBaseTypeId(BaseTypeId.CMIS_RELATIONSHIP);
1029        relationshipType.setParentTypeId(RELATIONSHIP_TYPE_ID);
1030        relationshipType.setIsControllableAcl(Boolean.FALSE);
1031        relationshipType.setIsControllablePolicy(Boolean.FALSE);
1032        relationshipType.setIsCreatable(Boolean.valueOf(!relType.isDefinedInContent()));
1033        relationshipType.setDescription(relType.getName());
1034        relationshipType.setDisplayName(relType.getName());
1035        relationshipType.setIsFileable(Boolean.FALSE);
1036        relationshipType.setIsIncludedInSupertypeQuery(Boolean.TRUE);
1037        relationshipType.setLocalName(relType.getName());
1038        relationshipType.setLocalNamespace(NAMESPACE);
1039        relationshipType.setIsQueryable(Boolean.FALSE);
1040        String id = "opencms:" + relType.getName().toUpperCase();
1041        relationshipType.setQueryName(id);
1042        relationshipType.setId(id);
1043        List<String> typeList = new ArrayList<String>();
1044        typeList.add("cmis:document");
1045        typeList.add("cmis:folder");
1046        relationshipType.setAllowedSourceTypes(typeList);
1047        relationshipType.setAllowedTargetTypes(typeList);
1048        addType(relationshipType);
1049    }
1050
1051    /**
1052     * Collects the descendants of a type.<p>
1053     *
1054     * @param depth the depth up to which the descendants should be collected
1055     * @param tc the parent type
1056     * @param includePropertyDefinitions true if the property definitions should be included
1057     *
1058     * @return the descendants of the type
1059     */
1060    private TypeDefinitionContainer getTypeDescendants(
1061        int depth,
1062        TypeDefinitionContainer tc,
1063        boolean includePropertyDefinitions) {
1064
1065        TypeDefinitionContainerImpl result = new TypeDefinitionContainerImpl();
1066
1067        TypeDefinition type = copyTypeDefintion(tc.getTypeDefinition());
1068        if (!includePropertyDefinitions) {
1069            type.getPropertyDefinitions().clear();
1070        }
1071
1072        result.setTypeDefinition(type);
1073
1074        if (depth != 0) {
1075            if (tc.getChildren() != null) {
1076                result.setChildren(new ArrayList<TypeDefinitionContainer>());
1077                for (TypeDefinitionContainer tdc : tc.getChildren()) {
1078                    result.getChildren().add(
1079                        getTypeDescendants(depth < 0 ? -1 : depth - 1, tdc, includePropertyDefinitions));
1080                }
1081            }
1082        }
1083
1084        return result;
1085    }
1086
1087    /**
1088     * Refreshes the internal data if the last update was longer ago than the udpate interval.<p>
1089     */
1090    private synchronized void refresh() {
1091
1092        try {
1093            long now = System.currentTimeMillis();
1094            if ((now - m_lastUpdate) > UPDATE_INTERVAL) {
1095                setup();
1096            }
1097        } catch (CmsException e) {
1098            LOG.error(e.getLocalizedMessage(), e);
1099        }
1100    }
1101
1102}