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

org.bndly.rest.controller.impl.ControllerResourceRegistryImpl 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.rest.api.DefaultCharacterEncodingProvider;
import org.bndly.rest.controller.api.ControllerBinding;
import org.bndly.rest.controller.api.ControllerResourceRegistry;
import org.bndly.rest.controller.api.DELETE;
import org.bndly.rest.controller.api.GET;
import org.bndly.rest.api.HTTPMethod;
import org.bndly.rest.controller.api.POST;
import org.bndly.rest.controller.api.PUT;
import org.bndly.rest.controller.api.Path;
import org.bndly.rest.api.ResourceURI;
import org.bndly.rest.api.ResourceURIParser;
import org.bndly.rest.controller.api.ControllerResourceRegistryListener;
import java.lang.annotation.Annotation;
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 org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(service = ControllerResourceRegistry.class)
public class ControllerResourceRegistryImpl implements ControllerResourceRegistry {

	private static final Logger LOG = LoggerFactory.getLogger(ControllerResourceRegistryImpl.class);
	
	@Reference
	private DefaultCharacterEncodingProvider defaultCharacterEncodingProvider;
	
	private interface DestructionLogic {
		public void destruct();
	}
	
	private class KnownController {
		private final Object controller;
		private final String baseUri;
		private final List destructionLogic = new ArrayList<>();
		private final List bindings = new ArrayList<>();

		public KnownController(Object controller, String baseUri) {
			this.controller = controller;
			this.baseUri = baseUri;
		}

		public Object getController() {
			return controller;
		}

		public List getDestructionLogic() {
			return destructionLogic;
		}

		public List getBindings() {
			return bindings;
		}

		public String getBaseUri() {
			return baseUri;
		}
		
	}
	
	private final List knownControllers = new ArrayList<>();
	private final List bindings = new ArrayList<>();
	private final PathNode controllerTree = new PathNode(null);
	private final ReadWriteLock controllerLock = new ReentrantReadWriteLock();
	
	private final List listeners = new ArrayList<>();
	private final ReadWriteLock listenersLock = new ReentrantReadWriteLock();

	@Reference(
			cardinality = ReferenceCardinality.MULTIPLE,
			policy = ReferencePolicy.DYNAMIC,
			service = ControllerResourceRegistryListener.class,
			bind = "addControllerResourceRegistryListener",
			unbind = "removeControllerResourceRegistryListener"
	)
	@Override
	public void addControllerResourceRegistryListener(ControllerResourceRegistryListener listener) {
		if (listener != null) {
			listenersLock.writeLock().lock();
			try {
				listeners.add(0, listener);
			} finally {
				listenersLock.writeLock().unlock();
			}
			listener.boundTo(this);
		}
	}

	@Override
	public void removeControllerResourceRegistryListener(ControllerResourceRegistryListener listener) {
		if (listener != null) {
			listenersLock.writeLock().lock();
			try {
				listeners.remove(listener);
			} finally {
				listenersLock.writeLock().unlock();
			}
			listener.unboundFrom(this);
		}
	}

	/*
	 Idee: Controller Baum aufbauen um dann schnell einen Aufruf an den richtigen 
	 Controller weiterleiten zu können.
	 */
	@Override
	public void deploy(Object controller) {
		deploy(controller, null);
	}

	@Override
	public void deploy(Object controller, String baseURI) {
		controllerLock.writeLock().lock();
		try {
			if (baseURI != null) {
				LOG.info("deploying controller {} with base uri {}", controller.getClass().getName(), baseURI);
			} else {
				LOG.info("deploying controller {}", controller.getClass().getName());
			}
			KnownController kc = new KnownController(controller, baseURI);
			buildControllerBindings(controller.getClass(), kc, baseURI);
			knownControllers.add(kc);
			listenersLock.readLock().lock();
			try {
				for (ControllerResourceRegistryListener controllerResourceRegistryListener : listeners) {
					controllerResourceRegistryListener.deployedController(controller, controller.getClass(), kc.getBindings(), baseURI, this);
				}
			} finally {
				listenersLock.readLock().unlock();
			}
		} catch (DoubleBoundControllerException e) {
			for (KnownController knownController : knownControllers) {
				if (knownController.getController() == controller) {
					return;
				}
			}
			LOG.error("deployment of controller {} with base uri {} failed", controller.getClass().getName(), baseURI, e);
			throw e;
		} catch (Exception e) {
			LOG.error("deployment of controller {} with base uri {} failed", controller.getClass().getName(), baseURI, e);
		} finally {
			controllerLock.writeLock().unlock();
		}
	}

