blob: 597eeb19978829207beb64eb70d2da57fa064433 [file] [log] [blame]
/**
*
* 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.apache.zest.library.rest.client.api;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.util.HashMap;
import java.util.Map;
import org.apache.zest.api.injection.scope.Uses;
import org.apache.zest.api.util.Classes;
import org.apache.zest.library.rest.client.spi.NullResponseHandler;
import org.apache.zest.library.rest.client.spi.ResponseHandler;
import org.apache.zest.library.rest.client.spi.ResultHandler;
import org.apache.zest.library.rest.common.Resource;
import org.apache.zest.library.rest.common.link.Link;
import org.apache.zest.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();
}
}