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