	@Override
	public ControllerBinding resolveBindingForResourceURI(ResourceURI uri, HTTPMethod httpMethod) {
		PathNode node = findPathNodeForUri(controllerTree, 0, uri);
		if (node == null) {
			// path could not be found in nodes
			return null;
		}
		SelectorNode sNode = findSelectorNodeInPathNodeForResourceURI(node, uri);
		if (sNode == null) {
			return null;
		}
		ExtensionNode eNode = findExtensionNodeInSelectorNodeForResourceURI(sNode, uri);
		if (eNode == null) {
			return null;
		}
		MethodNode mNode = findMethodNodeInExtensionNode(eNode, httpMethod);
		if (mNode == null) {
			return null;
		}
		return mNode.getBinding();
	}

	private boolean controllerBindingAppliesToUri(ControllerBinding controllerBinding, ResourceURI uri) {
		ResourceURI pattern = controllerBinding.getResourceURIPattern();
		List patternSelectors = pattern.getSelectors();
		List selectors = uri.getSelectors();
		if (patternSelectors != null) {
			if (selectors == null) {
				return false;
			}
			if (patternSelectors.size() != selectors.size()) {
				return false;
			}
			for (int i = 0; i < selectors.size(); i++) {
				ResourceURI.Selector selector = selectors.get(i);
				ResourceURI.Selector patternSelector = patternSelectors.get(i);
				if (!selector.getName().equals(patternSelector.getName())) {
					return false;
				}
			}
		}
		ResourceURI.Extension patternExtension = controllerBinding.getResourceURIPattern().getExtension();
		ResourceURI.Extension ext = uri.getExtension();
		if (patternExtension != null) {
			if (ext == null) {
				return false;
			}
			return ext.getName().equalsIgnoreCase(patternExtension.getName());
		} else {
			if (ext != null) {
				return false;
			}
		}
		return true;
	}

	private void buildControllerBindings(Class controllerType, KnownController knownController, String baseURI) {
		Object controller = knownController.getController();
		Path classPath = controllerType.getAnnotation(Path.class);
		for (Method method : controllerType.getMethods()) {
			Path methodPath = method.getAnnotation(Path.class);
			// if there is no method path, the method should at leas have a HTTP method assigned
			List httpMethods = getHttpMethodForJavaMethod(method);
			if (httpMethods == null) {
				continue;
			}
			String pathToMethod = joinPathElements(baseURI, classPath, methodPath);
			if (pathToMethod != null) {
				// iterate the path to method and split it into static and variable elements
				ResourceURI uri = new ResourceURIParser(defaultCharacterEncodingProvider.createPathCoder(), pathToMethod).parse().getResourceURI();
				for (Annotation httpMethodAnnotation : httpMethods) {
					ControllerBinding binding = createBinding(controllerType, method, controller, baseURI, httpMethodAnnotation, uri);
					PathNode pn = appendToPathNode(0, controllerTree, binding, uri);
					SelectorNode sn = appendToSelectorNode(0, pn.getSelectorNode(), binding, uri);
					ExtensionNode en = appendToExtensionNode(sn.getExtensionNode(), binding, uri);
					final MethodNode mn = appendToMethodNode(en.getMethodNode(), binding);
					knownController.getDestructionLogic().add(new DestructionLogic() {

						@Override
						public void destruct() {
							mn.removeFromTree();
						}
					});
					knownController.getBindings().add(binding);
					controllerLock.writeLock().lock();
					try {
						bindings.add(binding);
					} finally {
						controllerLock.writeLock().unlock();
					}
				}
			}
		}
	}

	private SelectorNode appendToSelectorNode(int currentIndex, SelectorNode selectorNode, ControllerBinding binding, ResourceURI uri) {
		if (uri.getSelectors() != null) {
			if (currentIndex < uri.getSelectors().size()) {
				ResourceURI.Selector currentSelector = uri.getSelectors().get(currentIndex);
				if (selectorNode.getChildren() != null) {
					for (SelectorNode sn : selectorNode.getChildren()) {
						if (sn.getName().equals(currentSelector.getName())) {
							return appendToSelectorNode(currentIndex + 1, sn, binding, uri);
						}
					}
				}
				SelectorNode sn = new SelectorNode(selectorNode, currentSelector.getName());
				selectorNode.addChild(sn);
				return appendToSelectorNode(currentIndex + 1, sn, binding, uri);
			} else {
				return selectorNode;
			}
		} else {
			return selectorNode;
		}
	}

