001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *   Jens Huebel, Open Text
018 *   Florent Guillaume, Nuxeo
019 */
020package org.nuxeo.ecm.core.opencmis.impl.util;
021
022import java.math.BigInteger;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029
030import org.apache.chemistry.opencmis.commons.definitions.MutableTypeDefinition;
031import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
032import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
033import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
034import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
035import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
036import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
037import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
038import org.apache.chemistry.opencmis.commons.impl.WSConverter;
039import org.apache.chemistry.opencmis.commons.impl.dataobjects.AbstractPropertyDefinition;
040import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionContainerImpl;
041import org.apache.chemistry.opencmis.commons.impl.dataobjects.TypeDefinitionListImpl;
042import org.apache.chemistry.opencmis.server.support.TypeDefinitionFactory;
043import org.apache.chemistry.opencmis.server.support.TypeManager;
044
045/**
046 * Manages a type system for a repository.
047 * <p>
048 * Types can be added, the inheritance can be managed and type can be retrieved for a given type id.
049 * <p>
050 * Structures are not copied when returned.
051 */
052public class TypeManagerImpl implements TypeManager {
053
054    public static final int DEFAULT_MAX_TYPE_CHILDREN = 100;
055
056    protected Map<String, TypeDefinitionContainer> typesMap = new HashMap<String, TypeDefinitionContainer>();
057
058    protected Map<String, String> propQueryNameToId = new HashMap<String, String>();
059
060    @Override
061    public TypeDefinitionContainer getTypeById(String typeId) {
062        return typesMap.get(typeId);
063    }
064
065    public TypeDefinition getTypeDefinition(String typeId) {
066        TypeDefinitionContainer typec = getTypeById(typeId);
067        return typec == null ? null : typec.getTypeDefinition();
068    }
069
070    /**
071     * Checks if a type is known.
072     *
073     * @param typeId the type id
074     * @return {@code true} if known
075     * @since 5.9.3
076     */
077    public boolean hasType(String typeId) {
078        return typesMap.containsKey(typeId);
079    }
080
081    @Override
082    public TypeDefinition getTypeByQueryName(String typeQueryName) {
083        for (Entry<String, TypeDefinitionContainer> entry : typesMap.entrySet()) {
084            TypeDefinition type = entry.getValue().getTypeDefinition();
085            if (type.getQueryName().equals(typeQueryName)) {
086                return type;
087            }
088        }
089        return null;
090    }
091
092    public TypeDefinitionList getTypeChildren(String typeId, Boolean includePropertyDefinitions, BigInteger maxItems,
093            BigInteger skipCount) {
094        TypeDefinitionContainer typec;
095        if (typeId == null) {
096            // return root types
097            typec = null;
098        } else {
099            typec = typesMap.get(typeId);
100            if (typec == null) {
101                throw new CmisInvalidArgumentException("No such type: " + typeId);
102            }
103        }
104        List<TypeDefinitionContainer> types;
105        if (typec == null) {
106            // return root types
107            // TODO maintain pre-computed root types
108            types = new ArrayList<TypeDefinitionContainer>(4);
109            for (TypeDefinitionContainer tc : typesMap.values()) {
110                if (tc.getTypeDefinition().getParentTypeId() == null) {
111                    types.add(tc);
112                }
113            }
114        } else {
115            types = typec.getChildren();
116        }
117        List<TypeDefinition> list = new ArrayList<TypeDefinition>(types.size());
118        for (TypeDefinitionContainer tdc : types) {
119            TypeDefinition type = tdc.getTypeDefinition();
120            if (!Boolean.TRUE.equals(includePropertyDefinitions)) {
121                type = WSConverter.convert(WSConverter.convert(type)); // clone
122                // TODO avoid recomputing type-without-properties
123                type.getPropertyDefinitions().clear();
124            }
125            list.add(type);
126        }
127        list = ListUtils.batchList(list, maxItems, skipCount, DEFAULT_MAX_TYPE_CHILDREN);
128        return new TypeDefinitionListImpl(list);
129    }
130
131    public List<TypeDefinitionContainer> getTypeDescendants(String typeId, int depth, Boolean includePropertyDefinitions) {
132        List<TypeDefinitionContainer> types;
133        boolean includeProps = Boolean.TRUE.equals(includePropertyDefinitions);
134        if (typeId == null) {
135            // return all types, unlimited depth
136            types = new ArrayList<TypeDefinitionContainer>(4);
137            for (TypeDefinitionContainer tc : typesMap.values()) {
138                if (tc.getTypeDefinition().getParentTypeId() == null) {
139                    types.add(tc);
140                }
141            }
142            if (!includeProps) {
143                // remove props
144                types = cloneTypes(types, -1, false);
145            }
146        } else {
147            TypeDefinitionContainer typec = typesMap.get(typeId);
148            if (typec == null) {
149                throw new CmisInvalidArgumentException("No such type: " + typeId);
150            }
151            if (depth == 0 || depth < -1) {
152                throw new CmisInvalidArgumentException("Invalid depth: " + depth);
153            }
154            if (depth == -1) {
155                types = typec.getChildren();
156                if (!includeProps) {
157                    // remove props
158                    types = cloneTypes(types, -1, false);
159                }
160            } else {
161                types = typec.getChildren();
162                // truncate tree
163                types = cloneTypes(types, depth - 1, includeProps);
164            }
165        }
166        return types;
167    }
168
169    @Override
170    public Collection<TypeDefinitionContainer> getTypeDefinitionList() {
171        List<TypeDefinitionContainer> typeRoots = new ArrayList<TypeDefinitionContainer>();
172        // iterate types map and return a list collecting the root types:
173        for (TypeDefinitionContainer typeCont : typesMap.values()) {
174            if (typeCont.getTypeDefinition().getParentTypeId() == null) {
175                typeRoots.add(typeCont);
176            }
177        }
178        return typeRoots;
179    }
180
181    @Override
182    public List<TypeDefinitionContainer> getRootTypes() {
183        List<TypeDefinitionContainer> rootTypes = new ArrayList<TypeDefinitionContainer>();
184        for (TypeDefinitionContainer type : typesMap.values()) {
185            String id = type.getTypeDefinition().getId();
186            if (BaseTypeId.CMIS_DOCUMENT.value().equals(id) || BaseTypeId.CMIS_FOLDER.value().equals(id)
187                    || BaseTypeId.CMIS_RELATIONSHIP.value().equals(id) || BaseTypeId.CMIS_POLICY.value().equals(id)) {
188                rootTypes.add(type);
189            }
190        }
191        return rootTypes;
192    }
193
194    /**
195     * Add a type to the type system. Add type to children of parent types. If specified, add all properties from
196     * inherited types.,
197     *
198     * @param type new type to add
199     * @param addInheritedProperties
200     */
201    @Override
202    public void addTypeDefinition(TypeDefinition type, boolean addInheritedProperties) {
203        String id = type.getId();
204        if (typesMap.containsKey(id)) {
205            throw new RuntimeException("Type already exists: " + id);
206        }
207
208        TypeDefinitionContainer typeContainer = new TypeDefinitionContainerImpl(type);
209        // add type to type map
210        typesMap.put(id, typeContainer);
211
212        String parentId = type.getParentTypeId();
213        if (parentId != null) {
214            if (!typesMap.containsKey(parentId)) {
215                throw new RuntimeException("Cannot add type " + id + ", parent does not exist: " + parentId);
216            }
217            TypeDefinitionContainer parentTypeContainer = typesMap.get(parentId);
218            // add new type to children of parent types
219            parentTypeContainer.getChildren().add(typeContainer);
220            if (addInheritedProperties) {
221                // recursively add inherited properties
222                Map<String, PropertyDefinition<?>> propDefs = typeContainer.getTypeDefinition().getPropertyDefinitions();
223                addInheritedProperties(propDefs, parentTypeContainer.getTypeDefinition());
224            }
225        }
226
227        // prop query names
228        for (PropertyDefinition<?> pd : type.getPropertyDefinitions().values()) {
229            String propQueryName = pd.getQueryName();
230            String propId = pd.getId();
231            String old = propQueryNameToId.put(propQueryName, propId);
232            if (old != null && !old.equals(propId)) {
233                throw new RuntimeException("Cannot add type " + id + ", query name " + propQueryName
234                        + " already used for property id " + old);
235            }
236        }
237    }
238
239    public void addTypeDefinition(TypeDefinition type) {
240        addTypeDefinition(type, true);
241    }
242
243    @Override
244    public String getPropertyIdForQueryName(TypeDefinition typeDefinition, String propQueryName) {
245        for (PropertyDefinition<?> pd : typeDefinition.getPropertyDefinitions().values()) {
246            if (pd.getQueryName().equals(propQueryName)) {
247                return pd.getId();
248            }
249        }
250        return null;
251    }
252
253    public String getPropertyIdForQueryName(String propQueryName) {
254        return propQueryNameToId.get(propQueryName);
255    }
256
257    protected void addInheritedProperties(Map<String, PropertyDefinition<?>> propDefs, TypeDefinition type) {
258        if (type.getPropertyDefinitions() != null) {
259            addInheritedPropertyDefinitions(propDefs, type.getPropertyDefinitions());
260        }
261        TypeDefinitionContainer parentTypeContainer = typesMap.get(type.getParentTypeId());
262        if (parentTypeContainer != null) {
263            addInheritedProperties(propDefs, parentTypeContainer.getTypeDefinition());
264        }
265    }
266
267    protected void addInheritedPropertyDefinitions(Map<String, PropertyDefinition<?>> propDefs,
268            Map<String, PropertyDefinition<?>> superPropDefs) {
269        for (PropertyDefinition<?> superPropDef : superPropDefs.values()) {
270            PropertyDefinition<?> clone = WSConverter.convert(WSConverter.convert(superPropDef));
271            ((AbstractPropertyDefinition<?>) clone).setIsInherited(Boolean.TRUE);
272            propDefs.put(superPropDef.getId(), clone);
273        }
274    }
275
276    /**
277     * Returns a clone of a types tree.
278     * <p>
279     * Removes properties on the clone if requested, cuts the children of the clone if the depth is exceeded.
280     */
281    protected static List<TypeDefinitionContainer> cloneTypes(List<TypeDefinitionContainer> types, int depth,
282            boolean includePropertyDefinitions) {
283        List<TypeDefinitionContainer> res = new ArrayList<TypeDefinitionContainer>(types.size());
284        TypeDefinitionFactory tdFactory = TypeDefinitionFactory.newInstance();
285        for (TypeDefinitionContainer tc : types) {
286            MutableTypeDefinition td = tdFactory.copy(tc.getTypeDefinition(), includePropertyDefinitions);
287            TypeDefinitionContainerImpl clone = new TypeDefinitionContainerImpl(td);
288            if (depth != 0) {
289                clone.setChildren(cloneTypes(tc.getChildren(), depth - 1, includePropertyDefinitions));
290            }
291            res.add(clone);
292        }
293        return res;
294    }
295
296    @Override
297    public void updateTypeDefinition(TypeDefinition typeDefinition) {
298        throw new CmisNotSupportedException();
299    }
300
301    @Override
302    public void deleteTypeDefinition(String typeId) {
303        throw new CmisNotSupportedException();
304    }
305
306}