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.logging.log4j.LogManager;
034import org.apache.logging.log4j.Logger;
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.DefaultComponent;
039import org.nuxeo.runtime.model.Descriptor;
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 static final Logger log = LogManager.getLogger(MarshallerRegistryImpl.class);
053
054    /**
055     * @since 10.3
056     */
057    public static final String XP_MARSHALLERS = "marshallers";
058
059    /**
060     * All {@link Writer}'s {@link MarshallerInspector} ordered by their priority.
061     */
062    private static final Set<MarshallerInspector> writers = new ConcurrentSkipListSet<>();
063
064    /**
065     * {@link Writer}'s {@link MarshallerInspector} organized by their managed {@link MediaType}.
066     */
067    private static final Map<MediaType, Set<MarshallerInspector>> writersByMediaType = new ConcurrentHashMap<>();
068
069    /**
070     * All {@link Reader}'s {@link MarshallerInspector} ordered by their priority.
071     */
072    private static final Set<MarshallerInspector> readers = new ConcurrentSkipListSet<>();
073
074    /**
075     * {@link Reader}'s {@link MarshallerInspector} organized by their managed {@link MediaType}.
076     */
077    private static final Map<MediaType, Set<MarshallerInspector>> readersByMediaType = new ConcurrentHashMap<>();
078
079    /**
080     * {@link MarshallerInspector} organized by their managed {@link Marshaller} class.
081     */
082    private static final Map<Class<?>, MarshallerInspector> marshallersByType = new ConcurrentHashMap<>();
083
084    @Override
085    public void deactivate(ComponentContext context) {
086        clear();
087        super.deactivate(context);
088    }
089
090    @Override
091    protected boolean register(String xp, Descriptor descriptor) {
092        boolean registered = super.register(xp, descriptor);
093        if (registered) {
094            MarshallerRegistryDescriptor mrd = getDescriptor(xp, descriptor.getId());
095            if (mrd.enable) {
096                register(mrd.klass);
097            } else {
098                deregister(mrd.klass);
099            }
100        }
101        return registered;
102    }
103
104    @Override
105    protected boolean unregister(String xp, Descriptor descriptor) {
106        MarshallerRegistryDescriptor mrd = getDescriptor(xp, descriptor.getId());
107        if (mrd == null) {
108            return false;
109        }
110        boolean unregistered = super.unregister(xp, descriptor);
111        if (unregistered) {
112            if (mrd.enable) {
113                deregister(mrd.klass);
114            }
115        }
116        return unregistered;
117    }
118
119    @Override
120    public void register(Class<?> marshaller) {
121        if (marshaller == null) {
122            throw new MarshallingException("Cannot register null marshaller");
123        }
124        MarshallerInspector inspector = new MarshallerInspector(marshaller);
125        if (!inspector.isWriter() && !inspector.isReader()) {
126            throw new MarshallingException(
127                    "The marshaller registry just supports Writer and Reader for now. You have to implement "
128                            + Writer.class.getName() + " or " + Reader.class.getName());
129        }
130        if (marshallersByType.get(marshaller) != null) {
131            log.warn("The marshaller {} is already registered.", marshaller.getName());
132            return;
133        } else {
134            marshallersByType.put(marshaller, inspector);
135        }
136        if (inspector.isWriter()) {
137            writers.add(inspector);
138            for (MediaType mediaType : inspector.getSupports()) {
139                Set<MarshallerInspector> inspectors = writersByMediaType.get(mediaType);
140                if (inspectors == null) {
141                    inspectors = new ConcurrentSkipListSet<>();
142                    writersByMediaType.put(mediaType, inspectors);
143                }
144                inspectors.add(inspector);
145            }
146        }
147        if (inspector.isReader()) {
148            readers.add(inspector);
149            for (MediaType mediaType : inspector.getSupports()) {
150                Set<MarshallerInspector> inspectors = readersByMediaType.get(mediaType);
151                if (inspectors == null) {
152                    inspectors = new ConcurrentSkipListSet<>();
153                    readersByMediaType.put(mediaType, inspectors);
154                }
155                inspectors.add(inspector);
156            }
157        }
158    }
159
160    @Override
161    public void deregister(Class<?> marshaller) {
162        if (marshaller == null) {
163            log.warn("Cannot deregister null marshaller");
164            return;
165        }
166        MarshallerInspector inspector = new MarshallerInspector(marshaller);
167        if (!inspector.isWriter() && !inspector.isReader()) {
168            throw new MarshallingException(
169                    "The marshaller registry just supports Writer and Reader for now. You have to implement "
170                            + Writer.class.getName() + " or " + Reader.class.getName());
171        }
172        marshallersByType.remove(marshaller);
173        if (inspector.isWriter()) {
174            writers.remove(inspector);
175            for (MediaType mediaType : inspector.getSupports()) {
176                Set<MarshallerInspector> inspectors = writersByMediaType.get(mediaType);
177                if (inspectors != null) {
178                    inspectors.remove(inspector);
179                }
180            }
181        }
182        if (inspector.isReader()) {
183            readers.remove(inspector);
184            for (MediaType mediaType : inspector.getSupports()) {
185                Set<MarshallerInspector> inspectors = readersByMediaType.get(mediaType);
186                if (inspectors != null) {
187                    inspectors.remove(inspector);
188                }
189            }
190        }
191    }
192
193    @Override
194    public <T> Writer<T> getWriter(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, false);
198    }
199
200    @Override
201    public <T> Writer<T> getUniqueWriter(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
202            MediaType mediatype) {
203        Set<MarshallerInspector> candidates = writersByMediaType.get(mediatype);
204        return (Writer<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, writers, true);
205    }
206
207    @Override
208    @SuppressWarnings("unchecked")
209    public <T> Collection<Writer<T>> getAllWriters(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
210            MediaType mediatype) {
211        Set<MarshallerInspector> candidates = writersByMediaType.get(mediatype);
212        Collection<Marshaller<T>> founds = getAllMarshallers(ctx, marshalledClazz, genericType, mediatype, candidates,
213                writers);
214        return (Collection<Writer<T>>) (Collection<?>) founds;
215    }
216
217    @Override
218    public <T> Writer<T> getWriter(RenderingContext ctx, Class<T> marshalledClazz, MediaType mediatype) {
219        return getWriter(ctx, marshalledClazz, marshalledClazz, mediatype);
220    }
221
222    @Override
223    public <T> Reader<T> getReader(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
224            MediaType mediatype) {
225        Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype);
226        return (Reader<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, readers, false);
227    }
228
229    @Override
230    public <T> Reader<T> getUniqueReader(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
231            MediaType mediatype) {
232        Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype);
233        return (Reader<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, readers, true);
234    }
235
236    @Override
237    @SuppressWarnings("unchecked")
238    public <T> Collection<Reader<T>> getAllReaders(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
239            MediaType mediatype) {
240        Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype);
241        Collection<Marshaller<T>> founds = getAllMarshallers(ctx, marshalledClazz, genericType, mediatype, candidates,
242                readers);
243        return (Collection<Reader<T>>) (Collection<?>) founds;
244    }
245
246    @Override
247    public <T> Reader<T> getReader(RenderingContext ctx, Class<T> marshalledClazz, MediaType mediatype) {
248        return getReader(ctx, marshalledClazz, marshalledClazz, mediatype);
249    }
250
251    public <T> Marshaller<T> getMarshaller(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
252            MediaType mediatype, Set<MarshallerInspector> customs, Set<MarshallerInspector> wildcards,
253            boolean forceInstantiation) {
254        if (customs != null) {
255            Marshaller<T> found = searchCandidate(ctx, marshalledClazz, genericType, mediatype, customs,
256                    forceInstantiation);
257            if (found != null) {
258                return found;
259            }
260        }
261        return searchCandidate(ctx, marshalledClazz, genericType, mediatype, wildcards, forceInstantiation);
262    }
263
264    public <T> Collection<Marshaller<T>> getAllMarshallers(RenderingContext ctx, Class<T> marshalledClazz,
265            Type genericType, MediaType mediatype, Set<MarshallerInspector> customs,
266            Set<MarshallerInspector> wildcards) {
267        Map<MarshallerInspector, Marshaller<T>> result = new HashMap<>();
268        if (customs != null) {
269            result.putAll(searchAllCandidates(ctx, marshalledClazz, genericType, mediatype, customs));
270        }
271        result.putAll(searchAllCandidates(ctx, marshalledClazz, genericType, mediatype, wildcards));
272        return result.values();
273    }
274
275    @SuppressWarnings("unchecked")
276    private <T> Marshaller<T> searchCandidate(RenderingContext ctx, Class<T> marshalledClazz, Type genericType,
277            MediaType mediatype, Set<MarshallerInspector> candidates, boolean forceInstantiation) {
278        for (MarshallerInspector inspector : candidates) {
279            // checks the managed class is compatible
280            if (inspector.getMarshalledType().isAssignableFrom(marshalledClazz)) {
281                // checks the generic type is compatible
282                if (genericType == null || marshalledClazz.equals(inspector.getGenericType())
283                        || TypeUtils.isAssignable(genericType, inspector.getGenericType())) {
284                    Marshaller<T> marshaller = null;
285                    if (forceInstantiation) {
286                        marshaller = (Marshaller<T>) inspector.getNewInstance(ctx, false);
287                    } else {
288                        marshaller = inspector.getInstance(ctx);
289                    }
290                    // checks the marshaller accepts the request
291                    if (marshaller.accept(marshalledClazz, genericType, mediatype)) {
292                        return marshaller;
293                    }
294                }
295            }
296        }
297        return null;
298    }
299
300    private <T> Map<MarshallerInspector, Marshaller<T>> searchAllCandidates(RenderingContext ctx,
301            Class<T> marshalledClazz, Type genericType, MediaType mediatype, Set<MarshallerInspector> candidates) {
302        Map<MarshallerInspector, Marshaller<T>> result = new HashMap<>();
303        for (MarshallerInspector inspector : candidates) {
304            // checks the managed class is compatible
305            if (inspector.getMarshalledType().isAssignableFrom(marshalledClazz)) {
306                // checks the generic type is compatible
307                if (genericType == null || marshalledClazz.equals(inspector.getGenericType())
308                        || TypeUtils.isAssignable(genericType, inspector.getGenericType())) {
309                    // checks the marshaller accepts the request
310                    Marshaller<T> marshaller = inspector.getInstance(ctx);
311                    if (marshaller.accept(marshalledClazz, genericType, mediatype)) {
312                        result.put(inspector, marshaller);
313                    }
314                }
315            }
316        }
317        return result;
318    }
319
320    @Override
321    public <T> T getInstance(RenderingContext ctx, Class<T> marshallerClass) {
322        MarshallerInspector inspector = marshallersByType.get(marshallerClass);
323        if (inspector == null) {
324            inspector = new MarshallerInspector(marshallerClass);
325        }
326        return inspector.getInstance(ctx);
327    }
328
329    @Override
330    public <T> T getUniqueInstance(RenderingContext ctx, Class<T> marshallerClass) {
331        MarshallerInspector inspector = marshallersByType.get(marshallerClass);
332        if (inspector == null) {
333            inspector = new MarshallerInspector(marshallerClass);
334        }
335        @SuppressWarnings("unchecked")
336        T result = (T) inspector.getNewInstance(ctx, false);
337        return result;
338    }
339
340    @Override
341    public void clear() {
342        marshallersByType.clear();
343        writersByMediaType.clear();
344        readersByMediaType.clear();
345        writers.clear();
346        readers.clear();
347    }
348
349}