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

com.github.dynamicextensionsalfresco.webscripts.AnnotationWebScript Maven / Gradle / Ivy

Go to download

Adds an OSGi container to alfresco repository supporting dynamic code reloading, classpath isolation and a bunch of other useful features

There is a newer version: 3.1.0
Show newest version
package com.github.dynamicextensionsalfresco.webscripts;

import com.github.dynamicextensionsalfresco.webscripts.annotations.Attribute;
import com.github.dynamicextensionsalfresco.webscripts.arguments.HandlerMethodArgumentsResolver;
import com.github.dynamicextensionsalfresco.webscripts.messages.AnnotationWebScriptOutputMessage;
import com.github.dynamicextensionsalfresco.webscripts.resolutions.DefaultResolutionParameters;
import com.github.dynamicextensionsalfresco.webscripts.resolutions.Resolution;
import com.github.dynamicextensionsalfresco.webscripts.resolutions.TemplateResolution;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.extensions.webscripts.*;
import org.springframework.http.*;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;

public class AnnotationWebScript implements WebScript {
	/* Dependencies */

	private HandlerMethodArgumentsResolver argumentsResolver;

	private MessageConverterRegistry messageConverterRegistry;

	/* Configuration */

	private final Description description;

	private final Object handler;

	private final HandlerMethods handlerMethods;

	private final String id;


	/* Main operations */

	public AnnotationWebScript(final Description description, final Object handler,
			final HandlerMethods handlerMethods, final HandlerMethodArgumentsResolver argumentsResolver,
                               final MessageConverterRegistry messageConverterRegistry) {
		Assert.notNull(description, "Description cannot be null.");
		Assert.hasText(description.getId(), "No ID provided in Description.");
		Assert.notNull(handler, "Handler cannot be null.");
		Assert.notNull(handlerMethods, "Methods cannot be null.");

		this.description = description;
		this.handler = handler;
		this.handlerMethods = handlerMethods;
		this.argumentsResolver = argumentsResolver;
		this.messageConverterRegistry = messageConverterRegistry;
		this.id = description.getId();
	}

	public Object getHandler() {
		return handler;
	}

	public HandlerMethods getHandlerMethods() {
		return handlerMethods;
	}

	@Override
	public final void execute(final WebScriptRequest request, final WebScriptResponse response) throws IOException {
		final AnnotationWebScriptRequest annotationRequest = new AnnotationWebScriptRequest(request);
		final AnnotationWebscriptResponse wrappedResponse = new AnnotationWebscriptResponse(response);
		try {
			invokeAttributeHandlerMethods(annotationRequest, wrappedResponse);
			invokeBeforeHandlerMethods(annotationRequest, wrappedResponse);
			final Object returnValue = invokeUriHandlerMethod(annotationRequest, wrappedResponse);
			handleUriMethodReturnValue(handlerMethods, annotationRequest, wrappedResponse, returnValue);
		} catch (final Throwable e) {
			invokeExceptionHandlerMethods(e, annotationRequest, wrappedResponse);
		}
	}

	/*
	 * This method appears to be new in the Web Scripts 1.0.0 API. This implementation does nothing, because we want to
	 * retain backwards-compatibility.
	 */
	@Override
	public void init(final Container container, final Description description) {
	}

	@Override
	public Description getDescription() {
		return description;
	}

	@Override
	public ResourceBundle getResources() {
		/* Not yet supported. */
		return null;
	}

	@Override
	public void setURLModelFactory(final URLModelFactory arg0) {
		/* Not yet implemented. */
	}

	/* Handler operations */

	protected boolean invokeBeforeHandlerMethods(final AnnotationWebScriptRequest request,
			final WebScriptResponse response) {
		for (final Method method : handlerMethods.getBeforeMethods()) {
			method.setAccessible(true);
			final Object[] arguments = getArgumentsResolver().resolveHandlerMethodArguments(method, handler, request,
					response);
			final Object returnValue = ReflectionUtils.invokeMethod(method, handler, arguments);
			if (Boolean.FALSE.equals(returnValue)) {
				return false;
			}
		}
		return true;

	}