	private ExtensionNode appendToExtensionNode(ExtensionNode extensionNode, ControllerBinding binding, ResourceURI uri) {
		if (uri.getExtension() != null) {
			if (extensionNode.getChildren() != null) {
				for (ExtensionNode m : extensionNode.getChildren()) {
					if (m.getName().equals(uri.getExtension().getName())) {
						return m;
					}
				}
			}
			ExtensionNode en = new ExtensionNode(extensionNode, uri.getExtension().getName());
			extensionNode.addChild(en);
			return en;
		} else {
			return extensionNode;
		}
	}

	private MethodNode appendToMethodNode(MethodNode methodNode, ControllerBinding binding) {
		if (methodNode.getChildren() != null) {
			for (MethodNode m : methodNode.getChildren()) {
				if (m.getHttpMethod().equals(binding.getHTTPMethod())) {
					throw new DoubleBoundControllerException();
				}
			}
		}
		MethodNode mn = new MethodNode(methodNode, binding.getHTTPMethod(), binding);
		methodNode.addChild(mn);
		return mn;
	}

	private PathNode appendToPathNode(int currentIndex, PathNode currentNode, ControllerBinding binding, ResourceURI uri) {
		ResourceURI.Path p = uri.getPath();
		if (p != null && currentIndex < p.getElements().size()) {
			String currentElement = p.getElements().get(currentIndex);
			boolean currentElementIsVariable = PathNode.isVariable(currentElement);
			PathNode variableNode = null;
			if (currentNode.getChildren() != null) {
				for (PathNode pathNode : currentNode.getChildren()) {
					if (pathNode.isVariable()) {
						variableNode = pathNode;
					}
					if (pathNode.getElementName().equals(currentElement)) {
						return appendToPathNode(currentIndex + 1, pathNode, binding, uri);
					}
				}
			}
			PathNode newNode;
			if (currentElementIsVariable) {
				if (variableNode == null) {
					newNode = new PathNode(currentNode, currentElement);
				} else {
					return appendToPathNode(currentIndex + 1, variableNode, binding, uri);
				}
			} else {
				newNode = new PathNode(currentNode, currentElement);
			}
			currentNode.addChild(newNode);
			return appendToPathNode(currentIndex + 1, newNode, binding, uri);
		} else {
			return currentNode;
		}
	}

	private ControllerBinding createBinding(final Class controllerType, final Method method, final Object controller, String baseURI, Annotation httpMethodAnnotation, final ResourceURI uri) {
		return new ControllerBindingImpl(controllerType, controller, baseURI, uri, method, httpMethodAnnotation);
	}

	private String joinPathElements(String baseURI, Path classPath, Path methodPath) {
		if (classPath == null && methodPath == null) {
			return assertStartsWithSlash(baseURI);
		}
		String c = null;
		if (classPath != null) {
			c = classPath.value();
		}
		String m = null;
		if (methodPath != null) {
			m = methodPath.value();
		}
		if (c != null) {
			c = assertStartsWithSlash(c);
		}
		if (m != null) {
			m = assertStartsWithSlash(m);
		}

		StringBuffer sb = new StringBuffer();
		if (baseURI == null && c == null && m == null) {
			sb.append('/');
		} else {
			if (baseURI != null) {
				sb.append(assertStartsWithSlash(baseURI));
			}
			if (c != null) {
				sb.append(c);
			}
			if (m != null) {
				sb.append(m);
			}
		}
		return sb.toString();
	}

	private String assertStartsWithSlash(String in) {
		if (in == null || in.length() == 0) {
			return null;
		}
		if (in.charAt(0) != '/') {
			return "/" + in;
		}
		return in;
	}

	private List getHttpMethodForJavaMethod(Method method) {
		List methods = null;
		methods = testMethodFor(GET.class, method, methods);
		methods = testMethodFor(PUT.class, method, methods);
		methods = testMethodFor(POST.class, method, methods);
		methods = testMethodFor(DELETE.class, method, methods);
		return methods;
	}

	private List testMethodFor(Class annotation, Method method, List listOfFound) {
		Annotation a = method.getAnnotation(annotation);
		if (a != null) {
			if (listOfFound == null) {
				listOfFound = new ArrayList<>();
			}
			listOfFound.add(a);
		}
		return listOfFound;
	}

	@Override
	public boolean isVariableElement(String element) {
		int l = element.length();
		return l > 2 && element.charAt(0) == '{' && element.charAt(l - 1) == '}';
	}

	@Override
	public boolean isVariableElementOfName(String element, String name) {
		if (!isVariableElement(element)) {
			return false;
		}
		return element.substring(1, element.length() - 1).equals(name);
	}

