All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bndly.rest.controller.impl.ControllerResourceProvider Maven / Gradle / Ivy

The newest version!
package org.bndly.rest.controller.impl;

/*-
 * #%L
 * REST Controller Impl
 * %%
 * Copyright (C) 2013 - 2020 Cybercon GmbH
 * %%
 * 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.
 * #L%
 */

import org.bndly.common.converter.api.ConversionException;
import org.bndly.common.converter.api.Converter;
import org.bndly.common.converter.api.ConverterRegistry;
import org.bndly.common.data.io.ReplayableInputStream;
import org.bndly.rest.api.CacheContext;
import org.bndly.rest.api.Context;
import org.bndly.rest.controller.api.ControllerBinding;
import org.bndly.rest.controller.api.ControllerResourceRegistry;
import org.bndly.rest.controller.api.ExceptionMapper;
import org.bndly.rest.controller.api.Meta;
import org.bndly.rest.controller.api.PathParam;
import org.bndly.rest.controller.api.QueryParam;
import org.bndly.rest.api.Resource;
import org.bndly.rest.api.ResourceBuildingException;
import org.bndly.rest.api.ResourceProvider;
import org.bndly.rest.api.ResourceURI;
import org.bndly.rest.controller.api.CacheControl;
import org.bndly.rest.controller.api.EntityParser;
import org.bndly.rest.controller.api.Response;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.xml.bind.annotation.XmlRootElement;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;

/**
 *
 * @author cybercon <[email protected]>
 */
@Component(service = ResourceProvider.class)
public class ControllerResourceProvider implements ResourceProvider {

	@Reference
	private ControllerResourceRegistry controllerResourceRegistry;
	
	private final List entityParsers = new ArrayList<>();
	private final ReadWriteLock entityParsersLock = new ReentrantReadWriteLock();

	@Reference
	private ConverterRegistry converterRegistry;
	
	private final DelegatingExceptionMapper exceptionMapper = new DelegatingExceptionMapper();

	@Activate
	public void activate() {
	}
	
	@Deactivate
	public void deactivate() {
		exceptionMapper.clear();
	}
	
	@Reference(
			bind = "addEntityParser",
			unbind = "removeEntityParser",
			cardinality = ReferenceCardinality.MULTIPLE,
			policy = ReferencePolicy.DYNAMIC,
			service = EntityParser.class
	)
	public void addEntityParser(EntityParser entityParser) {
		if (entityParser != null) {
			entityParsersLock.writeLock().lock();
			try {
				entityParsers.add(entityParser);
			} finally {
				entityParsersLock.writeLock().unlock();
			}
		}
	}

	public void removeEntityParser(EntityParser entityParser) {
		if (entityParser != null) {
			entityParsersLock.writeLock().lock();
			try {
				Iterator iterator = entityParsers.iterator();
				while (iterator.hasNext()) {
					EntityParser next = iterator.next();
					if (next == entityParser) {
						iterator.remove();
					}
				}
			} finally {
				entityParsersLock.writeLock().unlock();
			}
		}
	}
	
	@Reference(
			bind = "addExceptionMapper",
			unbind = "removeExceptionMapper", 
			cardinality = ReferenceCardinality.MULTIPLE,
			policy = ReferencePolicy.DYNAMIC,
			service = ExceptionMapper.class
	)
	public void addExceptionMapper(ExceptionMapper exceptionMapper) {
		if (exceptionMapper != null) {
			this.exceptionMapper.registerMapper(exceptionMapper);
		}
	}

	public void removeExceptionMapper(ExceptionMapper exceptionMapper) {
		if (exceptionMapper != null) {
			this.exceptionMapper.unregisterMapper(exceptionMapper);
		}
	}
	
	@Override
	public boolean supports(Context context) {
		ControllerBinding binding = controllerResourceRegistry.resolveBindingForResourceURI(context.getURI(), context.getMethod());
		return binding != null;
	}