	protected void invokeAttributeHandlerMethods(final AnnotationWebScriptRequest request,
			final WebScriptResponse response) {
		for (final Method method : handlerMethods.getAttributeMethods()) {
			method.setAccessible(true);
			final Object[] arguments = getArgumentsResolver().resolveHandlerMethodArguments(method, handler, request,
					response);
			final Object attribute = ReflectionUtils.invokeMethod(method, handler, arguments);
			if (attribute == null) {
				continue;
			}
			final Attribute annotation = AnnotationUtils.findAnnotation(method, Attribute.class);
			final Map model = request.getModel();
			if (StringUtils.hasText(annotation.value())) {
				model.put(annotation.value(), attribute);
			} else {
				String name = method.getName();
				if (name.startsWith("get") && name.length() > 3) {
					name = name.substring(3, 4).toLowerCase() + name.substring(4);
				}
				model.put(name, attribute);
			}
		}
	}

	protected Object invokeUriHandlerMethod(final AnnotationWebScriptRequest request,
			final AnnotationWebscriptResponse response) {
		final Method uriMethod = handlerMethods.getUriMethod();
		final Object[] arguments = argumentsResolver.resolveHandlerMethodArguments(uriMethod, handler, request,
				response);
		uriMethod.setAccessible(true);
		return ReflectionUtils.invokeMethod(uriMethod, handler, arguments);
	}

	@SuppressWarnings("unchecked")
	protected void handleUriMethodReturnValue(HandlerMethods handlerMethods, final AnnotationWebScriptRequest request,
                                              final AnnotationWebscriptResponse response, final Object returnValue) throws Exception {
        Resolution resolution = null;
        if (returnValue instanceof Map) { // returning a map will result in response template by default.
            resolution = new TemplateResolution((Map) returnValue);
        } else if (returnValue instanceof String) { // returning a string will also result in a template response.
            resolution = new TemplateResolution((String)returnValue);
        } else if (returnValue instanceof Resolution) {
            resolution = (Resolution) returnValue;
        }

        if (returnValue instanceof HttpEntity) {
            handleHttpEntityResponse(request, response, (HttpEntity) returnValue);
        }
        /**
         * If the method is annotated with {@link org.springframework.web.bind.annotation.ResponseBody}
         * The response should be serialized automatically by what the request asked in the accept header.
         * If the accept header is not present, the default format value of the method is used.
         * If there is no default format available, an exception is thrown.
         */
        else if (handlerMethods.useResponseBody()){

            // if the webscript returns 'void' we should do nothing.
        	if (handlerMethods.getUriMethod().getReturnType() != Void.TYPE) {
                handleResponseBody(request, response, returnValue);
            }

        }
        /**
         * If no {@link org.springframework.web.bind.annotation.ResponseBody} annotation is present,
         * check if a template needs to be used.
         */
        else if (this.handlerMethods.useResponseTemplate()) {
            final String responseTemplateName = handlerMethods.getResponseTemplateName();
            if (responseTemplateName != null) {
                if (resolution instanceof TemplateResolution) {
                    if (((TemplateResolution) resolution).getTemplate() == null) {
                        ((TemplateResolution) resolution).setTemplate(responseTemplateName);
                    }
                } else if (resolution == null) {
                    resolution = new TemplateResolution(responseTemplateName);
                }
            }
        }

        /**
         *
         */
        if (resolution != null) {
            if (resolution instanceof TemplateResolution) {
                final TemplateResolution templateResolution = (TemplateResolution)resolution;
                final Map model = request.getModel();

                if (templateResolution.getModel() != null && templateResolution.getModel() != model) {
                    model.putAll(templateResolution.getModel());
                }
                templateResolution.setModel(model);
            }

            resolution.resolve(request, response,
                new DefaultResolutionParameters(handlerMethods.getUriMethod(), description, handler)
            );
        }
        else {


		}
    }

    private void handleHttpEntityResponse(AnnotationWebScriptRequest request, AnnotationWebscriptResponse response, HttpEntity returnValue) throws IOException, HttpMediaTypeNotAcceptableException, HttpMediaTypeNotSupportedException {
		if (returnValue == null) {
			return;
		}

		AnnotationWebScriptOutputMessage outputMessage = new AnnotationWebScriptOutputMessage(response);
		// Process status code
		if (returnValue instanceof ResponseEntity) {
			outputMessage.setStatusCode(((ResponseEntity) returnValue).getStatusCode());
		}
		HttpHeaders entityHeaders = returnValue.getHeaders();
		if (!entityHeaders.isEmpty()) {
			outputMessage.getHeaders().putAll(entityHeaders);
		}

		Object body = returnValue.getBody();
		if (body != null) {
			this.handleResponseBody(request, response, returnValue.getBody());
		}
		else {
			// flush headers
			outputMessage.getBody();
		}

	}