	@Override
	public void undeploy(Object controller) {
		controllerLock.writeLock().lock();
		try {
			LOG.info("undeploying controller {}", controller.getClass().getName());
			List toRemove = new ArrayList<>();
			for (KnownController knownController : knownControllers) {
				if (knownController.getController() == controller) {
					toRemove.add(knownController);
					bindings.removeAll(knownController.getBindings());
					for (DestructionLogic destructionLogic : knownController.getDestructionLogic()) {
						// remove the controllerBinding from the lookup tree
						destructionLogic.destruct();
					}
					listenersLock.readLock().lock();
					try {
						for (ControllerResourceRegistryListener controllerResourceRegistryListener : listeners) {
							controllerResourceRegistryListener.undeployedController(
								controller, controller.getClass(), knownController.getBindings(), knownController.getBaseUri(), this
							);
						}
					} finally {
						listenersLock.readLock().unlock();
					}
				}
			}
			if (toRemove.isEmpty()) {
				LOG.warn("undeploying controller {} did not affect anything, because it was not a known controller.", controller.getClass().getName());
			}
			knownControllers.removeAll(toRemove);
		} catch (RuntimeException e) {
			LOG.error("failed to undeploy controller: " + e.getMessage(), e);
			throw e;
		} finally {
			controllerLock.writeLock().unlock();
		}
	}

	@Override
	public Iterator listDeployedControllerBindings() {
		return bindings.iterator();
	}

	private PathNode findPathNodeForUri(PathNode currentNode, int currentIndex, ResourceURI uri) {
		ResourceURI.Path p = uri.getPath();
		if (currentNode != null && p != null && currentIndex < p.getElements().size()) {
			String currentElementName = p.getElements().get(currentIndex);
			PathNode subNode = null;
			PathNode variableNode = currentNode.isVariable() ? currentNode : null;
			if (currentNode.getChildren() != null) {
				for (PathNode pathNode : currentNode.getChildren()) {
					if (pathNode.getElementName().equals(currentElementName)) {
						subNode = pathNode;
						break;
					} else if (pathNode.isVariable()) {
						if (variableNode != null && variableNode != currentNode) {
							throw new IllegalStateException("there are two variable nodes with the same parent");
						}
						variableNode = pathNode;
					}
				}
			}
			if (subNode == null && variableNode != null) {
				subNode = variableNode;
			}
			return findPathNodeForUri(subNode, currentIndex + 1, uri);
		}
		return currentNode;
	}

	private SelectorNode findSelectorNodeInPathNodeForResourceURI(PathNode node, ResourceURI uri) {
		return findSelectorNodeInPathNodeForResourceURI(node.getSelectorNode(), 0, uri);
	}

	private SelectorNode findSelectorNodeInPathNodeForResourceURI(SelectorNode node, int currentIndex, ResourceURI uri) {
		List s = uri.getSelectors();
		if (s != null && currentIndex < s.size()) {
			ResourceURI.Selector current = s.get(currentIndex);
			if (node.getChildren() != null) {
				for (SelectorNode selectorNode : node.getChildren()) {
					if (selectorNode.getName().equals(current.getName())) {
						return findSelectorNodeInPathNodeForResourceURI(selectorNode, currentIndex + 1, uri);
					}
				}
			}
			return node;
		}
		return node;
	}

	private ExtensionNode findExtensionNodeInSelectorNodeForResourceURI(SelectorNode sNode, ResourceURI uri) {
		ResourceURI.Extension ext = uri.getExtension();
		if (ext != null) {
			List children = sNode.getExtensionNode().getChildren();
			ExtensionNode subNode = null;
			ExtensionNode variableExtensionNode = null;
			if (children != null) {
				for (ExtensionNode extensionNode : children) {
					if (extensionNode.getName().equals(ext.getName())) {
						subNode = extensionNode;
						break;
					} else if (extensionNode.isVariable()) {
						variableExtensionNode = extensionNode;
					}
				}
			}
			if (subNode == null && variableExtensionNode != null) {
				return variableExtensionNode;
			} else if (subNode != null) {
				return subNode;
			}
			return sNode.getExtensionNode();
		} else {
			return sNode.getExtensionNode();
		}
	}

	private MethodNode findMethodNodeInExtensionNode(ExtensionNode eNode, HTTPMethod httpMethod) {
		List children = eNode.getMethodNode().getChildren();
		if (children != null) {
			for (MethodNode methodNode : children) {
				if (methodNode.getHttpMethod().equals(httpMethod)) {
					return methodNode;
				}
			}
		}
		return eNode.getMethodNode();
	}
	
	/**
	 * This method returns the internal list of all controller bindings.
	 * @return 
	 */
	public List getBindings() {
		return bindings;
	}

	public void setDefaultCharacterEncodingProvider(DefaultCharacterEncodingProvider defaultCharacterEncodingProvider) {
		this.defaultCharacterEncodingProvider = defaultCharacterEncodingProvider;
	}
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy