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