org.alfresco.repo.remoteconnector.LocalWebScriptConnectorServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.alfresco.repo.remoteconnector.LocalWebScriptConnectorServiceImpl.java

Source

/*
 * #%L
 * Alfresco Remote API
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.remoteconnector;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import net.sf.acegisecurity.Authentication;

import org.alfresco.service.cmr.remoteconnector.RemoteConnectorClientException;
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorResponse;
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorServerException;
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.web.scripts.BaseWebScriptTest;
import org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory;
import org.alfresco.repo.web.scripts.servlet.LocalTestRunAsAuthenticatorFactory.LocalTestRunAsAuthenticator;
import org.alfresco.repo.web.scripts.servlet.BasicHttpAuthenticatorFactory.BasicHttpAuthenticator;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.springframework.extensions.webscripts.Authenticator;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.TestWebScriptServer;
import org.springframework.extensions.webscripts.TestWebScriptServer.Request;
import org.springframework.extensions.webscripts.TestWebScriptServer.Response;
import org.springframework.extensions.webscripts.servlet.ServletAuthenticatorFactory;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;
import org.springframework.extensions.webscripts.servlet.WebScriptServletResponse;

/**
 * Testing implementation of {@link RemoteConnectorService} which talks to
 *  the local webscripts only
 *  
 * @author Nick Burch
 * @since 4.0.2
 */
public class LocalWebScriptConnectorServiceImpl implements RemoteConnectorService {
    /**
     * The logger
     */
    private static Log logger = LogFactory.getLog(LocalWebScriptConnectorServiceImpl.class);

    public static final String LOCAL_BASE_URL = "http://localhost:8080/alfresco/";
    private static final String SERVICE_URL = "/service/";
    public static final String LOCAL_SERVICE_URL = LOCAL_BASE_URL + "service/";

    private WebScriptHelper helper;
    private LocalAndRemoteAuthenticator auth;

    public LocalWebScriptConnectorServiceImpl(BaseWebScriptTest webScriptTest) throws Exception {
        this.helper = new WebScriptHelper(webScriptTest);
        this.auth = new LocalAndRemoteAuthenticator(webScriptTest);
    }

    /**
     * Builds a new Request object
     */
    public RemoteConnectorRequest buildRequest(String url, String method) {
        // Ensure we accept this URL
        if (url.startsWith(LOCAL_BASE_URL)) {
            // Good, that's probably us, make it a relative url
            url = url.substring(LOCAL_BASE_URL.length() - 1);

            // Make sure it's a service one
            if (url.startsWith(SERVICE_URL)) {
                // Strip off and use
                url = url.substring(SERVICE_URL.length() - 1);
            } else {
                throw new IllegalArgumentException("Only /service/ local URLs are supported, can't handle " + url);
            }
        } else {
            throw new IllegalArgumentException("Not a local URL: " + url);
        }

        // Build and return
        return new RemoteConnectorRequestImpl(url, method);
    }

    /**
     * Builds a new Request object, using HttpClient method descriptions
     */
    public RemoteConnectorRequest buildRequest(String url, Class<? extends HttpMethodBase> method) {
        // Get the method name
        String methodName;
        try {
            HttpMethodBase httpMethod = method.getConstructor(String.class).newInstance(url);
            methodName = httpMethod.getName();
        } catch (Exception e) {
            throw new AlfrescoRuntimeException("Error identifying method name", e);
        }

        // Build and return
        return buildRequest(url, methodName);
    }

    /**
     * Executes the specified request, and return the response
     */
    public RemoteConnectorResponse executeRequest(RemoteConnectorRequest request) throws IOException,
            AuthenticationException, RemoteConnectorClientException, RemoteConnectorServerException {
        // Convert the request object
        RemoteConnectorRequestImpl requestImpl = (RemoteConnectorRequestImpl) request;
        Request req = new Request(request.getMethod(), request.getURL());
        req.setType(request.getContentType());

        if (request.getRequestBody() != null) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            requestImpl.getRequestBody().writeRequest(baos);
            req.setBody(baos.toByteArray());
        }

        // Log
        if (logger.isInfoEnabled())
            logger.info("Performing local " + request.getMethod() + " request to " + request.getURL());

        // Capture the user details, as they may be changed during the request processing
        Authentication fullAuth = AuthenticationUtil.getFullAuthentication();
        String runAsUser = AuthenticationUtil.getRunAsUser();

        // If they've specified Authentication details in the request, clear our security context
        //  and switch to that user, to avoid our context confusing the real request
        Header authHeader = null;
        Map<String, String> headers = new HashMap<String, String>();
        for (Header header : request.getRequestHeaders()) {
            if (header.getName().equals("Authorization")) {
                authHeader = header;
            }
            headers.put(header.getName(), header.getValue());
        }
        if (authHeader != null) {
            AuthenticationUtil.clearCurrentSecurityContext();
            if (logger.isDebugEnabled())
                logger.debug("HTTP Authorization found for the request, clearing security context, Auth is "
                        + authHeader);
        }
        req.setHeaders(headers);

        // Execute the request against the WebScript Test Framework
        Response resp;
        try {
            resp = helper.sendRequest(req, -1);
        } catch (Exception e) {
            throw new AlfrescoRuntimeException("Problem requesting", e);
        }

        // Reset the user details, now we're done performing the request
        AuthenticationUtil.setFullAuthentication(fullAuth);
        if (runAsUser != null && !runAsUser.equals(fullAuth.getName())) {
            AuthenticationUtil.setRunAsUser(runAsUser);
        }

        // Log
        if (logger.isInfoEnabled())
            logger.info("Response to request was " + resp.getStatus() + " - " + resp);

        // Check the status for specific typed exceptions
        if (resp.getStatus() == Status.STATUS_UNAUTHORIZED) {
            throw new AuthenticationException("Not Authorized to access this resource");
        }
        if (resp.getStatus() == Status.STATUS_FORBIDDEN) {
            throw new AuthenticationException("Forbidden to access this resource");
        }

        // Check for failures where we don't care about the response body
        if (resp.getStatus() >= 500 && resp.getStatus() <= 599) {
            throw new RemoteConnectorServerException(resp.getStatus(), "(not available)");
        }

        // Convert the response into our required format
        String charset = null;
        String contentType = resp.getContentType();
        if (contentType != null && contentType.contains("charset=")) {
            int splitAt = contentType.indexOf("charset=") + "charset=".length();
            charset = contentType.substring(splitAt);
        }

        InputStream body = new ByteArrayInputStream(resp.getContentAsByteArray());
        Header[] respHeaders = new Header[0]; // TODO Can't easily get the list...

        RemoteConnectorResponse response = new RemoteConnectorResponseImpl(request, contentType, charset,
                resp.getStatus(), respHeaders, body);

        // If it's a client error, let them know what went wrong
        if (resp.getStatus() >= 400 && resp.getStatus() <= 499) {
            throw new RemoteConnectorClientException(resp.getStatus(), "(not available)", response);
        }

        // Otherwise return the response for processing
        return response;
    }

    /**
     * Executes the given request, requesting a JSON response, and
     *  returns the parsed JSON received back
     *  
     * @throws ParseException If the response is not valid JSON
     */
    public JSONObject executeJSONRequest(RemoteConnectorRequest request)
            throws ParseException, IOException, AuthenticationException {
        return RemoteConnectorServiceImpl.doExecuteJSONRequest(request, this);
    }

    private static class WebScriptHelper {
        private BaseWebScriptTest test;
        private Method sendRequest;

        private WebScriptHelper(BaseWebScriptTest test) throws Exception {
            this.test = test;

            sendRequest = BaseWebScriptTest.class.getDeclaredMethod("sendRequest", Request.class, Integer.TYPE);
            sendRequest.setAccessible(true);
        }

        private Response sendRequest(Request request, int expectedStatus) throws Exception {
            return (Response) sendRequest.invoke(test, request, expectedStatus);
        }
    }

    /**
     * A wrapper around {@link BasicHttpAuthenticator}, which uses the
     *  Authentication Context if present, otherwise HTTP Auth
     */
    private static class LocalAndRemoteAuthenticator implements ServletAuthenticatorFactory {
        private BasicHttpAuthenticatorFactory httpAuthFactory;

        private LocalAndRemoteAuthenticator(BaseWebScriptTest test) throws Exception {
            // Get the test server
            Method getServer = BaseWebScriptTest.class.getDeclaredMethod("getServer");
            getServer.setAccessible(true);
            TestWebScriptServer server = (TestWebScriptServer) getServer.invoke(test);

            // Grab the real auth factory from the context
            httpAuthFactory = (BasicHttpAuthenticatorFactory) server.getApplicationContext()
                    .getBean("webscripts.authenticator.basic");

            // Wire us into the test
            test.setCustomAuthenticatorFactory(this);
            server.setServletAuthenticatorFactory(this);
        }

        @Override
        public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res) {
            // Do we have current details?
            if (AuthenticationUtil.getFullyAuthenticatedUser() != null) {
                // There are already details existing
                // Allow these to be kept and used
                String fullUser = AuthenticationUtil.getFullyAuthenticatedUser();
                logger.debug("Existing Authentication found, remaining as " + fullUser);
                return new LocalTestRunAsAuthenticator(fullUser);
            }

            // Fall back to the http auth one
            logger.debug("No existing Authentication found, using regular HTTP Auth");
            return httpAuthFactory.create(req, res);
        }
    }
}