001/*
002 * (C) Copyright 2015 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 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
018 */
019
020package org.nuxeo.ecm.core.io.registry;
021
022import java.lang.reflect.Type;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.Set;
027import java.util.concurrent.ConcurrentHashMap;
028import java.util.concurrent.ConcurrentSkipListSet;
029
030import javax.ws.rs.core.MediaType;
031
032import org.apache.commons.lang3.reflect.TypeUtils;
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.core.io.registry.context.RenderingContext;
036import org.nuxeo.ecm.core.io.registry.reflect.MarshallerInspector;
037import org.nuxeo.runtime.model.ComponentContext;
038import org.nuxeo.runtime.model.ComponentInstance;
039import org.nuxeo.runtime.model.DefaultComponent;
040
041/**
042 * Implementation of {@link MarshallerRegistry}.
043 * <p>
044 * This implementation is based on {@link MarshallerInspector} class which is able to create marshaller instance and
045 * inject properties. This class also manage marshaller's priorities.
046 * </p>
047 *
048 * @since 7.2
049 */
050public class MarshallerRegistryImpl extends DefaultComponent implements MarshallerRegistry {
051
052    private final Log log = LogFactory.getLog(MarshallerRegistryImpl.class);
053
054    /**
055     * All {@link Writer}'s {@link MarshallerInspector} ordered by their priority.
056     */
057    private static final Set<MarshallerInspector> writers = new ConcurrentSkipListSet<MarshallerInspector>();
058
059    /**
060     * {@link Writer}'s {@link MarshallerInspector} organized by their managed {@link MediaType}.
061     */
062    private static final Map<MediaType, Set<MarshallerInspector>> writersByMediaType = new ConcurrentHashMap<MediaType, Set<MarshallerInspector>>();
063
064    /**
065     * All {@link Reader}'s {@link MarshallerInspector} ordered by their priority.
066     */
067    private static final Set<MarshallerInspector> readers = new ConcurrentSkipListSet<MarshallerInspector>();
068
069    /**
070     * {@link Reader}'s {@link MarshallerInspector} organized by their managed {@link MediaType}.
071     */
072    private static final Map<MediaType, Set<MarshallerInspector>> readersByMediaType = new ConcurrentHashMap<MediaType, Set<MarshallerInspector>>();
073
074    /**
075     * {@link MarshallerInspector} organized by their managed {@link Marshaller} class.
076     */
077    private static final Map<Class<?>, MarshallerInspector> marshallersByType = new ConcurrentHashMap<Class<?>, MarshallerInspector>();
078
079    @Override
080    public void activate(ComponentContext context) {
081        super.activate(context);
082        clear();
083    }
084
085    @Override
086    public void deactivate(ComponentContext context) {
087        clear();
088        super.deactivate(context);
089    }
090
091    @Override
092    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
093        if (extensionPoint.equals("marshallers")) {
094            MarshallerRegistryDescriptor mrd = (MarshallerRegistryDescriptor) contribution;
095            if (mrd.isEnable()) {
096                register(mrd.getClazz());
097            } else {
098                deregister(mrd.getClazz());
099            }
100        }
101    }
102
103    @Override
104    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
105        if (extensionPoint.equals("marshallers")) {
106            MarshallerRegistryDescriptor mrd = (MarshallerRegistryDescriptor) contribution;
107            if (mrd.isEnable()) {
108                deregister(mrd.getClazz());
109            } else {
110                register(mrd.getClazz());
111            }
112        }
113    }
114
115    @Override
116    public void register(Class<?> marshaller) {
117        if (marshaller == null) {
118            throw new MarshallingException("Cannot register null marshaller");
119        }
120        MarshallerInspector inspector = new MarshallerInspector(marshaller);
121        if (!inspector.isWriter() && !inspector.isReader()) {
122            throw new MarshallingException(
123                    "The marshaller registry just supports Writer and Reader for now. You have to implement "
124                            + Writer.class.getName() + " or " + Reader.class.getName());
125        }
126        if (marshallersByType.get(marshaller) != null) {
127            log.warn("The marshaller " + marshaller.getName() + " is already registered.");
128            return;
129        } else {
130            marshallersByType.put(marshaller, inspector);
131        }
132        if (inspector.isWriter()) {
133            writers.add(inspector);
134            for (MediaType mediaType : inspector.getSupports()) {
135                Set<MarshallerInspector> inspectors = writersByMediaType.get(mediaType);
136                if (inspectors == null) {
137                    inspectors = new ConcurrentSkipListSet<MarshallerInspector>();
138                    writersByMediaType.put(mediaType, inspectors);
139                }
140                inspectors.add(inspector);
141            }
142        }
143        if (inspector.isReader()) {
144            readers.add(inspector);
145            for (MediaType mediaType : inspector.getSupports()) {
146                Set<MarshallerInspector> inspectors = readersByMediaType.get(mediaType);
147                if (inspectors == null) {
148                    inspectors = new ConcurrentSkipListSet<MarshallerInspector>();
149                    readersByMediaType.put(mediaType, inspectors);
150                }
151                inspectors.add(inspector);
152            }
153        }
154    }
155
156    @Override
157    public void deregister(Class<?> marshaller) throws MarshallingException {
158        if (marshaller == null) {
159            log.warn("Cannot register null marshaller");
160            return;
161        }
162        MarshallerInspector inspector = new MarshallerInspector(marshaller);
163        if (!inspector.isWriter() && !inspector.isReader()) {
164            throw new MarshallingException(
165                    "The marshaller registry just supports Writer and Reader for now. You have to implement "
166                            + Writer.class.getName() + " or " + Reader.class.getName());
167        }
168        marshallersByType.remove(marshaller);
169        if (inspector.isWriter()) {
170            writers.remove(inspector);
171            for (MediaType mediaType : inspector.getSupports()) {
172                Set<MarshallerInspector> inspectors = writersByMediaType.get(mediaType);
173                if (inspectors != null) {
174                    inspectors.remove(inspector);
175                }
176            }
177        }
178        if (inspector.isReader()) {
179            readers.remove(inspector);
180            for (MediaType mediaType : inspector.getSupports()) {
181                Set<MarshallerInspector> inspectors = readersByMediaType.get(mediaType);
182                if (inspectors != null) {
183                    inspectors.remove(inspector);
184                }
185            }
186        }
187    }
188
189    @Override
190    public <T> Writer<T> getWriter(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype) {
191        Set<MarshallerInspector> candidates = writersByMediaType.get(mediatype);
192        return (Writer<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, writers, false);
193    }
194
195    @Override
196    public <T> Writer<T> getUniqueWriter(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
197            MediaType mediatype) {
198        Set<MarshallerInspector> candidates = writersByMediaType.get(mediatype);
199        return (Writer<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, writers, true);
200    }
201
202    @Override
203    @SuppressWarnings("unchecked")
204    public <T> Collection<Writer<T>> getAllWriters(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
205            MediaType mediatype) {
206        Set<MarshallerInspector> candidates = writersByMediaType.get(mediatype);
207        Collection<Marshaller<T>> founds = getAllMarshallers(ctx, marshalledClazz, genericType, mediatype, candidates,
208                writers);
209        return (Collection<Writer<T>>) (Collection<?>) founds;
210    }
211
212    @Override
213    public <T> Writer<T> getWriter(RenderingContext ctx, Class<T> marshalledClazz, MediaType mediatype) {
214        return getWriter(ctx, marshalledClazz, marshalledClazz, mediatype);
215    }
216
217    @Override
218    public <T> Reader<T> getReader(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype) {
219        Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype);
220        return (Reader<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, readers, false);
221    }
222
223    @Override
224    public <T> Reader<T> getUniqueReader(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
225            MediaType mediatype) {
226        Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype);
227        return (Reader<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, readers, true);
228    }
229
230    @Override
231    @SuppressWarnings("unchecked")
232    public <T> Collection<Reader<T>> getAllReaders(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
233            MediaType mediatype) {
234        Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype);
235        Collection<Marshaller<T>> founds = getAllMarshallers(ctx, marshalledClazz, genericType, mediatype, candidates,
236                readers);
237        return (Collection<Reader<T>>) (Collection<?>) founds;
238    }
239
240    @Override
241    public <T> Reader<T> getReader(RenderingContext ctx, Class<T> marshalledClazz, MediaType mediatype) {
242        return getReader(ctx, marshalledClazz, marshalledClazz, mediatype);
243    }
244
245    public <T> Marshaller<T> getMarshaller(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
246            MediaType mediatype, Set<MarshallerInspector> customs, Set<MarshallerInspector> wildcards,
247            boolean forceInstantiation) {
248        if (customs != null) {
249            Marshaller<T> found = searchCandidate(ctx, marshalledClazz, genericType, mediatype, customs,
250                    forceInstantiation);
251            if (found != null) {
252                return found;
253            }
254        }
255        return searchCandidate(ctx, marshalledClazz, genericType, mediatype, wildcards, forceInstantiation);
256    }
257
258    public <T> Collection<Marshaller<T>> getAllMarshallers(RenderingContext ctx, Class<T> marshalledClazz,
259            Type genericType, MediaType mediatype, Set<MarshallerInspector> customs, Set<MarshallerInspector> wildcards) {
260        Map<MarshallerInspector, Marshaller<T>> result = new HashMap<MarshallerInspector, Marshaller<T>>();
261        if (customs != null) {
262            result.putAll(searchAllCandidates(ctx, marshalledClazz, genericType, mediatype, customs));
263        }
264        result.putAll(searchAllCandidates(ctx, marshalledClazz, genericType, mediatype, wildcards));
265        return result.values();
266    }
267
268    @SuppressWarnings("unchecked")
269    private <T> Marshaller<T> searchCandidate(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
270            MediaType mediatype, Set<MarshallerInspector> candidates, boolean forceInstantiation) {
271        for (MarshallerInspector inspector : candidates) {
272            // checks the managed class is compatible
273            if (inspector.getMarshalledType().isAssignableFrom(marshalledClazz)) {
274                // checks the generic type is compatible
275                if (genericType == null || marshalledClazz.equals(inspector.getGenericType())
276                        || TypeUtils.isAssignable(genericType, inspector.getGenericType())) {
277                    Marshaller<T> marshaller = null;
278                    if (forceInstantiation) {
279                        marshaller = (Marshaller<T>) inspector.getNewInstance(ctx, false);
280                    } else {
281                        marshaller = inspector.getInstance(ctx);
282                    }
283                    // checks the marshaller accepts the request
284                    if (marshaller.accept(marshalledClazz, genericType, mediatype)) {
285                        return marshaller;
286                    }
287                }
288            }
289        }
290        return null;
291    }
292
293    private <T> Map<MarshallerInspector, Marshaller<T>> searchAllCandidates(RenderingContext ctx,
294            Class<T> marshalledClazz, Type genericType, MediaType mediatype, Set<MarshallerInspector> candidates) {
295        Map<MarshallerInspector, Marshaller<T>> result = new HashMap<MarshallerInspector, Marshaller<T>>();
296        for (MarshallerInspector inspector : candidates) {
297            // checks the managed class is compatible
298            if (inspector.getMarshalledType().isAssignableFrom(marshalledClazz)) {
299                // checks the generic type is compatible
300                if (genericType == null || marshalledClazz.equals(inspector.getGenericType())
301                        || TypeUtils.isAssignable(genericType, inspector.getGenericType())) {
302                    // checks the marshaller accepts the request
303                    Marshaller<T> marshaller = inspector.getInstance(ctx);
304                    if (marshaller.accept(marshalledClazz, genericType, mediatype)) {
305                        result.put(inspector, marshaller);
306                    }
307                }
308            }
309        }
310        return result;
311    }
312
313    @Override
314    public <T> T getInstance(RenderingContext ctx, Class<T> marshallerClass) {
315        MarshallerInspector inspector = marshallersByType.get(marshallerClass);
316        if (inspector == null) {
317            inspector = new MarshallerInspector(marshallerClass);
318        }
319        return inspector.getInstance(ctx);
320    }
321
322    @Override
323    public <T> T getUniqueInstance(RenderingContext ctx, Class<T> marshallerClass) {
324        MarshallerInspector inspector = marshallersByType.get(marshallerClass);
325        if (inspector == null) {
326            inspector = new MarshallerInspector(marshallerClass);
327        }
328        @SuppressWarnings("unchecked")
329        T result = (T) inspector.getNewInstance(ctx, false);
330        return result;
331    }
332
333    @Override
334    public void clear() {
335        marshallersByType.clear();
336        writersByMediaType.clear();
337        writers.clear();
338        readersByMediaType.clear();
339        readers.clear();
340    }
341
342}