	@Override
	public Resource build(Context context, ResourceProvider provider) throws ResourceBuildingException {
		ControllerBinding binding = controllerResourceRegistry.resolveBindingForResourceURI(context.getURI(), context.getMethod());
		Method method = binding.getMethod();
		// parse the arguments from URI or payload
		Object[] args;
		try {
			args = collectArgumentForInvocationOfBinding(binding, context);
		} catch (IOException e) {
			throw new ResourceBuildingException(context, "could build arguments for controller invocation.");
		}
		try {
			CacheControl cc = method.getAnnotation(CacheControl.class);
			if (cc == null) {
				cc = method.getDeclaringClass().getAnnotation(CacheControl.class);
			}
			if (cc != null) {
				CacheContext cacheContext = context.getCacheContext();
				if (cacheContext != null) {
					if (cc.preventCaching()) {
						cacheContext.preventCache();
					}
					if (cc.maxAge() > -1) {
						cacheContext.setMaxAge(cc.maxAge());
					}
				}
			}
			Object result = method.invoke(binding.getController(), args);
			if (Response.class.isInstance(result)) {
				Object entity = ((Response) result).getEntity();
				if (entity != null) {
					Response resp = (Response) result;
					if (entity.getClass().isAnnotationPresent(XmlRootElement.class)) {
						return new JAXBControllerResource(provider, context.getURI(), binding, resp);
					} else if (InputStream.class.isInstance(entity)) {
						return new StreamWritingResource(provider, context.getURI(), resp);
					} else if (Resource.class.isInstance(entity)) {
						return (Resource) entity;
					}
				}
			} else if (Resource.class.isInstance(result)) {
				return (Resource) result;
			}
			return new ControllerResource(provider, context.getURI(), binding, result);
		} catch (InvocationTargetException ex) {
			Response r = exceptionMapper.toResponse(ex.getTargetException());
			if (r != null) {
				Object entity = r.getEntity();
				if (entity != null && entity.getClass().isAnnotationPresent(XmlRootElement.class)) {
					return new JAXBControllerResource(provider, context.getURI(), binding, r);
				}
			}
			throw new ResourceBuildingException(context, ex);
		} catch (IllegalAccessException | IllegalArgumentException ex) {
			throw new ResourceBuildingException(context, ex);
		}
	}

	public void setControllerResourceRegistry(ControllerResourceRegistry controllerResourceRegistry) {
		this.controllerResourceRegistry = controllerResourceRegistry;
	}

	private Object[] collectArgumentForInvocationOfBinding(ControllerBinding binding, Context context) throws IOException {
		Method method = binding.getMethod();
		Class[] pt = method.getParameterTypes();
		if (pt == null || pt.length == 0) {
			return null;
		}

		Annotation[][] ptAnnotations = method.getParameterAnnotations();
		Object[] args = new Object[pt.length];

		for (int i = 0; i < pt.length; i++) {
			Class parameterType = pt[i];
			parameterType = replacePrimitiveWithAutoboxedType(parameterType);
			Annotation[] parameterAnnotations = ptAnnotations[i];
			boolean found = false;
			for (Annotation annotation : parameterAnnotations) {
				if (PathParam.class.isInstance(annotation)) {
					// convert path parameter
					Converter converter = converterRegistry.getConverter(String.class, parameterType);
					if (converter != null) {

						int indexOfParameterInPath = findIndexOfPathParameter(((PathParam) annotation).value(), binding.getResourceURIPattern());
						// TODO: find parameters in the path
						String pathParameter = null;
						if (indexOfParameterInPath > -1) {
							pathParameter = context.getURI().getPath().getElements().get(indexOfParameterInPath);
						}

						try {
							args[i] = converter.convert(pathParameter);
						} catch (ConversionException ex) {
							throw new IllegalArgumentException("could not convert path parameter", ex);
						}
					}
					found = true;
				} else if (QueryParam.class.isInstance(annotation)) {
					// convert query parameter
					QueryParam queryParamAnnotation = ((QueryParam) annotation);
					String name = queryParamAnnotation.value();
					if (queryParamAnnotation.asSelector()) {
						List selectors = context.getURI().getSelectors();
						if (selectors != null) {
							for (ResourceURI.Selector selector : selectors) {
								if (selector.getName().startsWith(name)) {
									try {
										String raw = selector.getName().substring(name.length());
										found = convertRawParameter(raw, parameterType, args, i);
										if (found) {
											break;
										}
									} catch (ConversionException ex) {
										// lets try the query parameters
									}
								}
							}
						}
					}
					if (!found) {
						try {
							found = getParameterFromRequestParameter(context, name, parameterType, args, i);
						} catch (ConversionException ex2) {
							throw new IllegalArgumentException("could not convert query parameter " + name, ex2);
						}
					}
					found = true;
				} else if (Meta.class.isInstance(annotation)) {
					// provide meta data of execution
					if (Context.class.isAssignableFrom(parameterType)) {
						args[i] = context;
					} else if (ResourceURI.class.isAssignableFrom(parameterType)) {
						args[i] = context.getURI();
					} else if (ResourceProvider.class.isAssignableFrom(parameterType)) {
						args[i] = this;
					}
					found = true;
				}
			}
			if (found) {
				continue;
			}
			
			args[i] = buildParameterFromPayload(context, parameterType);
		}

		return args;
	}
	
