/** * * Copyright 2009-2011 Rickard Öberg AB * * 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. */ package org.qi4j.library.rest.client.api; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.util.HashMap; import java.util.Map; import org.qi4j.api.injection.scope.Uses; import org.qi4j.api.util.Classes; import org.qi4j.library.rest.client.spi.NullResponseHandler; import org.qi4j.library.rest.client.spi.ResponseHandler; import org.qi4j.library.rest.client.spi.ResultHandler; import org.qi4j.library.rest.common.Resource; import org.qi4j.library.rest.common.link.Link; import org.qi4j.library.rest.common.link.LinksUtil; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.ChallengeResponse; import org.restlet.data.ChallengeScheme; import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.representation.EmptyRepresentation; import org.restlet.representation.ObjectRepresentation; import org.restlet.resource.ResourceException; import org.restlet.security.User; /** * Client-side context resources */ public class ContextResourceClient { @Uses private ContextResourceClientFactory contextResourceFactory; @Uses private Reference reference; private Resource resource; // Response handlers private ResponseHandler errorHandler = NullResponseHandler.INSTANCE; private ResponseHandler resourceHandler = NullResponseHandler.INSTANCE; private ResponseHandler deleteHandler = NullResponseHandler.INSTANCE; private Map<String, ResponseHandler> queryHandlers = new HashMap<String, ResponseHandler>( ); private Map<String, ResponseHandler> commandHandlers = new HashMap<String, ResponseHandler>( ); private Map<String, ResponseHandler> processingErrorHandlers = new HashMap<String, ResponseHandler>(); // DSL for registering rules public ContextResourceClient onError(ResponseHandler handler) { errorHandler = handler; return this; } public <T> ContextResourceClient onResource( final ResultHandler<T> handler) { resourceHandler = new ResponseHandler() { @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { final Class<T> resultType = (Class<T>) Classes.RAW_CLASS.map(( (ParameterizedType) handler.getClass().getGenericInterfaces()[ 0 ] ).getActualTypeArguments()[0]); T result = contextResourceFactory.readResponse( response, resultType ); if (result instanceof Resource) { resource = (Resource) result; } return handler.handleResult( result, client ); } }; return this; } public ContextResourceClient onQuery( String relation, ResponseHandler handler ) { queryHandlers.put( relation, handler ); return this; } public <T> ContextResourceClient onQuery( String relation, final ResultHandler<T> handler ) { final Class<T> resultType = (Class<T>) Classes.RAW_CLASS.map(( (ParameterizedType) handler.getClass().getGenericInterfaces()[ 0 ] ).getActualTypeArguments()[0]); queryHandlers.put( relation, new ResponseHandler() { @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { T result = contextResourceFactory.readResponse( response, resultType ); return handler.handleResult( result, client ); } }); return this; } public ContextResourceClient onCommand( String relation, ResponseHandler handler ) { commandHandlers.put( relation, handler); return this; } public <T> ContextResourceClient onCommand( String relation, final ResultHandler<T> handler ) { final Class<T> resultType = (Class<T>) Classes.RAW_CLASS.map(( (ParameterizedType) handler.getClass().getGenericInterfaces()[ 0 ] ).getActualTypeArguments()[0]); commandHandlers.put( relation, new ResponseHandler() { @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { T result = contextResourceFactory.readResponse( response, resultType ); return handler.handleResult( result, client ); } }); return this; } public ContextResourceClient onProcessingError( String relation, ResponseHandler handler ) { processingErrorHandlers.put( relation, handler); return this; } public <T> ContextResourceClient onProcessingError( String relation, final ResultHandler<T> handler) { final Class<T> resultType = (Class<T>) Classes.RAW_CLASS.map(( (ParameterizedType) handler.getClass().getGenericInterfaces()[ 0 ] ).getActualTypeArguments()[0]); processingErrorHandlers.put( relation, new ResponseHandler() { @Override public HandlerCommand handleResponse( Response response, ContextResourceClient client ) { T result = contextResourceFactory.readResponse( response, resultType ); return handler.handleResult( result, client ); } }); return this; } public ContextResourceClient onDelete(ResponseHandler handler) { deleteHandler = handler; return this; } public ContextResourceClientFactory getContextResourceClientFactory() { return contextResourceFactory; } public Reference getReference() { return reference; } public Resource getResource() { return resource; } public void start() { HandlerCommand command = refresh(); while (command != null) command = command.execute( this ); } // Callable from HandlerCommand HandlerCommand refresh() { if (resourceHandler == null) throw new IllegalStateException( "No handler set for resources" ); return invokeQuery( reference, null, resourceHandler, null ); } HandlerCommand query( String relation, Object queryRequest, ResponseHandler handler, ResponseHandler processingErrorHandler ) { return query( resource.query( relation ), queryRequest, handler, processingErrorHandler ); } HandlerCommand query( Link link, Object queryRequest, ResponseHandler handler, ResponseHandler processingErrorHandler ) { if (handler == null) handler = queryHandlers.get( link.rel().get() ); if (handler == null) throw new IllegalArgumentException( "No handler set for relation "+link.rel().get() ); if (processingErrorHandler == null) processingErrorHandler = processingErrorHandlers.get( link.rel().get() ); Reference linkRef = new Reference(link.href().get()); if (linkRef.isRelative()) linkRef = new Reference( reference.toUri().toString() + link.href().get() ); return invokeQuery( linkRef, queryRequest, handler, processingErrorHandler ); } private HandlerCommand invokeQuery( Reference ref, Object queryRequest, ResponseHandler resourceHandler, ResponseHandler processingErrorHandler ) { Request request = new Request( Method.GET, ref ); if( queryRequest != null ) { contextResourceFactory.writeRequest( request, queryRequest ); } contextResourceFactory.updateQueryRequest( request ); User user = request.getClientInfo().getUser(); if ( user != null) request.setChallengeResponse( new ChallengeResponse( ChallengeScheme.HTTP_BASIC, user.getName(), user.getSecret() ) ); Response response = new Response( request ); contextResourceFactory.getClient().handle( request, response ); if( response.getStatus().isSuccess() ) { contextResourceFactory.updateCache( response ); return resourceHandler.handleResponse( response, this ); } else if (response.getStatus().isRedirection()) { Reference redirectedTo = response.getLocationRef(); return invokeQuery( redirectedTo, queryRequest, resourceHandler, processingErrorHandler ); } else { if (response.getStatus().equals(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY) && processingErrorHandler != null) { return processingErrorHandler.handleResponse( response, this ); } else { // TODO This needs to be expanded to allow custom handling of all the various cases return errorHandler.handleResponse( response, this ); } } } // Commands HandlerCommand command( Link link, Object commandRequest, ResponseHandler handler, ResponseHandler processingErrorHandler ) { if (handler == null) handler = commandHandlers.get( link.rel().get() ); if (processingErrorHandler == null) processingErrorHandler = processingErrorHandlers.get( link.rel().get() ); // Check if we should do POST or PUT Method method; if( LinksUtil.withClass( "idempotent" ).satisfiedBy( link ) ) { method = Method.PUT; } else { method = Method.POST; } Reference ref = new Reference( reference.toUri().toString() + link.href().get() ); return invokeCommand( ref, method, commandRequest, handler, processingErrorHandler ); } private HandlerCommand invokeCommand( Reference ref, Method method, Object requestObject, ResponseHandler responseHandler, ResponseHandler processingErrorHandler ) { Request request = new Request( method, ref ); if (requestObject == null) requestObject = new EmptyRepresentation(); contextResourceFactory.writeRequest( request, requestObject ); contextResourceFactory.updateCommandRequest( request ); User user = request.getClientInfo().getUser(); if ( user != null) request.setChallengeResponse( new ChallengeResponse( ChallengeScheme.HTTP_BASIC, user.getName(), user.getSecret() ) ); Response response = new Response( request ); contextResourceFactory.getClient().handle( request, response ); try { if( response.getStatus().isSuccess() ) { contextResourceFactory.updateCache( response ); if (responseHandler != null) return responseHandler.handleResponse( response, this ); } else { if (response.getStatus().equals(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY) && processingErrorHandler != null) { return processingErrorHandler.handleResponse( response, this ); } else { // TODO This needs to be expanded to allow custom handling of all the various cases return errorHandler.handleResponse( response, this ); } } return null; // No handler found } finally { try { response.getEntity().exhaust(); } catch( Throwable e ) { // Ignore } } } // Delete public HandlerCommand delete( ResponseHandler responseHandler, ResponseHandler processingErrorHandler ) throws ResourceException { if (responseHandler == null) responseHandler = deleteHandler; Request request = new Request( Method.DELETE, new Reference( reference.toUri() ).toString() ); contextResourceFactory.updateCommandRequest( request ); int tries = 3; while( true ) { Response response = new Response( request ); try { contextResourceFactory.getClient().handle( request, response ); if( !response.getStatus().isSuccess() ) { return errorHandler.handleResponse( response, this ); } else { // Reset modification date contextResourceFactory.updateCache( response ); return responseHandler.handleResponse( response, this ); } } catch( ResourceException e ) { if( e.getStatus().equals( Status.CONNECTOR_ERROR_COMMUNICATION ) || e.getStatus().equals( Status.CONNECTOR_ERROR_CONNECTION ) ) { if( tries == 0 ) { throw e; // Give up } else { // Try again tries--; continue; } } else { // Abort throw e; } } finally { try { response.getEntity().exhaust(); } catch( Throwable e ) { // Ignore } } } } // Browse to other resources public synchronized ContextResourceClient newClient( Link link ) { if( link == null ) { throw new NullPointerException( "No link specified" ); } return newClient( link.href().get() ); } public synchronized ContextResourceClient newClient( String relativePath ) { if( relativePath.startsWith( "http://" ) ) { return contextResourceFactory.newClient( new Reference( relativePath ) ); } Reference reference = this.reference.clone(); if( relativePath.startsWith( "/" ) ) { reference.setPath( relativePath ); } else { reference.setPath( reference.getPath() + relativePath ); reference = reference.normalize(); } return contextResourceFactory.newClient( reference ); } // Internal private Object handlxeError( Response response ) throws ResourceException { if( response.getStatus().equals( Status.SERVER_ERROR_INTERNAL ) ) { if( MediaType.APPLICATION_JAVA_OBJECT.equals( response.getEntity().getMediaType() ) ) { try { Object exception = new ObjectRepresentation( response.getEntity() ).getObject(); throw new ResourceException( (Throwable) exception ); } catch( IOException e ) { throw new ResourceException( e ); } catch( ClassNotFoundException e ) { throw new ResourceException( e ); } } throw new ResourceException( Status.SERVER_ERROR_INTERNAL, response.getEntityAsText() ); } else { if( response.getEntity() != null ) { String text = response.getEntityAsText(); throw new ResourceException( response.getStatus().getCode(), response.getStatus() .getName(), text, response.getRequest().getResourceRef().toUri().toString() ); } else { throw new ResourceException( response.getStatus().getCode(), response.getStatus() .getName(), response.getStatus().getDescription(), response.getRequest() .getResourceRef() .toUri() .toString() ); } } } @Override public String toString() { return reference.toString(); } }