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