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