    private void handleResponseBody(AnnotationWebScriptRequest request, AnnotationWebscriptResponse response, Object returnValue) throws HttpMediaTypeNotSupportedException, HttpMediaTypeNotAcceptableException, IOException {
        MediaType defaultResponseType = null;
        String defaultResponse = this.getDescription().getDefaultFormat();
        if (defaultResponse != null && !defaultResponse.isEmpty()) {
            defaultResponseType = MediaType.parseMediaType(defaultResponse);
        }

        String[] headerResponseTypes = request.getHeaderValues("Accept"); // multiple accept headers can occur
        Set acceptResponseTypes = new HashSet();
        if (headerResponseTypes != null){
            for(String headerResponseType : headerResponseTypes){

                String[] responses = headerResponseType.split(","); // can also be comma seperated https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
                for (String acceptResponse : responses) {
                    acceptResponseTypes.add(MediaType.parseMediaType(acceptResponse));
                }
            }
        }

        MediaType responseType = null;
        if(defaultResponse == null && acceptResponseTypes.isEmpty()) { // no Content-Type information given anywhere
             // both default and accept cannot be null together
            throw new HttpMediaTypeNotSupportedException(null, this.messageConverterRegistry.getSupportedMediaTypes(), "Unable to convert, mediatype is null");
        }
        else if (acceptResponseTypes.isEmpty()) { // use the default
             responseType = defaultResponseType;
        }
        else { // loop over the set, and select the first that works
            for (MediaType mediaType : acceptResponseTypes){
                if (defaultResponse != null) { // first, check against the default type
                    if (mediaType.isCompatibleWith(defaultResponseType)){
                        responseType = defaultResponseType;
                        break;
                    }
                }

                if (this.messageConverterRegistry.carWrite(returnValue.getClass(), mediaType) != null) {
                    responseType = mediaType;
                    break;
                }

            }
        }

        if (responseType == null) {
            /**
             * When there is no default, and there is no support for the headers, this exception is thrown.
             */
            throw new HttpMediaTypeNotAcceptableException(this.messageConverterRegistry.getSupportedMediaTypes());
        }

        HttpMessageConverter converter = this.messageConverterRegistry.carWrite(returnValue.getClass(), responseType);

        if(converter == null) {
            // unsupported type requested
            List supported = this.messageConverterRegistry.getSupportedMediaTypes();

            /**
             * the {@link Jaxb2RootElementHttpMessageConverter} cannot convert a class
             * if the {@link javax.xml.bind.annotation.XmlRootElement} is missing from the class
             *
             * If this annotation is missing, the can write will return false.
             */
            throw new HttpMediaTypeNotSupportedException(responseType, supported);
        }


        AnnotationWebScriptOutputMessage outputMessage = new AnnotationWebScriptOutputMessage(response);
        converter.write(returnValue, responseType, outputMessage);
    }

    protected void invokeExceptionHandlerMethods(final Throwable exception, final AnnotationWebScriptRequest request,
			final WebScriptResponse response) throws IOException {
		final List exceptionHandlerMethods = handlerMethods.findExceptionHandlers(exception);
		if (exceptionHandlerMethods.isEmpty()) {
			translateException(exception);
		}
		try {
			request.setThrownException(exception);
			for (final Method exceptionHandler : exceptionHandlerMethods) {
				final Object[] arguments = getArgumentsResolver().resolveHandlerMethodArguments(exceptionHandler,
						handler, request, response);
				exceptionHandler.setAccessible(true);
				ReflectionUtils.invokeMethod(exceptionHandler, handler, arguments);
			}
		} catch (final Throwable e) {
			translateException(e);
		} finally {
			request.setThrownException(null);
		}

	}

	/* Utility operations */

	protected void translateException(final Throwable e) throws IOException {
		if (e instanceof IOException) {
			throw (IOException) e;
		} else if (e instanceof RuntimeException) {
			throw (RuntimeException) e;
		} else {
			throw new RuntimeException(e);
		}
	}

	/* Dependencies */

	public void setArgumentsResolver(final HandlerMethodArgumentsResolver handlerMethodArgumentsResolver) {
		Assert.notNull(handlerMethodArgumentsResolver);
		this.argumentsResolver = handlerMethodArgumentsResolver;
	}

	protected HandlerMethodArgumentsResolver getArgumentsResolver() {
		return argumentsResolver;
	}

	/* Equality operations */

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final AnnotationWebScript other = (AnnotationWebScript) obj;
		if (id == null) {
			if (other.id != null) {
				return false;
			}
		} else if (!id.equals(other.id)) {
			return false;
		}
		return true;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy