001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     bstefanescu
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.webengine.model.impl;
021
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.concurrent.ConcurrentHashMap;
028
029import org.nuxeo.ecm.core.schema.DocumentType;
030import org.nuxeo.ecm.core.schema.SchemaManager;
031import org.nuxeo.ecm.webengine.WebEngine;
032import org.nuxeo.ecm.webengine.loader.ClassProxy;
033import org.nuxeo.ecm.webengine.loader.StaticClassProxy;
034import org.nuxeo.ecm.webengine.model.AdapterType;
035import org.nuxeo.ecm.webengine.model.Resource;
036import org.nuxeo.ecm.webengine.model.ResourceType;
037import org.nuxeo.runtime.api.Framework;
038import org.nuxeo.runtime.contribution.impl.AbstractContributionRegistry;
039
040/**
041 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
042 */
043public class TypeRegistry extends AbstractContributionRegistry<String, TypeDescriptor> {
044
045    protected final Map<String, AbstractResourceType> types;
046
047    protected final Map<String, AdapterType> adapters;
048
049    protected final ModuleImpl module;
050
051    protected final WebEngine engine; // cannot use module.getEngine() since module may be null
052
053    protected ClassProxy docObjectClass;
054
055    public TypeRegistry(TypeRegistry parent, WebEngine engine, ModuleImpl module) {
056        super(parent);
057        types = new ConcurrentHashMap<String, AbstractResourceType>();
058        adapters = new ConcurrentHashMap<String, AdapterType>();
059        this.module = module;
060        this.engine = engine;
061        // register root type
062        if (parent == null) {
063            registerRootType();
064        } else {
065            importParentContributions();
066        }
067    }
068
069    public TypeRegistry(WebEngine engine, ModuleImpl module) {
070        this(null, engine, module);
071    }
072
073    protected void registerRootType() {
074        TypeDescriptor root = new TypeDescriptor(new StaticClassProxy(Resource.class), ResourceType.ROOT_TYPE_NAME,
075                null);
076        registerType(root);
077    }
078
079    public ResourceType getRootType() {
080        return types.get(ResourceType.ROOT_TYPE_NAME);
081    }
082
083    public ModuleImpl getModule() {
084        return module;
085    }
086
087    public ResourceType getType(String name) {
088        ResourceType type = types.get(name);
089        if (type == null) { // check for a non registered document type
090            if (registerDocumentTypeIfNeeded(name)) {
091                type = types.get(name);
092            }
093        }
094        return type;
095    }
096
097    public AdapterType getAdapter(String name) {
098        return adapters.get(name);
099    }
100
101    public AdapterType getAdapter(Resource target, String name) {
102        AdapterType adapter = adapters.get(name);
103        if (adapter != null && adapter.acceptResource(target)) {
104            return adapter;
105        }
106        return null;
107    }
108
109    public List<AdapterType> getAdapters(Resource resource) {
110        List<AdapterType> result = new ArrayList<AdapterType>();
111        collectAdaptersFor(resource, resource.getType(), result);
112        return result;
113    }
114
115    public List<String> getAdapterNames(Resource resource) {
116        List<String> result = new ArrayList<String>();
117        collectAdapterNamesFor(resource, resource.getType(), result);
118        return result;
119    }
120
121    public List<AdapterType> getEnabledAdapters(Resource resource) {
122        List<AdapterType> result = new ArrayList<AdapterType>();
123        collectEnabledAdaptersFor(resource, resource.getType(), result);
124        return result;
125    }
126
127    public List<String> getEnabledAdapterNames(Resource resource) {
128        List<String> result = new ArrayList<String>();
129        collectEnabledAdapterNamesFor(resource, resource.getType(), result);
130        return result;
131    }
132
133    protected void collectAdaptersFor(Resource ctx, ResourceType type, List<AdapterType> result) {
134        for (AdapterType adapter : getAdapters()) {
135            if (adapter.acceptResource(ctx)) {
136                result.add(adapter);
137            }
138        }
139    }
140
141    protected void collectAdapterNamesFor(Resource ctx, ResourceType type, List<String> result) {
142        for (AdapterType adapter : getAdapters()) {
143            if (adapter.acceptResource(ctx)) {
144                result.add(adapter.getAdapterName());
145            }
146        }
147    }
148
149    protected void collectEnabledAdaptersFor(Resource ctx, ResourceType type, List<AdapterType> result) {
150        for (AdapterType adapter : getAdapters()) {
151            if (adapter.acceptResource(ctx) && adapter.isEnabled(ctx)) {
152                result.add(adapter);
153            }
154        }
155    }
156
157    protected void collectEnabledAdapterNamesFor(Resource ctx, ResourceType type, List<String> result) {
158        for (AdapterType adapter : getAdapters()) {
159            if (adapter.acceptResource(ctx) && adapter.isEnabled(ctx)) {
160                result.add(adapter.getAdapterName());
161            }
162        }
163    }
164
165    public ResourceType[] getTypes() {
166        return types.values().toArray(new ResourceType[types.size()]);
167    }
168
169    public AdapterType[] getAdapters() {
170        return adapters.values().toArray(new AdapterType[adapters.size()]);
171    }
172
173    public void registerTypeDescriptor(TypeDescriptor td) {
174        if (td.isAdapter()) {
175            registerAdapter(td.asAdapterDescriptor());
176        } else {
177            registerType(td);
178        }
179    }
180
181    public synchronized void registerType(TypeDescriptor td) {
182        if (td.superType != null && !types.containsKey(td.superType)) {
183            registerDocumentTypeIfNeeded(td.superType);
184        }
185        addFragment(td.type, td, td.superType);
186    }
187
188    public synchronized void registerAdapter(AdapterDescriptor td) {
189        addFragment(td.type, td, td.superType);
190    }
191
192    public void unregisterType(TypeDescriptor td) {
193        removeFragment(td.type, td);
194    }
195
196    public void unregisterAdapter(TypeDescriptor td) {
197        removeFragment(td.type, td);
198    }
199
200    protected boolean registerDocumentTypeIfNeeded(String typeName) {
201        // we have a special case for document types.
202        // If a web document type is not resolved then use a default web document type
203        // This avoid defining web types for every document type in the system.
204        // The web document type use by default the same type hierarchy as document types
205        SchemaManager mgr = Framework.getLocalService(SchemaManager.class);
206        if (mgr != null) {
207            DocumentType doctype = mgr.getDocumentType(typeName);
208            if (doctype != null) { // this is a document type - register a default web type
209                DocumentType superSuperType = (DocumentType) doctype.getSuperType();
210                String superSuperTypeName = ResourceType.ROOT_TYPE_NAME;
211                if (superSuperType != null) {
212                    superSuperTypeName = superSuperType.getName();
213                }
214                try {
215                    if (docObjectClass == null) {
216                        docObjectClass = engine.getWebLoader().getClassProxy("org.nuxeo.ecm.core.rest.DocumentObject");
217                    }
218                    TypeDescriptor superWebType = new TypeDescriptor(docObjectClass, typeName, superSuperTypeName);
219                    registerType(superWebType);
220                    return true;
221                } catch (ClassNotFoundException e) {
222                    // TODO
223                    System.err.println("Cannot find document resource class. Automatic Core Type support will be disabled ");
224                }
225            }
226        }
227        return false;
228    }
229
230    @Override
231    protected TypeDescriptor clone(TypeDescriptor object) {
232        return object.clone();
233    }
234
235    @Override
236    protected void applyFragment(TypeDescriptor object, TypeDescriptor fragment) {
237        // a type fragment may be used to replace the type implementation class.
238        // Super type cannot be replaced
239        if (fragment.clazz != null) {
240            object.clazz = fragment.clazz;
241        }
242        if (object.isAdapter()) {
243            AdapterDescriptor so = (AdapterDescriptor) object;
244            AdapterDescriptor sf = (AdapterDescriptor) fragment;
245            if (sf.facets != null && sf.facets.length > 0) {
246                List<String> list = new ArrayList<String>();
247                if (so.facets != null && so.facets.length > 0) {
248                    list.addAll(Arrays.asList(so.facets));
249                }
250                list.addAll(Arrays.asList(sf.facets));
251            }
252            if (sf.targetType != null && !sf.targetType.equals(ResourceType.ROOT_TYPE_NAME)) {
253                so.targetType = sf.targetType;
254            }
255        }
256    }
257
258    @Override
259    protected void applySuperFragment(TypeDescriptor object, TypeDescriptor superFragment) {
260        // do not inherit from parents
261    }
262
263    @Override
264    protected void installContribution(String key, TypeDescriptor object) {
265        if (object.isAdapter()) {
266            installAdapterContribution(key, (AdapterDescriptor) object);
267        } else {
268            installTypeContribution(key, object);
269        }
270    }
271
272    protected void installTypeContribution(String key, TypeDescriptor object) {
273        AbstractResourceType type = new ResourceTypeImpl(engine, module, null, object.type, object.clazz,
274                object.visibility);
275        if (object.superType != null) {
276            type.superType = types.get(object.superType);
277            assert type.superType != null; // must never be null since the object is resolved
278        }
279        // import document facets if this type wraps a document type
280        SchemaManager mgr = Framework.getLocalService(SchemaManager.class);
281        if (mgr != null) {
282            DocumentType doctype = mgr.getDocumentType(type.getName());
283            if (doctype != null) {
284                if (type.facets == null) {
285                    type.facets = new HashSet<String>();
286                }
287                type.facets.addAll(doctype.getFacets());
288            }
289        }
290        // register the type
291        types.put(object.type, type);
292    }
293
294    protected void installAdapterContribution(String key, AdapterDescriptor object) {
295        AdapterTypeImpl type = new AdapterTypeImpl(engine, module, null, object.type, object.name, object.clazz,
296                object.visibility);
297        if (object.superType != null) {
298            type.superType = types.get(object.superType);
299            assert type.superType != null; // must never be null since the object is resolved
300        }
301        types.put(object.type, type);
302        adapters.put(object.name, type);
303    }
304
305    @Override
306    protected void updateContribution(String key, TypeDescriptor object, TypeDescriptor oldValue) {
307        if (object.isAdapter()) {
308            updateAdapterContribution(key, (AdapterDescriptor) object);
309        } else {
310            updateTypeContribution(key, object);
311        }
312    }
313
314    protected void updateTypeContribution(String key, TypeDescriptor object) {
315        // When a type is updated (i.e. reinstalled) we must not replace
316        // the existing type since it may contains some contributed actions.
317        // There are two methods to do this:
318        // 1. update the existing type
319        // 2. unresolve, reinstall then resolve the type contribution to force action reinstalling.
320        // we are using 1.
321        AbstractResourceType t = types.get(key);
322        if (t != null) { // update the type class
323            t.clazz = object.clazz;
324            t.loadAnnotations(engine.getAnnotationManager());
325            t.flushCache();
326        } else { // install the type - this should never happen since it is an update!
327            throw new IllegalStateException("Updating an object type which is not registered.");
328        }
329    }
330
331    protected void updateAdapterContribution(String key, AdapterDescriptor object) {
332        AbstractResourceType t = types.get(key);
333        if (t instanceof AdapterTypeImpl) { // update the type class
334            AdapterTypeImpl adapter = (AdapterTypeImpl) t;
335            adapter.clazz = object.clazz;
336            adapter.loadAnnotations(engine.getAnnotationManager());
337            t.flushCache();
338        } else { // install the type - this should never happen since it is an update!
339            throw new IllegalStateException("Updating an adapter type which is not registered: " + key);
340        }
341    }
342
343    @Override
344    protected void uninstallContribution(String key, TypeDescriptor value) {
345        AbstractResourceType t = types.remove(key);
346        if (t instanceof AdapterTypeImpl) {
347            adapters.remove(((AdapterTypeImpl) t).name);
348        }
349    }
350
351    @Override
352    protected boolean isMainFragment(TypeDescriptor object) {
353        return object.isMainFragment();
354    }
355
356}