blob: 328d2c042f10a7d5dd581eb6847814be558d86af [file] [log] [blame]
/*
* Copyright 2008 Niclas Hedhman. All rights Reserved.
*
* 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.admin;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.openrdf.model.Statement;
import org.openrdf.rio.RDFHandlerException;
import org.qi4j.api.association.AssociationDescriptor;
import org.qi4j.api.entity.EntityDescriptor;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.injection.scope.Service;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.Uses;
import org.qi4j.api.property.PropertyDescriptor;
import org.qi4j.api.usecase.Usecase;
import org.qi4j.api.usecase.UsecaseBuilder;
import org.qi4j.api.value.ValueSerialization;
import org.qi4j.api.value.ValueSerializationException;
import org.qi4j.functional.Iterables;
import org.qi4j.library.rdf.entity.EntityStateSerializer;
import org.qi4j.library.rdf.serializer.RdfXmlSerializer;
import org.qi4j.spi.entity.EntityState;
import org.qi4j.spi.entity.ManyAssociationState;
import org.qi4j.spi.entity.NamedAssociationState;
import org.qi4j.spi.entitystore.ConcurrentEntityStateModificationException;
import org.qi4j.spi.entitystore.EntityNotFoundException;
import org.qi4j.spi.entitystore.EntityStore;
import org.qi4j.spi.entitystore.EntityStoreUnitOfWork;
import org.qi4j.spi.entitystore.helpers.JSONEntityState;
import org.qi4j.spi.module.ModuleSpi;
import org.restlet.data.CharacterSet;
import org.restlet.data.Form;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Status;
import org.restlet.data.Tag;
import org.restlet.representation.EmptyRepresentation;
import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.representation.Variant;
import org.restlet.representation.WriterRepresentation;
import org.restlet.resource.ResourceException;
import org.restlet.resource.ServerResource;
public class EntityResource
extends ServerResource
{
@Service
private EntityStore entityStore;
@Service
private ValueSerialization valueSerialization;
@Structure
private ModuleSpi module;
@Uses
private EntityStateSerializer entitySerializer;
private String identity;
public EntityResource()
{
// Define the supported variant.
getVariants().addAll( Arrays.asList(
new Variant( MediaType.TEXT_HTML ),
new Variant( MediaType.APPLICATION_RDF_XML ),
new Variant( MediaType.APPLICATION_JSON ) ) );
setNegotiated( true );
setAllowedMethods( Collections.singleton( Method.ALL ) );
}
@Override
protected void doInit()
throws ResourceException
{
// /entity/{identity}
Map<String, Object> attributes = getRequest().getAttributes();
identity = (String) attributes.get( "identity" );
}
@Override
protected Representation delete( Variant variant )
throws ResourceException
{
Usecase usecase = UsecaseBuilder.newUsecase( "Remove entity" );
EntityStoreUnitOfWork uow = entityStore.newUnitOfWork( usecase, module, System.currentTimeMillis() );
try
{
EntityReference identityRef = EntityReference.parseEntityReference( identity );
uow.entityStateOf( module, identityRef ).remove();
uow.applyChanges().commit();
getResponse().setStatus( Status.SUCCESS_NO_CONTENT );
}
catch( EntityNotFoundException e )
{
uow.discard();
getResponse().setStatus( Status.CLIENT_ERROR_NOT_FOUND );
}
return new EmptyRepresentation();
}
@Override
protected Representation get( Variant variant )
throws ResourceException
{
EntityStoreUnitOfWork uow = entityStore.newUnitOfWork( UsecaseBuilder.newUsecase( "Get entity" ),
module,
System.currentTimeMillis() );
try
{
EntityState entityState = getEntityState( uow );
// Check modification date
Date lastModified = getRequest().getConditions().getModifiedSince();
if( lastModified != null )
{
if( lastModified.getTime() / 1000 == entityState.lastModified() / 1000 )
{
throw new ResourceException( Status.REDIRECTION_NOT_MODIFIED );
}
}
// Generate the right representation according to its media type.
if( MediaType.APPLICATION_RDF_XML.equals( variant.getMediaType() ) )
{
return entityHeaders( representRdfXml( entityState ), entityState );
}
else if( MediaType.TEXT_HTML.equals( variant.getMediaType() ) )
{
return entityHeaders( representHtml( entityState ), entityState );
}
else if( MediaType.APPLICATION_JSON.equals( variant.getMediaType() ) )
{
return entityHeaders( representJson( entityState ), entityState );
}
}
catch( ResourceException ex )
{
uow.discard();
throw ex;
}
throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND );
}
private EntityState getEntityState( EntityStoreUnitOfWork unitOfWork )
throws ResourceException
{
EntityState entityState;
try
{
EntityReference entityReference = EntityReference.parseEntityReference( identity );
entityState = unitOfWork.entityStateOf( module, entityReference );
}
catch( EntityNotFoundException e )
{
throw new ResourceException( Status.CLIENT_ERROR_NOT_FOUND );
}
return entityState;
}
private Representation entityHeaders( Representation representation, EntityState entityState )
{
representation.setModificationDate( new Date( entityState.lastModified() ) );
representation.setTag( new Tag( "" + entityState.version() ) );
representation.setCharacterSet( CharacterSet.UTF_8 );
representation.setLanguages( Collections.singletonList( Language.ENGLISH ) );
return representation;
}
private Representation representHtml( final EntityState entity )
{
return new WriterRepresentation( MediaType.TEXT_HTML )
{
@Override
public void write( Writer writer )
throws IOException
{
PrintWriter out = new PrintWriter( writer );
out.println( "<html><head><title>" + entity.identity() + "</title>"
+ "<link rel=\"alternate\" type=\"application/rdf+xml\" "
+ "href=\"" + entity.identity() + ".rdf\"/></head><body>" );
out.println( "<h1>" + entity.identity() + "</h1>" );
out.println( "<form method=\"post\" action=\"" + getRequest().getResourceRef().getPath() + "\">\n" );
out.println( "<fieldset><legend>Properties</legend>\n<table>" );
final EntityDescriptor descriptor = entity.entityDescriptor();
for( PropertyDescriptor persistentProperty : descriptor.state().properties() )
{
Object value = entity.propertyValueOf( persistentProperty.qualifiedName() );
out.println( "<tr><td>"
+ "<label for=\"" + persistentProperty.qualifiedName() + "\" >"
+ persistentProperty.qualifiedName().name()
+ "</label></td>\n"
+ "<td><input "
+ "size=\"80\" "
+ "type=\"text\" "
+ ( persistentProperty.isImmutable() ? "readonly=\"true\" " : "" )
+ "name=\"" + persistentProperty.qualifiedName() + "\" "
+ "value=\"" + ( value == null ? "" : valueSerialization.serialize( value ) )
+ "\"/></td></tr>" );
}
out.println( "</table></fieldset>\n" );
out.println( "<fieldset><legend>Associations</legend>\n<table>" );
for( AssociationDescriptor associationType : descriptor.state().associations() )
{
Object value = entity.associationValueOf( associationType.qualifiedName() );
if( value == null )
{
value = "";
}
out.println( "<tr><td>"
+ "<label for=\"" + associationType.qualifiedName() + "\" >"
+ associationType.qualifiedName().name()
+ "</label></td>\n"
+ "<td><input "
+ "type=\"text\" "
+ "size=\"80\" "
+ "name=\"" + associationType.qualifiedName() + "\" "
+ "value=\"" + value + "\"/></td></tr>" );
}
out.println( "</table></fieldset>\n" );
out.println( "<fieldset><legend>ManyAssociations</legend>\n<table>" );
for( AssociationDescriptor associationType : descriptor.state().manyAssociations() )
{
ManyAssociationState identities = entity.manyAssociationValueOf( associationType.qualifiedName() );
String value = "";
for( EntityReference identity : identities )
{
value += identity.identity() + "\n";
}
out.println( "<tr><td>"
+ "<label for=\"" + associationType.qualifiedName() + "\" >"
+ associationType.qualifiedName().name()
+ "</label></td>\n"
+ "<td><textarea "
+ "rows=\"10\" "
+ "cols=\"80\" "
+ "name=\"" + associationType.qualifiedName() + "\" >"
+ value
+ "</textarea></td></tr>" );
}
out.println( "</table></fieldset>\n" );
out.println( "<fieldset><legend>NamedAssociations</legend>\n<table>" );
for( AssociationDescriptor associationType : descriptor.state().namedAssociations() )
{
NamedAssociationState identities = entity.namedAssociationValueOf( associationType.qualifiedName() );
String value = "";
for( String name : identities )
{
value += name + "\n" + identities.get( name ).identity() + "\n";
}
out.println( "<tr><td>"
+ "<label for=\"" + associationType.qualifiedName() + "\" >"
+ associationType.qualifiedName().name()
+ "</label></td>\n"
+ "<td><textarea "
+ "rows=\"10\" "
+ "cols=\"80\" "
+ "name=\"" + associationType.qualifiedName() + "\" >"
+ value
+ "</textarea></td></tr>" );
}
out.println( "</table></fieldset>\n" );
out.println( "<input type=\"submit\" value=\"Update\"/></form>\n" );
out.println( "</body></html>\n" );
}
};
}
private Representation representJson( EntityState entityState )
{
if( entityState instanceof JSONEntityState )
{
JSONEntityState jsonState = (JSONEntityState) entityState;
return new StringRepresentation( jsonState.state().toString(), MediaType.APPLICATION_JSON );
}
else
{
throw new ResourceException( Status.CLIENT_ERROR_NOT_ACCEPTABLE );
}
}
private Representation representRdfXml( final EntityState entity )
throws ResourceException
{
Representation representation = new WriterRepresentation( MediaType.APPLICATION_RDF_XML )
{
@Override
public void write( Writer writer )
throws IOException
{
try
{
Iterable<Statement> statements = entitySerializer.serialize( entity );
new RdfXmlSerializer().serialize( statements, writer );
}
catch( RDFHandlerException e )
{
throw new IOException( e );
}
}
};
representation.setCharacterSet( CharacterSet.UTF_8 );
return representation;
}
@Override
protected Representation put( Representation representation, Variant variant )
throws ResourceException
{
return post( representation, variant );
}
@Override
public Representation post( Representation entityRepresentation, Variant variant )
throws ResourceException
{
Usecase usecase = UsecaseBuilder.newUsecase( "Update entity" );
EntityStoreUnitOfWork unitOfWork = entityStore.newUnitOfWork( usecase, module, System.currentTimeMillis() );
EntityState entity = getEntityState( unitOfWork );
Form form = new Form( entityRepresentation );
try
{
final EntityDescriptor descriptor = entity.entityDescriptor();
// Parse JSON into properties
for( PropertyDescriptor persistentProperty : descriptor.state().properties() )
{
if( !persistentProperty.isImmutable() )
{
String formValue = form.getFirstValue( persistentProperty.qualifiedName().name(), null );
if( formValue == null )
{
entity.setPropertyValue( persistentProperty.qualifiedName(), null );
}
else
{
entity.setPropertyValue(
persistentProperty.qualifiedName(),
valueSerialization.deserialize( persistentProperty.valueType(), formValue ) );
}
}
}
for( AssociationDescriptor associationType : descriptor.state().associations() )
{
String newStringAssociation = form.getFirstValue( associationType.qualifiedName().name() );
if( newStringAssociation == null || newStringAssociation.isEmpty() )
{
entity.setAssociationValue( associationType.qualifiedName(), null );
}
else
{
entity.setAssociationValue( associationType.qualifiedName(),
EntityReference.parseEntityReference( newStringAssociation ) );
}
}
for( AssociationDescriptor associationType : descriptor.state().manyAssociations() )
{
String newStringAssociation = form.getFirstValue( associationType.qualifiedName().name() );
ManyAssociationState manyAssociation = entity.manyAssociationValueOf( associationType.qualifiedName() );
if( newStringAssociation == null )
{
// Remove "left-overs"
for( EntityReference entityReference : manyAssociation )
{
manyAssociation.remove( entityReference );
}
continue;
}
BufferedReader bufferedReader = new BufferedReader( new StringReader( newStringAssociation ) );
String identity;
try
{
// Synchronize old and new association
int index = 0;
while( ( identity = bufferedReader.readLine() ) != null )
{
EntityReference reference = new EntityReference( identity );
if( manyAssociation.count() < index && manyAssociation.get( index ).equals( reference ) )
{
continue;
}
try
{
unitOfWork.entityStateOf( module, reference );
manyAssociation.remove( reference );
manyAssociation.add( index++, reference );
}
catch( EntityNotFoundException e )
{
// Ignore this entity - doesn't exist
}
}
// Remove "left-overs"
while( manyAssociation.count() > index )
{
manyAssociation.remove( manyAssociation.get( index ) );
}
}
catch( IOException e )
{
// Ignore
}
}
for( AssociationDescriptor associationType : descriptor.state().namedAssociations() )
{
String newStringAssociation = form.getFirstValue( associationType.qualifiedName().name() );
NamedAssociationState namedAssociation = entity.namedAssociationValueOf( associationType.qualifiedName() );
if( newStringAssociation == null )
{
// Remove "left-overs"
for( String name : namedAssociation )
{
namedAssociation.remove( name );
}
continue;
}
Set<String> names = new HashSet<>();
BufferedReader bufferedReader = new BufferedReader( new StringReader( newStringAssociation ) );
String line;
try
{
while( ( line = bufferedReader.readLine() ) != null )
{
String name = line;
line = bufferedReader.readLine();
if( line == null )
{
break;
}
String identity = line;
EntityReference reference = new EntityReference( identity );
try
{
unitOfWork.entityStateOf( module, reference );
namedAssociation.remove( name );
namedAssociation.put( name, reference );
names.add( name );
}
catch( EntityNotFoundException e )
{
// Ignore this entity - doesn't exist
}
}
// Remove "left-overs"
for( String assocName : Iterables.toList( namedAssociation ) )
{
if( !names.contains( assocName ) )
{
namedAssociation.remove( assocName );
}
}
}
catch( IOException e )
{
// Ignore
}
}
}
catch( ValueSerializationException | IllegalArgumentException e )
{
throw new ResourceException( Status.SERVER_ERROR_INTERNAL, e );
}
try
{
unitOfWork.applyChanges().commit();
}
catch( ConcurrentEntityStateModificationException e )
{
throw new ResourceException( Status.CLIENT_ERROR_CONFLICT );
}
catch( EntityNotFoundException e )
{
throw new ResourceException( Status.CLIENT_ERROR_GONE );
}
getResponse().setStatus( Status.SUCCESS_RESET_CONTENT );
return new EmptyRepresentation();
}
}