001/*
002 * (C) Copyright 2006-2007 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 *     Nuxeo - initial API and implementation
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.platform.types;
023
024import static org.nuxeo.ecm.platform.types.localconfiguration.UITypesConfigurationConstants.UI_TYPES_CONFIGURATION_FACET;
025
026import java.util.ArrayList;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034
035import org.nuxeo.ecm.core.api.DocumentModel;
036import org.nuxeo.ecm.core.api.localconfiguration.LocalConfigurationService;
037import org.nuxeo.ecm.core.schema.DocumentType;
038import org.nuxeo.ecm.core.schema.SchemaManager;
039import org.nuxeo.ecm.core.schema.SchemaManagerImpl;
040import org.nuxeo.ecm.platform.types.localconfiguration.UITypesConfiguration;
041import org.nuxeo.runtime.api.Framework;
042import org.nuxeo.runtime.model.ComponentContext;
043import org.nuxeo.runtime.model.ComponentInstance;
044import org.nuxeo.runtime.model.ComponentName;
045import org.nuxeo.runtime.model.DefaultComponent;
046
047public class TypeService extends DefaultComponent implements TypeManager {
048
049    public static final ComponentName ID = new ComponentName("org.nuxeo.ecm.platform.types.TypeService");
050
051    public static final String DEFAULT_CATEGORY = "misc";
052
053    public static final String HIDDEN_IN_CREATION = "create";
054
055    private TypeRegistry typeRegistry;
056
057    private Runnable recomputeCallback;
058
059    @Override
060    public void activate(ComponentContext context) {
061        typeRegistry = new TypeRegistry();
062        recomputeCallback = typeRegistry::recomputeTypes;
063        SchemaManagerImpl schemaManager = (SchemaManagerImpl) Framework.getService(SchemaManager.class);
064        schemaManager.registerRecomputeCallback(recomputeCallback);
065    }
066
067    @Override
068    public void deactivate(ComponentContext context) {
069        SchemaManagerImpl schemaManager = (SchemaManagerImpl)Framework.getService(SchemaManager.class);
070        schemaManager.unregisterRecomputeCallback(recomputeCallback);
071        typeRegistry = null;
072    }
073
074    @Override
075    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
076        if (extensionPoint.equals("types")) {
077            typeRegistry.addContribution((Type) contribution);
078        }
079    }
080
081    @Override
082    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
083        if (extensionPoint.equals("types")) {
084            typeRegistry.removeContribution((Type) contribution);
085        }
086    }
087
088    public TypeRegistry getTypeRegistry() {
089        return typeRegistry;
090    }
091
092    // Service implementation for TypeManager interface
093
094    @Override
095    public String[] getSuperTypes(String typeName) {
096        SchemaManager schemaMgr = Framework.getService(SchemaManager.class);
097        DocumentType type = schemaMgr.getDocumentType(typeName);
098        if (type == null) {
099            return null;
100        }
101        type = (DocumentType) type.getSuperType();
102        List<String> superTypes = new ArrayList<>();
103        while (type != null) {
104            superTypes.add(type.getName());
105            type = (DocumentType) type.getSuperType();
106        }
107        return superTypes.toArray(new String[superTypes.size()]);
108    }
109
110    @Override
111    public Type getType(String typeName) {
112        return typeRegistry.getType(typeName);
113    }
114
115    @Override
116    public boolean hasType(String typeName) {
117        return typeRegistry.hasType(typeName);
118    }
119
120    @Override
121    public Collection<Type> getTypes() {
122        Collection<Type> types = new ArrayList<>();
123        types.addAll(typeRegistry.getTypes());
124        return types;
125    }
126
127    @Override
128    public Collection<Type> getAllowedSubTypes(String typeName) {
129        return getAllowedSubTypes(typeName, null);
130    }
131
132    @Override
133    public Collection<Type> getAllowedSubTypes(String typeName, DocumentModel currentDoc) {
134        Collection<Type> allowed = new ArrayList<>();
135        Type type = getType(typeName);
136        if (type != null) {
137            Map<String, SubType> allowedSubTypes = type.getAllowedSubTypes();
138            if (currentDoc != null) {
139                allowedSubTypes = filterSubTypesFromConfiguration(allowedSubTypes, currentDoc);
140            }
141            for (String subTypeName : allowedSubTypes.keySet()) {
142                Type subType = getType(subTypeName);
143                if (subType != null) {
144                    allowed.add(subType);
145                }
146            }
147        }
148        return allowed;
149    }
150
151    @Override
152    public Collection<Type> findAllAllowedSubTypesFrom(String typeName) {
153        return findAllAllowedSubTypesFrom(typeName, null, null);
154    }
155
156    @Override
157    public Collection<Type> findAllAllowedSubTypesFrom(String typeName, DocumentModel currentDoc) {
158        return findAllAllowedSubTypesFrom(typeName, currentDoc, null);
159    }
160
161    protected Collection<Type> findAllAllowedSubTypesFrom(String typeName, DocumentModel currentDoc,
162            List<String> alreadyProcessedTypes) {
163        if (alreadyProcessedTypes == null) {
164            alreadyProcessedTypes = new ArrayList<>();
165        }
166        Set<Type> allAllowedSubTypes = new HashSet<>();
167
168        Collection<Type> allowedSubTypes = getAllowedSubTypes(typeName, currentDoc);
169        allAllowedSubTypes.addAll(allowedSubTypes);
170        alreadyProcessedTypes.add(typeName);
171        for (Type subType : allowedSubTypes) {
172            if (!alreadyProcessedTypes.contains(subType.getId())) {
173                allAllowedSubTypes.addAll(findAllAllowedSubTypesFrom(subType.getId(), currentDoc, alreadyProcessedTypes));
174            }
175        }
176
177        return allAllowedSubTypes;
178    }
179
180    protected UITypesConfiguration getConfiguration(DocumentModel currentDoc) {
181        LocalConfigurationService localConfigurationService = Framework.getService(LocalConfigurationService.class);
182        return localConfigurationService.getConfiguration(UITypesConfiguration.class, UI_TYPES_CONFIGURATION_FACET,
183                currentDoc);
184    }
185
186    @Override
187    public Map<String, List<Type>> getTypeMapForDocumentType(String typeName, DocumentModel currentDoc) {
188        Type type = getType(typeName);
189        if (type != null) {
190            Map<String, List<Type>> docTypesMap = new HashMap<>();
191            Map<String, SubType> allowedSubTypes = type.getAllowedSubTypes();
192            allowedSubTypes = filterSubTypesFromConfiguration(allowedSubTypes, currentDoc);
193            for (Map.Entry<String, SubType> entry : allowedSubTypes.entrySet()) {
194                if (canCreate(entry.getValue())) {
195                    Type subType = getType(entry.getKey());
196                    if (subType != null) {
197                        String key = subType.getCategory();
198                        if (key == null) {
199                            key = DEFAULT_CATEGORY;
200                        }
201                        if (!docTypesMap.containsKey(key)) {
202                            docTypesMap.put(key, new ArrayList<Type>());
203                        }
204                        docTypesMap.get(key).add(subType);
205                    }
206                }
207            }
208            return docTypesMap;
209        }
210        return new HashMap<>();
211    }
212
213    @Override
214    public boolean canCreate(String typeName, String containerTypeName) {
215        Type containerType = getType(containerTypeName);
216        Map<String, SubType> allowedSubTypes = containerType.getAllowedSubTypes();
217        return canCreate(typeName, allowedSubTypes);
218    }
219
220    @Override
221    public boolean canCreate(String typeName, String containerTypeName, DocumentModel currentDoc) {
222        Map<String, SubType> allowedSubTypes = getFilteredAllowedSubTypes(containerTypeName, currentDoc);
223        return canCreate(typeName, allowedSubTypes);
224    }
225
226    protected Map<String, SubType> getFilteredAllowedSubTypes(String containerTypeName, DocumentModel currentDoc) {
227        Type containerType = getType(containerTypeName);
228        if (containerType == null) {
229            return Collections.emptyMap();
230        }
231        Map<String, SubType> allowedSubTypes = containerType.getAllowedSubTypes();
232        return filterSubTypesFromConfiguration(allowedSubTypes, currentDoc);
233    }
234
235    protected boolean canCreate(String typeName, Map<String, SubType> allowedSubTypes) {
236        if (!isAllowedSubType(typeName, allowedSubTypes)) {
237            return false;
238        }
239
240        SubType subType = allowedSubTypes.get(typeName);
241        return canCreate(subType);
242    }
243
244    protected boolean canCreate(SubType subType) {
245        List<String> hidden = subType.getHidden();
246        return !(hidden != null && hidden.contains(HIDDEN_IN_CREATION));
247    }
248
249    @Override
250    public boolean isAllowedSubType(String typeName, String containerTypeName) {
251        Type containerType = getType(containerTypeName);
252        if (containerType == null) {
253            return false;
254        }
255        Map<String, SubType> allowedSubTypes = containerType.getAllowedSubTypes();
256        return isAllowedSubType(typeName, allowedSubTypes);
257    }
258
259    protected boolean isAllowedSubType(String typeName, Map<String, SubType> allowedSubTypes) {
260        for (String subTypeName : allowedSubTypes.keySet()) {
261            if (subTypeName.equals(typeName)) {
262                return true;
263            }
264        }
265        return false;
266    }
267
268    @Override
269    public boolean isAllowedSubType(String typeName, String containerTypeName, DocumentModel currentDoc) {
270        Map<String, SubType> allowedSubTypes = getFilteredAllowedSubTypes(containerTypeName, currentDoc);
271        return isAllowedSubType(typeName, allowedSubTypes);
272    }
273
274    protected Map<String, SubType> filterSubTypesFromConfiguration(Map<String, SubType> allowedSubTypes,
275            DocumentModel currentDoc) {
276        UITypesConfiguration uiTypesConfiguration = getConfiguration(currentDoc);
277        if (uiTypesConfiguration != null) {
278            allowedSubTypes = uiTypesConfiguration.filterSubTypes(allowedSubTypes);
279        }
280        return allowedSubTypes;
281    }
282
283}