/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011-2015 ForgeRock AS.
*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (the License). You may not use this file except in
* compliance with the License.
*
* You can obtain a copy of the License at
* http://forgerock.org/license/CDDLv1.0.html
* See the License for the specific language governing
* permission and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at http://forgerock.org/license/CDDLv1.0.html
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*/
package org.forgerock.openidm.shell.impl;
import static org.forgerock.json.JsonValue.json;
import static org.forgerock.json.resource.Responses.newActionResponse;
import static org.forgerock.json.resource.Responses.newResourceResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.forgerock.http.util.Json;
import org.forgerock.json.JsonValue;
import org.forgerock.json.JsonValueException;
import org.forgerock.json.resource.ActionRequest;
import org.forgerock.json.resource.ActionResponse;
import org.forgerock.json.resource.BadRequestException;
import org.forgerock.json.resource.Connection;
import org.forgerock.json.resource.CreateRequest;
import org.forgerock.json.resource.DeleteRequest;
import org.forgerock.json.resource.InternalServerErrorException;
import org.forgerock.json.resource.NotSupportedException;
import org.forgerock.json.resource.PatchOperation;
import org.forgerock.json.resource.PatchRequest;
import org.forgerock.json.resource.QueryRequest;
import org.forgerock.json.resource.QueryResourceHandler;
import org.forgerock.json.resource.QueryResponse;
import org.forgerock.json.resource.ReadRequest;
import org.forgerock.json.resource.Request;
import org.forgerock.json.resource.ResourceException;
import org.forgerock.json.resource.ResourceResponse;
import org.forgerock.json.resource.UpdateRequest;
import org.forgerock.services.context.Context;
import org.forgerock.util.promise.Promise;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.Conditions;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Preference;
import org.restlet.data.Reference;
import org.restlet.data.Tag;
import org.restlet.ext.jackson.JacksonRepresentation;
import org.restlet.representation.EmptyRepresentation;
import org.restlet.representation.Representation;
import org.restlet.resource.ClientResource;
/**
* A {@link Connection} to the remote OpenIDM instance.
*/
public class HttpRemoteJsonResource implements Connection {
/**
* Requests that the origin server accepts the entity enclosed in the
* request as a new subordinate of the resource identified by the request
* URI.
*
* @see <a
* href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5">HTTP
* RFC - 9.5 POST</a>
*/
public static final Method PATCH = new Method("PATCH");
/**Base reference used for requesting this resource. */
private Reference baseReference;
/** Username used for authentication when accessing the resource. */
private String username = "";
/** Password used for authentication when accessing the resource. */
private String password = "";
/**
* Construct the HttpRemoteJsonResource.
*/
public HttpRemoteJsonResource() {
}
/**
* Create a HttpRemoteJsonResource with credentials.
*
* @param uri URI of this resource.
* @param username Username for HTTP basic authentication
* @param password Password for HTTP basic authentication.
*/
public HttpRemoteJsonResource(final String uri, final String username, final String password) {
this.username = username;
this.password = password;
baseReference = new Reference(uri);
}
/**
* {@inheritDoc}
*/
@Override
public ActionResponse action(Context context, ActionRequest request) throws ResourceException {
JsonValue params = new JsonValue(request.getAdditionalParameters());
JsonValue result = handle(request, request.getResourcePath(), params);
return newActionResponse(result);
}
/**
* {@inheritDoc}
*/
@Override
public Promise<ActionResponse, ResourceException> actionAsync(Context context, ActionRequest request) {
return new NotSupportedException().asPromise();
}
/**
* {@inheritDoc}
*/
@Override
public void close() {
}
/**
* {@inheritDoc}
*/
@Override
public ResourceResponse create(Context context, CreateRequest request) throws ResourceException {
JsonValue response = handle(request, request.getResourcePathObject().child(request.getNewResourceId()).toString(), null);
return newResourceResponse(response.get("_id").asString(), response.get("_rev").asString(), response);
}
/**
* {@inheritDoc}
*/
@Override
public Promise<ResourceResponse, ResourceException> createAsync(Context context, CreateRequest request) {
return new NotSupportedException().asPromise();
}
/**
* {@inheritDoc}
*/
@Override
public ResourceResponse delete(Context context, DeleteRequest request) throws ResourceException {
final JsonValue response = handle(request, request.getResourcePath(), null);
return newResourceResponse(response.get("_id").asString(), response.get("_rev").asString(), response);
}
/**
* {@inheritDoc}
*/
@Override
public Promise<ResourceResponse, ResourceException> deleteAsync(Context context, DeleteRequest request) {
return new NotSupportedException().asPromise();
}
/**
* {@inheritDoc}
*/
@Override
public boolean isClosed() {
return false;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isValid() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public ResourceResponse patch(Context context, PatchRequest request) throws ResourceException {
throw new NotSupportedException();
}
/**
* {@inheritDoc}
*/
@Override
public Promise<ResourceResponse, ResourceException> patchAsync(Context context, PatchRequest request) {
return new NotSupportedException().asPromise();
}
/**
* {@inheritDoc}
*/
@Override
public QueryResponse query(Context context, QueryRequest request, QueryResourceHandler handler) throws ResourceException {
throw new NotSupportedException();
}
/**
* {@inheritDoc}
*/
@Override
public QueryResponse query(Context context, QueryRequest request, Collection<? super ResourceResponse> results) throws ResourceException {
throw new NotSupportedException();
}
/**
* {@inheritDoc}
*/
@Override
public Promise<QueryResponse, ResourceException> queryAsync(Context context, QueryRequest request, QueryResourceHandler handler) {
return new NotSupportedException().asPromise();
}
/**
* {@inheritDoc}
*/
@Override
public ResourceResponse read(Context context, ReadRequest request) throws ResourceException {
JsonValue result = handle(request, request.getResourcePath(), null);
return newResourceResponse(result.get("_id").asString(), result.get("_rev").asString(), result);
}
/**
* {@inheritDoc}
*/
@Override
public Promise<ResourceResponse, ResourceException> readAsync(Context context, ReadRequest request) {
return new NotSupportedException().asPromise();
}
/**
* {@inheritDoc}
*/
@Override
public ResourceResponse update(Context context, UpdateRequest request) throws ResourceException {
JsonValue result = handle(request, request.getResourcePath(), null);
return newResourceResponse(result.get("_id").asString(), result.get("_rev").asString(), result);
}
/**
* {@inheritDoc}
*/
@Override
public Promise<ResourceResponse, ResourceException> updateAsync(Context context, UpdateRequest request) {
return new NotSupportedException().asPromise();
}
private ClientResource getClientResource(Reference ref) {
ClientResource clientResource = new ClientResource(new org.restlet.Context(), new Reference(baseReference, ref));
List<Preference<MediaType>> acceptedMediaTypes = new ArrayList<Preference<MediaType>>(1);
acceptedMediaTypes.add(new Preference<MediaType>(MediaType.APPLICATION_JSON));
clientResource.getClientInfo().setAcceptedMediaTypes(acceptedMediaTypes);
clientResource.getLogger().setLevel(Level.WARNING);
ChallengeResponse rc = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, username, password);
clientResource.setChallengeResponse(rc);
return clientResource;
}
private JsonValue handle(Request request, String id, JsonValue params)
throws org.forgerock.json.resource.ResourceException {
Representation response = null;
ClientResource clientResource = null;
try {
Reference remoteRef = new Reference(id);
// Get the client resource corresponding to this request's resource name
clientResource = getClientResource(remoteRef);
// Prepare query params
if (params != null && !params.isNull()) {
for (Map.Entry<String, Object> entry : params.asMap().entrySet()) {
if (entry.getValue() instanceof String) {
clientResource.addQueryParameter(entry.getKey(), (String) entry.getValue());
}
}
}
// Payload
Representation representation = null;
JsonValue value = getRequestValue(request);
if (!value.isNull()) {
representation = new JacksonRepresentation<Map<String, Object>>(value.asMap());
}
// ETag
Conditions conditions = new Conditions();
switch (request.getRequestType()) {
case CREATE:
conditions.setNoneMatch(Arrays.asList(Tag.ALL));
clientResource.getRequest().setConditions(conditions);
response = clientResource.put(representation);
break;
case READ:
response = clientResource.get();
break;
case UPDATE:
conditions.setMatch(getTag(((UpdateRequest) request).getRevision()));
clientResource.getRequest().setConditions(conditions);
response = clientResource.put(representation);
break;
case DELETE:
conditions.setMatch(Arrays.asList(Tag.ALL));
clientResource.getRequest().setConditions(conditions);
response = clientResource.delete();
break;
case PATCH:
conditions.setMatch(getTag(((PatchRequest) request).getRevision()));
clientResource.getRequest().setConditions(conditions);
clientResource.setMethod(PATCH);
clientResource.getRequest().setEntity(representation);
response = clientResource.handle();
break;
case QUERY:
response = clientResource.get();
break;
case ACTION:
clientResource.setQueryValue("_action", ((ActionRequest) request).getAction());
response = clientResource.post(representation);
break;
default:
throw new BadRequestException();
}
if (!clientResource.getStatus().isSuccess()) {
throw org.forgerock.json.resource.ResourceException.getException(clientResource
.getStatus().getCode(), clientResource.getStatus().getDescription(),
clientResource.getStatus().getThrowable());
}
return (null != response && !(response instanceof EmptyRepresentation)
? json(Json.readJson(response.getText()))
: json(null));
} catch (JsonValueException jve) {
throw new BadRequestException(jve);
} catch (org.restlet.resource.ResourceException e) {
StringBuilder sb = new StringBuilder(e.getStatus().getDescription());
if (null != clientResource) {
try {
sb.append(" ").append(clientResource.getResponse().getEntity().getText());
} catch (IOException e1) {
// unable to add response text to exception message, proceed without
}
}
throw ResourceException.getException(e.getStatus().getCode(), sb.toString(), e.getCause());
} catch (Exception e) {
throw new InternalServerErrorException(e);
} finally {
if (null != response) {
response.release();
}
}
}
private JsonValue getRequestValue(Request request) throws Exception {
switch (request.getRequestType()) {
case CREATE:
return ((CreateRequest) request).getContent();
case UPDATE:
return new JsonValue(((UpdateRequest) request).getContent());
case PATCH:
ObjectMapper mapper = new ObjectMapper();
List<PatchOperation> ops = ((PatchRequest) request).getPatchOperations();
JsonValue value = new JsonValue(new ArrayList<Object>());
for (PatchOperation op : ops) {
value.add(new JsonValue(mapper.readValue(op.toString(), Object.class)));
}
return value;
case ACTION:
JsonValue content = ((ActionRequest) request).getContent();
if (content != null && !content.isNull()) {
return content;
} else {
return new JsonValue(new HashMap<String, Object>());
}
}
return new JsonValue(null);
}
private List<Tag> getTag(String tag) {
List<Tag> result = new ArrayList<Tag>(1);
if (null != tag && tag.trim().length() > 0) {
result.add(Tag.parse(tag));
}
return result;
}
/**
* Gets the username.
*
* @return the username
*/
public String getUsername() {
return username;
}
/**
* Sets the username.
*
* @param username the username
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Gets the password.
*
* @return the password
*/
public String getPassword() {
return password;
}
/**
* Sets the password.
*
* @param password the password
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Sets the port.
*
* @param port the port
*/
public void setPort(int port) {
baseReference.setHostPort(port);
}
/**
* Sets the base URI.
*
* @param baseUri the base URI
*/
public void setBaseUri(final String baseUri) {
baseReference = new Reference(baseUri);
}
}