/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Nicolas Chapurlat <nchapurlat@nuxeo.com> */ package org.nuxeo.ecm.core.io.registry; import java.lang.reflect.Type; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import javax.ws.rs.core.MediaType; import org.apache.commons.lang3.reflect.TypeUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.core.io.registry.context.RenderingContext; import org.nuxeo.ecm.core.io.registry.reflect.MarshallerInspector; import org.nuxeo.runtime.model.ComponentContext; import org.nuxeo.runtime.model.ComponentInstance; import org.nuxeo.runtime.model.DefaultComponent; /** * Implementation of {@link MarshallerRegistry}. * <p> * This implementation is based on {@link MarshallerInspector} class which is able to create marshaller instance and * inject properties. This class also manage marshaller's priorities. * </p> * * @since 7.2 */ public class MarshallerRegistryImpl extends DefaultComponent implements MarshallerRegistry { private final Log log = LogFactory.getLog(MarshallerRegistryImpl.class); /** * All {@link Writer}'s {@link MarshallerInspector} ordered by their priority. */ private static final Set<MarshallerInspector> writers = new ConcurrentSkipListSet<MarshallerInspector>(); /** * {@link Writer}'s {@link MarshallerInspector} organized by their managed {@link MediaType}. */ private static final Map<MediaType, Set<MarshallerInspector>> writersByMediaType = new ConcurrentHashMap<MediaType, Set<MarshallerInspector>>(); /** * All {@link Reader}'s {@link MarshallerInspector} ordered by their priority. */ private static final Set<MarshallerInspector> readers = new ConcurrentSkipListSet<MarshallerInspector>(); /** * {@link Reader}'s {@link MarshallerInspector} organized by their managed {@link MediaType}. */ private static final Map<MediaType, Set<MarshallerInspector>> readersByMediaType = new ConcurrentHashMap<MediaType, Set<MarshallerInspector>>(); /** * {@link MarshallerInspector} organized by their managed {@link Marshaller} class. */ private static final Map<Class<?>, MarshallerInspector> marshallersByType = new ConcurrentHashMap<Class<?>, MarshallerInspector>(); @Override public void activate(ComponentContext context) { super.activate(context); clear(); } @Override public void deactivate(ComponentContext context) { clear(); super.deactivate(context); } @Override public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { if (extensionPoint.equals("marshallers")) { MarshallerRegistryDescriptor mrd = (MarshallerRegistryDescriptor) contribution; if (mrd.isEnable()) { register(mrd.getClazz()); } else { deregister(mrd.getClazz()); } } } @Override public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) { if (extensionPoint.equals("marshallers")) { MarshallerRegistryDescriptor mrd = (MarshallerRegistryDescriptor) contribution; if (mrd.isEnable()) { deregister(mrd.getClazz()); } else { register(mrd.getClazz()); } } } @Override public void register(Class<?> marshaller) { if (marshaller == null) { throw new MarshallingException("Cannot register null marshaller"); } MarshallerInspector inspector = new MarshallerInspector(marshaller); if (!inspector.isWriter() && !inspector.isReader()) { throw new MarshallingException( "The marshaller registry just supports Writer and Reader for now. You have to implement " + Writer.class.getName() + " or " + Reader.class.getName()); } if (marshallersByType.get(marshaller) != null) { log.warn("The marshaller " + marshaller.getName() + " is already registered."); return; } else { marshallersByType.put(marshaller, inspector); } if (inspector.isWriter()) { writers.add(inspector); for (MediaType mediaType : inspector.getSupports()) { Set<MarshallerInspector> inspectors = writersByMediaType.get(mediaType); if (inspectors == null) { inspectors = new ConcurrentSkipListSet<MarshallerInspector>(); writersByMediaType.put(mediaType, inspectors); } inspectors.add(inspector); } } if (inspector.isReader()) { readers.add(inspector); for (MediaType mediaType : inspector.getSupports()) { Set<MarshallerInspector> inspectors = readersByMediaType.get(mediaType); if (inspectors == null) { inspectors = new ConcurrentSkipListSet<MarshallerInspector>(); readersByMediaType.put(mediaType, inspectors); } inspectors.add(inspector); } } } @Override public void deregister(Class<?> marshaller) throws MarshallingException { if (marshaller == null) { log.warn("Cannot register null marshaller"); return; } MarshallerInspector inspector = new MarshallerInspector(marshaller); if (!inspector.isWriter() && !inspector.isReader()) { throw new MarshallingException( "The marshaller registry just supports Writer and Reader for now. You have to implement " + Writer.class.getName() + " or " + Reader.class.getName()); } marshallersByType.remove(marshaller); if (inspector.isWriter()) { writers.remove(inspector); for (MediaType mediaType : inspector.getSupports()) { Set<MarshallerInspector> inspectors = writersByMediaType.get(mediaType); if (inspectors != null) { inspectors.remove(inspector); } } } if (inspector.isReader()) { readers.remove(inspector); for (MediaType mediaType : inspector.getSupports()) { Set<MarshallerInspector> inspectors = readersByMediaType.get(mediaType); if (inspectors != null) { inspectors.remove(inspector); } } } } @Override public <T> Writer<T> getWriter(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype) { Set<MarshallerInspector> candidates = writersByMediaType.get(mediatype); return (Writer<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, writers, false); } @Override public <T> Writer<T> getUniqueWriter(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype) { Set<MarshallerInspector> candidates = writersByMediaType.get(mediatype); return (Writer<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, writers, true); } @Override @SuppressWarnings("unchecked") public <T> Collection<Writer<T>> getAllWriters(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype) { Set<MarshallerInspector> candidates = writersByMediaType.get(mediatype); Collection<Marshaller<T>> founds = getAllMarshallers(ctx, marshalledClazz, genericType, mediatype, candidates, writers); return (Collection<Writer<T>>) (Collection<?>) founds; } @Override public <T> Writer<T> getWriter(RenderingContext ctx, Class<T> marshalledClazz, MediaType mediatype) { return getWriter(ctx, marshalledClazz, marshalledClazz, mediatype); } @Override public <T> Reader<T> getReader(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype) { Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype); return (Reader<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, readers, false); } @Override public <T> Reader<T> getUniqueReader(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype) { Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype); return (Reader<T>) getMarshaller(ctx, marshalledClazz, genericType, mediatype, candidates, readers, true); } @Override @SuppressWarnings("unchecked") public <T> Collection<Reader<T>> getAllReaders(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype) { Set<MarshallerInspector> candidates = readersByMediaType.get(mediatype); Collection<Marshaller<T>> founds = getAllMarshallers(ctx, marshalledClazz, genericType, mediatype, candidates, readers); return (Collection<Reader<T>>) (Collection<?>) founds; } @Override public <T> Reader<T> getReader(RenderingContext ctx, Class<T> marshalledClazz, MediaType mediatype) { return getReader(ctx, marshalledClazz, marshalledClazz, mediatype); } public <T> Marshaller<T> getMarshaller(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype, Set<MarshallerInspector> customs, Set<MarshallerInspector> wildcards, boolean forceInstantiation) { if (customs != null) { Marshaller<T> found = searchCandidate(ctx, marshalledClazz, genericType, mediatype, customs, forceInstantiation); if (found != null) { return found; } } return searchCandidate(ctx, marshalledClazz, genericType, mediatype, wildcards, forceInstantiation); } public <T> Collection<Marshaller<T>> getAllMarshallers(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype, Set<MarshallerInspector> customs, Set<MarshallerInspector> wildcards) { Map<MarshallerInspector, Marshaller<T>> result = new HashMap<MarshallerInspector, Marshaller<T>>(); if (customs != null) { result.putAll(searchAllCandidates(ctx, marshalledClazz, genericType, mediatype, customs)); } result.putAll(searchAllCandidates(ctx, marshalledClazz, genericType, mediatype, wildcards)); return result.values(); } @SuppressWarnings("unchecked") private <T> Marshaller<T> searchCandidate(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype, Set<MarshallerInspector> candidates, boolean forceInstantiation) { for (MarshallerInspector inspector : candidates) { // checks the managed class is compatible if (inspector.getMarshalledType().isAssignableFrom(marshalledClazz)) { // checks the generic type is compatible if (genericType == null || marshalledClazz.equals(inspector.getGenericType()) || TypeUtils.isAssignable(genericType, inspector.getGenericType())) { Marshaller<T> marshaller = null; if (forceInstantiation) { marshaller = (Marshaller<T>) inspector.getNewInstance(ctx, false); } else { marshaller = inspector.getInstance(ctx); } // checks the marshaller accepts the request if (marshaller.accept(marshalledClazz, genericType, mediatype)) { return marshaller; } } } } return null; } private <T> Map<MarshallerInspector, Marshaller<T>> searchAllCandidates(RenderingContext ctx, Class<T> marshalledClazz, Type genericType, MediaType mediatype, Set<MarshallerInspector> candidates) { Map<MarshallerInspector, Marshaller<T>> result = new HashMap<MarshallerInspector, Marshaller<T>>(); for (MarshallerInspector inspector : candidates) { // checks the managed class is compatible if (inspector.getMarshalledType().isAssignableFrom(marshalledClazz)) { // checks the generic type is compatible if (genericType == null || marshalledClazz.equals(inspector.getGenericType()) || TypeUtils.isAssignable(genericType, inspector.getGenericType())) { // checks the marshaller accepts the request Marshaller<T> marshaller = inspector.getInstance(ctx); if (marshaller.accept(marshalledClazz, genericType, mediatype)) { result.put(inspector, marshaller); } } } } return result; } @Override public <T> T getInstance(RenderingContext ctx, Class<T> marshallerClass) { MarshallerInspector inspector = marshallersByType.get(marshallerClass); if (inspector == null) { inspector = new MarshallerInspector(marshallerClass); } return inspector.getInstance(ctx); } @Override public <T> T getUniqueInstance(RenderingContext ctx, Class<T> marshallerClass) { MarshallerInspector inspector = marshallersByType.get(marshallerClass); if (inspector == null) { inspector = new MarshallerInspector(marshallerClass); } @SuppressWarnings("unchecked") T result = (T) inspector.getNewInstance(ctx, false); return result; } @Override public void clear() { marshallersByType.clear(); writersByMediaType.clear(); writers.clear(); readersByMediaType.clear(); readers.clear(); } }