	private boolean getParameterFromRequestParameter(Context context, String name, Class parameterType, Object[] args, int i) throws ConversionException {
		ResourceURI.QueryParameter param = context.getURI().getParameter(name);
		if (param != null) {
			String v = param.getValue();
			return convertRawParameter(v, parameterType, args, i);
		}
		return false;
	}

	private boolean convertRawParameter(String raw, Class parameterType, Object[] args, int i) throws ConversionException {
		Converter converter = converterRegistry.getConverter(String.class, parameterType);
		if (converter != null) {
			args[i] = converter.convert(raw);
			return true;
		}
		return false;
	}

	private Object buildParameterFromPayload(Context context, Class parameterType) throws IOException, IllegalArgumentException {
		// if it is no annotated parameter, we assume it is a payload
		ReplayableInputStream is = context.getInputStream();
		if (context.getInputContentType() == null) {
			throw new IllegalArgumentException("can not parse content without content-type header.");
		}
		entityParsersLock.readLock().lock();
		try {
			Iterator it = entityParsers.iterator();
			while (it.hasNext()) {
				EntityParser parser = it.next();
				String inputCtName = context.getInputContentType().getName();
				if (inputCtName != null) {
					if (inputCtName.startsWith(parser.getSupportedContentType().getName())) {
						return parser.parse(is, parameterType);
					}
				}
			}
		} finally {
			entityParsersLock.readLock().unlock();
		}
		throw new IllegalArgumentException("unsupported content type: " + context.getInputContentType().getName());
	}

	private Class replacePrimitiveWithAutoboxedType(Class parameterType) {
		if (parameterType.isPrimitive()) {
			if (byte.class.equals(parameterType)) {
				parameterType = Byte.class;
			} else if (short.class.equals(parameterType)) {
				parameterType = Short.class;
			} else if (int.class.equals(parameterType)) {
				parameterType = Integer.class;
			} else if (long.class.equals(parameterType)) {
				parameterType = Long.class;
			} else if (float.class.equals(parameterType)) {
				parameterType = Float.class;
			} else if (double.class.equals(parameterType)) {
				parameterType = Double.class;
			} else if (char.class.equals(parameterType)) {
				parameterType = Character.class;
			}
		}
		return parameterType;
	}

	private int findIndexOfPathParameter(String name, ResourceURI resourceURIPattern) {
		ResourceURI.Path p = resourceURIPattern.getPath();
		if (p != null) {
			for (int i = 0; i < p.getElements().size(); i++) {
				String element = p.getElements().get(i);
				if (controllerResourceRegistry.isVariableElementOfName(element, name)) {
					return i;
				}
			}
		}
		return -1;
	}
	
	public void setConverterRegistry(ConverterRegistry converterRegistry) {
		this.converterRegistry = converterRegistry;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy