mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged BRANCHES/DEV/V4.0-BUG-FIX to HEAD:
35637: RemoteCredentialsService and RemoteAlfrescoTicketService, with tests git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@35639 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
package org.alfresco.repo.remoteconnector;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
|
||||
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
|
||||
import org.apache.commons.httpclient.Header;
|
||||
import org.apache.commons.httpclient.HttpMethodBase;
|
||||
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
|
||||
import org.apache.commons.httpclient.methods.DeleteMethod;
|
||||
import org.apache.commons.httpclient.methods.GetMethod;
|
||||
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
|
||||
import org.apache.commons.httpclient.methods.PostMethod;
|
||||
import org.apache.commons.httpclient.methods.PutMethod;
|
||||
import org.apache.commons.httpclient.methods.RequestEntity;
|
||||
import org.apache.commons.httpclient.methods.StringRequestEntity;
|
||||
|
||||
/**
|
||||
* Helper wrapper around a Remote Request, to be performed by the
|
||||
* {@link RemoteConnectorService}.
|
||||
*
|
||||
* @author Nick Burch
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public class RemoteConnectorRequestImpl implements RemoteConnectorRequest
|
||||
{
|
||||
public static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||
|
||||
private final String url;
|
||||
private final String methodName;
|
||||
private final HttpMethodBase method;
|
||||
private final List<Header> headers = new ArrayList<Header>();
|
||||
private RequestEntity requestBody;
|
||||
|
||||
public RemoteConnectorRequestImpl(String url, String methodName)
|
||||
{
|
||||
this(url, buildHttpClientMethod(url, methodName));
|
||||
}
|
||||
public RemoteConnectorRequestImpl(String url, Class<? extends HttpMethodBase> method)
|
||||
{
|
||||
this(url, buildHttpClientMethod(url, method));
|
||||
}
|
||||
private RemoteConnectorRequestImpl(String url, HttpMethodBase method)
|
||||
{
|
||||
this.url = url;
|
||||
this.method = method;
|
||||
this.methodName = method.getName();
|
||||
}
|
||||
|
||||
protected static HttpMethodBase buildHttpClientMethod(String url, String method)
|
||||
{
|
||||
if ("GET".equals(method))
|
||||
{
|
||||
return new GetMethod(url);
|
||||
}
|
||||
if ("POST".equals(method))
|
||||
{
|
||||
return new PostMethod(url);
|
||||
}
|
||||
if ("PUT".equals(method))
|
||||
{
|
||||
return new PutMethod(url);
|
||||
}
|
||||
if ("DELETE".equals(method))
|
||||
{
|
||||
return new DeleteMethod(url);
|
||||
}
|
||||
if (TestingMethod.METHOD_NAME.equals(method))
|
||||
{
|
||||
return new TestingMethod(url);
|
||||
}
|
||||
throw new UnsupportedOperationException("Method '"+method+"' not supported");
|
||||
}
|
||||
protected static HttpMethodBase buildHttpClientMethod(String url, Class<? extends HttpMethodBase> method)
|
||||
{
|
||||
HttpMethodBase request = null;
|
||||
try
|
||||
{
|
||||
request = method.getConstructor(String.class).newInstance(url);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("HttpClient broken", e);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
public String getURL()
|
||||
{
|
||||
return url;
|
||||
}
|
||||
public String getMethod()
|
||||
{
|
||||
return methodName;
|
||||
}
|
||||
public HttpMethodBase getMethodInstance()
|
||||
{
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getContentType()
|
||||
{
|
||||
for (Header hdr : headers)
|
||||
{
|
||||
if (HEADER_CONTENT_TYPE.equals( hdr.getName() ))
|
||||
{
|
||||
return hdr.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public void setContentType(String contentType)
|
||||
{
|
||||
for (Header hdr : headers)
|
||||
{
|
||||
if (HEADER_CONTENT_TYPE.equals( hdr.getName() ))
|
||||
{
|
||||
hdr.setValue(contentType);
|
||||
return;
|
||||
}
|
||||
}
|
||||
headers.add(new Header(HEADER_CONTENT_TYPE, contentType));
|
||||
}
|
||||
|
||||
public RequestEntity getRequestBody()
|
||||
{
|
||||
return requestBody;
|
||||
}
|
||||
public void setRequestBody(String body)
|
||||
{
|
||||
try
|
||||
{
|
||||
requestBody = new StringRequestEntity(body, getContentType(), "UTF-8");
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {} // Can't occur
|
||||
}
|
||||
public void setRequestBody(byte[] body)
|
||||
{
|
||||
requestBody = new ByteArrayRequestEntity(body);
|
||||
}
|
||||
public void setRequestBody(InputStream body)
|
||||
{
|
||||
requestBody = new InputStreamRequestEntity(body);
|
||||
}
|
||||
public void setRequestBody(RequestEntity body)
|
||||
{
|
||||
requestBody = body;
|
||||
}
|
||||
|
||||
public Header[] getRequestHeaders()
|
||||
{
|
||||
return headers.toArray(new Header[headers.size()]);
|
||||
}
|
||||
public void addRequestHeader(Header header)
|
||||
{
|
||||
addRequestHeaders(new Header[] {header});
|
||||
}
|
||||
public void addRequestHeader(String name, String value)
|
||||
{
|
||||
addRequestHeader(new Header(name,value));
|
||||
}
|
||||
public void addRequestHeaders(Header[] headers)
|
||||
{
|
||||
for (Header newHdr : headers)
|
||||
{
|
||||
// See if we already have one of these headers
|
||||
Header existingHdr = null;
|
||||
for (Header hdr : this.headers)
|
||||
{
|
||||
if (newHdr.getName().equals( hdr.getName() ))
|
||||
{
|
||||
existingHdr = hdr;
|
||||
}
|
||||
}
|
||||
|
||||
// Update or add as needed
|
||||
if (existingHdr != null)
|
||||
{
|
||||
existingHdr.setValue(newHdr.getValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
this.headers.add(newHdr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An HttpClient Method implementation for the method "TESTING",
|
||||
* which we use in certain unit tests
|
||||
*/
|
||||
private static class TestingMethod extends GetMethod
|
||||
{
|
||||
private static final String METHOD_NAME = "TESTING";
|
||||
|
||||
private TestingMethod(String url)
|
||||
{
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return METHOD_NAME;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
package org.alfresco.repo.remoteconnector;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
|
||||
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorResponse;
|
||||
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
|
||||
import org.apache.commons.httpclient.Header;
|
||||
import org.apache.tika.io.IOUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Helper wrapper around a Remote Request, to be performed by the
|
||||
* {@link RemoteConnectorService}.
|
||||
*
|
||||
* @author Nick Burch
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public class RemoteConnectorResponseImpl implements RemoteConnectorResponse
|
||||
{
|
||||
private RemoteConnectorRequest request;
|
||||
private String contentType;
|
||||
private String charset;
|
||||
|
||||
private Header[] headers;
|
||||
|
||||
private InputStream bodyStream;
|
||||
private byte[] bodyBytes;
|
||||
|
||||
/**
|
||||
* Creates a new Response object with the data coming from a stream.
|
||||
* Because of the HttpClient lifecycle, a HttpClient response
|
||||
* InputStream shouldn't be used as cleanup is needed
|
||||
*/
|
||||
public RemoteConnectorResponseImpl(RemoteConnectorRequest request, String contentType,
|
||||
String charset, Header[] headers, InputStream response)
|
||||
{
|
||||
this.request = request;
|
||||
this.contentType = contentType;
|
||||
this.charset = charset;
|
||||
this.headers = headers;
|
||||
this.bodyStream = response;
|
||||
this.bodyBytes = null;
|
||||
}
|
||||
public RemoteConnectorResponseImpl(RemoteConnectorRequest request, String contentType,
|
||||
String charset, Header[] headers, byte[] response)
|
||||
{
|
||||
this(request, contentType, charset, headers, new ByteArrayInputStream(response));
|
||||
this.bodyBytes = response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCharset()
|
||||
{
|
||||
return charset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType()
|
||||
{
|
||||
int split = contentType.indexOf(';');
|
||||
if (split == -1)
|
||||
{
|
||||
return contentType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return contentType.substring(0, split);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRawContentType()
|
||||
{
|
||||
return contentType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteConnectorRequest getRequest()
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header[] getResponseHeaders()
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getResponseBodyAsBytes() throws IOException
|
||||
{
|
||||
if (bodyBytes == null)
|
||||
{
|
||||
bodyBytes = IOUtils.toByteArray(bodyStream);
|
||||
bodyStream.close();
|
||||
|
||||
// Build a new stream version in case they also want that
|
||||
bodyStream = new ByteArrayInputStream(bodyBytes);
|
||||
}
|
||||
return bodyBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResponseBodyAsStream()
|
||||
{
|
||||
return bodyStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponseBodyAsString() throws IOException
|
||||
{
|
||||
String charset = this.charset;
|
||||
if (charset == null)
|
||||
{
|
||||
charset = "UTF-8";
|
||||
}
|
||||
|
||||
return new String(getResponseBodyAsBytes(), charset);
|
||||
}
|
||||
}
|
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright (C) 2005-2012 Alfresco Software Limited.
|
||||
*
|
||||
* This file is part of Alfresco
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
package org.alfresco.repo.remoteconnector;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorRequest;
|
||||
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorResponse;
|
||||
import org.alfresco.service.cmr.remoteconnector.RemoteConnectorService;
|
||||
import org.apache.commons.httpclient.Header;
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.commons.httpclient.HttpMethodBase;
|
||||
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
|
||||
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
|
||||
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.json.simple.parser.JSONParser;
|
||||
import org.springframework.extensions.webscripts.Status;
|
||||
|
||||
/**
|
||||
* HttpClient powered implementation of {@link RemoteConnectorService}, which
|
||||
* performs requests to remote HTTP servers
|
||||
*
|
||||
* @author Nick Burch
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public class RemoteConnectorServiceImpl implements RemoteConnectorService
|
||||
{
|
||||
/**
|
||||
* The logger
|
||||
*/
|
||||
private static Log logger = LogFactory.getLog(RemoteConnectorServiceImpl.class);
|
||||
private static final long MAX_BUFFER_RESPONSE_SIZE = 10*1024*1024;
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
public RemoteConnectorServiceImpl()
|
||||
{
|
||||
httpClient = new HttpClient();
|
||||
httpClient.setHttpConnectionManager(new MultiThreadedHttpConnectionManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new Request object
|
||||
*/
|
||||
public RemoteConnectorRequest buildRequest(String url, String method)
|
||||
{
|
||||
return new RemoteConnectorRequestImpl(url, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new Request object, using HttpClient method descriptions
|
||||
*/
|
||||
public RemoteConnectorRequest buildRequest(String url, Class<? extends HttpMethodBase> method)
|
||||
{
|
||||
return new RemoteConnectorRequestImpl(url, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the specified request, and return the response
|
||||
*/
|
||||
public RemoteConnectorResponse executeRequest(RemoteConnectorRequest request) throws IOException, AuthenticationException
|
||||
{
|
||||
RemoteConnectorRequestImpl reqImpl = (RemoteConnectorRequestImpl)request;
|
||||
HttpMethodBase httpRequest = reqImpl.getMethodInstance();
|
||||
|
||||
// Attach the headers to the request
|
||||
for (Header hdr : request.getRequestHeaders())
|
||||
{
|
||||
httpRequest.addRequestHeader(hdr);
|
||||
}
|
||||
|
||||
// Attach the body, if possible
|
||||
if (httpRequest instanceof EntityEnclosingMethod)
|
||||
{
|
||||
if (request.getRequestBody() != null)
|
||||
{
|
||||
((EntityEnclosingMethod)httpRequest).setRequestEntity( reqImpl.getRequestBody() );
|
||||
}
|
||||
}
|
||||
|
||||
// Log what we're doing
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Performing " + request.getMethod() + " request to " + request.getURL());
|
||||
|
||||
// Perform the request
|
||||
int status = httpClient.executeMethod(httpRequest);
|
||||
String statusText = httpRequest.getStatusText();
|
||||
|
||||
Header[] responseHdrs = httpRequest.getResponseHeaders();
|
||||
Header responseContentTypeH = httpRequest.getResponseHeader(RemoteConnectorRequestImpl.HEADER_CONTENT_TYPE);
|
||||
String responseCharSet = httpRequest.getResponseCharSet();
|
||||
String responseContentType = (responseContentTypeH != null ? responseContentTypeH.getValue() : null);
|
||||
|
||||
|
||||
// Decide on how best to handle the response, based on the size
|
||||
// Ideally, we want to close the HttpClient resources immediately, but
|
||||
// that isn't possible for very large responses
|
||||
RemoteConnectorResponse response = null;
|
||||
if (httpRequest.getResponseContentLength() > MAX_BUFFER_RESPONSE_SIZE)
|
||||
{
|
||||
// Need to wrap the InputStream in something that'll close
|
||||
InputStream wrappedStream = new HttpClientReleasingInputStream(httpRequest);
|
||||
|
||||
// Now build the response
|
||||
response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet,
|
||||
responseHdrs, wrappedStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fairly small response, just keep the bytes and make life simple
|
||||
response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet,
|
||||
responseHdrs, httpRequest.getResponseBody());
|
||||
|
||||
// Now we have the bytes, we can close the HttpClient resources
|
||||
httpRequest.releaseConnection();
|
||||
httpRequest = null;
|
||||
}
|
||||
|
||||
|
||||
// Log the response
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("Response was " + status + " " + statusText);
|
||||
|
||||
// Decide if we should throw an exception
|
||||
if (status == Status.STATUS_FORBIDDEN)
|
||||
{
|
||||
// Tidy if needed
|
||||
if (httpRequest != null)
|
||||
httpRequest.releaseConnection();
|
||||
// Then report the error
|
||||
throw new AuthenticationException(statusText);
|
||||
}
|
||||
if (status == Status.STATUS_INTERNAL_SERVER_ERROR)
|
||||
{
|
||||
// Tidy if needed
|
||||
if (httpRequest != null)
|
||||
httpRequest.releaseConnection();
|
||||
// Then report the error
|
||||
throw new IOException(statusText);
|
||||
}
|
||||
// TODO Handle the rest of the different status codes
|
||||
|
||||
|
||||
// Return our created response
|
||||
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 doExecuteJSONRequest(request, this);
|
||||
}
|
||||
|
||||
public static JSONObject doExecuteJSONRequest(RemoteConnectorRequest request, RemoteConnectorService service) throws ParseException, IOException, AuthenticationException
|
||||
{
|
||||
// Set as JSON
|
||||
request.setContentType(MimetypeMap.MIMETYPE_JSON);
|
||||
|
||||
// Perform the request
|
||||
RemoteConnectorResponse response = service.executeRequest(request);
|
||||
|
||||
// Parse this as JSON
|
||||
JSONParser parser = new JSONParser();
|
||||
String jsonText = response.getResponseBodyAsString();
|
||||
Object json = parser.parse(jsonText);
|
||||
|
||||
// Check it's the right type and return
|
||||
if (json instanceof JSONObject)
|
||||
{
|
||||
return (JSONObject)json;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ParseException(0, json);
|
||||
}
|
||||
}
|
||||
|
||||
private static class HttpClientReleasingInputStream extends FilterInputStream
|
||||
{
|
||||
private HttpMethodBase httpRequest;
|
||||
private HttpClientReleasingInputStream(HttpMethodBase httpRequest) throws IOException
|
||||
{
|
||||
super(httpRequest.getResponseBodyAsStream());
|
||||
this.httpRequest = httpRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
// Tidy the main stream
|
||||
super.close();
|
||||
|
||||
// Now release the underlying resources
|
||||
if (httpRequest != null)
|
||||
{
|
||||
httpRequest.releaseConnection();
|
||||
httpRequest = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user