/**
*
* Copyright
* 2009-2015 Jayway Products AB
* 2016-2017 Föreningen Sambruk
*
* Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.txt
*
* 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 se.streamsource.dci.restlet.client;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.qi4j.bootstrap.ImportedServiceDeclaration.NEW_OBJECT;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import org.hamcrest.CoreMatchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.qi4j.api.common.Optional;
import org.qi4j.api.common.UseDefaults;
import org.qi4j.api.common.Visibility;
import org.qi4j.api.composite.TransientComposite;
import org.qi4j.api.constraint.Name;
import org.qi4j.api.entity.EntityComposite;
import org.qi4j.api.injection.scope.Structure;
import org.qi4j.api.injection.scope.Uses;
import org.qi4j.api.property.Property;
import org.qi4j.api.structure.Module;
import org.qi4j.api.unitofwork.ConcurrentEntityModificationException;
import org.qi4j.api.unitofwork.UnitOfWorkCallback;
import org.qi4j.api.unitofwork.UnitOfWorkCompletionException;
import org.qi4j.api.util.Iterables;
import org.qi4j.api.value.ValueComposite;
import org.qi4j.bootstrap.ApplicationAssembler;
import org.qi4j.bootstrap.ApplicationAssembly;
import org.qi4j.bootstrap.ApplicationAssemblyFactory;
import org.qi4j.bootstrap.AssemblyException;
import org.qi4j.bootstrap.ModuleAssembly;
import org.qi4j.spi.service.importer.NewObjectImporter;
import org.qi4j.spi.structure.ApplicationModelSPI;
import org.qi4j.test.AbstractQi4jTest;
import org.restlet.Client;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Server;
import org.restlet.Uniform;
import org.restlet.data.Form;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.ResourceException;
import org.restlet.service.MetadataService;
import se.streamsource.dci.api.DeleteContext;
import se.streamsource.dci.api.InteractionConstraintsService;
import se.streamsource.dci.api.InteractionValidation;
import se.streamsource.dci.api.Requires;
import se.streamsource.dci.api.RequiresValid;
import se.streamsource.dci.api.Role;
import se.streamsource.dci.api.RoleMap;
import se.streamsource.dci.qi4j.RoleInjectionProviderFactory;
import se.streamsource.dci.restlet.server.CommandQueryResource;
import se.streamsource.dci.restlet.server.CommandQueryRestlet;
import se.streamsource.dci.restlet.server.CommandResult;
import se.streamsource.dci.restlet.server.DCIAssembler;
import se.streamsource.dci.restlet.server.NullCommandResult;
import se.streamsource.dci.restlet.server.api.SubResource;
import se.streamsource.dci.restlet.server.api.SubResources;
import se.streamsource.dci.value.ResourceValue;
import se.streamsource.dci.value.ValueAssembler;
import se.streamsource.dci.value.link.LinkValue;
import se.streamsource.dci.value.link.Links;
/**
* Test for CommandQueryClient
*/
public class CommandQueryClientTest
extends AbstractQi4jTest
{
static public Server server;
public CommandQueryClient cqc;
public static String command = null; // Commands will set this
private ModuleAssembly module;
protected ApplicationModelSPI newApplication()
throws AssemblyException
{
ApplicationAssembler assembler = new ApplicationAssembler()
{
public ApplicationAssembly assemble( ApplicationAssemblyFactory applicationFactory )
throws AssemblyException
{
ApplicationAssembly assembly = applicationFactory.newApplicationAssembly( CommandQueryClientTest.this );
assembly.setMetaInfo( new RoleInjectionProviderFactory() );
assembly.setMetaInfo( new MetadataService() );
return assembly;
}
};
try
{
return qi4j.newApplicationModel( assembler );
}
catch (AssemblyException e)
{
assemblyException( e );
return null;
}
}
public void assemble( ModuleAssembly module ) throws AssemblyException
{
module.objects( RootContext.class, SubContext.class, SubContext2.class, RootResource.class, SubResource1.class );
new ClientAssembler().assemble( module );
module.values(TestQuery.class, TestResult.class, TestCommand.class);
module.forMixin( TestQuery.class ).declareDefaults().abc().set("def");
new ValueAssembler().assemble( module );
new DCIAssembler().assemble( module );
module.objects(NullCommandResult.class );
module.importedServices(CommandResult.class).importedBy( NEW_OBJECT );
module.objects(RootRestlet.class );
module.importedServices( InteractionConstraintsService.class ).
importedBy( NewObjectImporter.class ).
visibleIn( Visibility.application );
module.objects( InteractionConstraintsService.class );
module.importedServices( MetadataService.class ).importedBy( NEW_OBJECT );
module.objects( MetadataService.class );
module.objects( DescribableContext.class );
module.transients( TestComposite.class );
}
@Before
public void startWebServer() throws Exception
{
server = new Server( Protocol.HTTP, 8888 );
CommandQueryRestlet restlet = objectBuilderFactory.newObjectBuilder( CommandQueryRestlet.class ).use( new org.restlet.Context() ).newInstance();
server.setNext(restlet);
server.start();
Client client = new Client( Protocol.HTTP );
Reference ref = new Reference( "http://localhost:8888/" );
cqc = objectBuilderFactory.newObjectBuilder( CommandQueryClientFactory.class ).use(client, new NullResponseHandler() ).newInstance().newClient( ref );
}
@After
public void stopWebServer() throws Exception
{
server.stop();
}
@Test
public void testQueryWithValue()
{
TestResult result = cqc.query( "querywithvalue", TestResult.class, valueBuilderFactory.newValueFromJSON( TestQuery.class, "{'abc':'foo'}" ));
assertThat(result.toJSON(), equalTo("{\"xyz\":\"bar\"}"));
}
@Test
public void testQueryWithValueGetDefaults()
{
TestQuery result = cqc.query( "querywithvalue", TestQuery.class );
assertThat( result.toJSON(), equalTo( "{\"abc\":\"def\"}" ) );
}
@Test
public void testQueryWithStringResult()
{
String result = cqc.query( "querywithstringresult", String.class, valueBuilderFactory.newValueFromJSON( TestQuery.class, "{'abc':'foo'}" ));
assertThat( result, equalTo( "bar") );
}
@Test
public void testQueryWithIntegerResult()
{
int result = cqc.query( "querywithintegerresult", Integer.class, valueBuilderFactory.newValueFromJSON( TestQuery.class, "{'abc':'foo'}" ));
assertThat( result, equalTo( 7) );
}
@Test
public void testCommandWithValueGetDefaults()
{
TestCommand result = cqc.query( "commandwithvalue", TestCommand.class );
assertThat( result.toJSON(), equalTo( "{\"abc\":\"\"}" ) );
}
@Test
public void testQueryWithoutValue()
{
TestResult result = cqc.query( "querywithoutvalue", TestResult.class );
assertThat( result.toJSON(), equalTo( "{\"xyz\":\"bar\"}" ) );
}
@Test
public void testPostCommandWithWrongValue()
{
try
{
cqc.postCommand( "commandwithvalue", valueBuilderFactory.newValueFromJSON( TestCommand.class, "{'abc':'wrong'}" ) );
} catch (ResourceException e)
{
assertThat( e.getStatus().getDescription(), equalTo( "Wrong argument" ) );
}
}
@Test
public void testPostCommandWithRightValue()
{
cqc.postCommand("commandwithvalue", valueBuilderFactory.newValueFromJSON(TestCommand.class, "{'abc':'right'}"));
}
@Test
public void testPutCommandWithRightValue()
{
cqc.putCommand("idempotentcommandwithvalue", valueBuilderFactory.newValueFromJSON(TestCommand.class, "{'abc':'right'}"));
}
@Test
public void testDelete()
{
cqc.delete();
assertThat( command, equalTo( "delete" ) );
}
@Test
public void testSubResourceQueryWithValue()
{
CommandQueryClient cqc2 = cqc.getSubClient( "subresource" );
TestResult result = cqc2.query( "querywithvalue", TestResult.class, valueBuilderFactory.newValueFromJSON( TestQuery.class, "{'abc':'foo'}" ));
assertThat(result.toJSON(), equalTo("{\"xyz\":\"bar\"}"));
}
@Test
public void testInteractionValidation()
{
CommandQueryClient cqc2 = cqc.getSubClient("subresource");
LinkValue xyzLink;
{
ResourceValue result = cqc2.query();
xyzLink = Iterables.first( Iterables.filter( Links.withId( "xyz" ), result.commands().get() ) );
assertThat( xyzLink, CoreMatchers.<Object>notNullValue());
Form form = new Form();
form.set( "valid", "false" );
cqc2.postCommand( xyzLink.rel().get(), form.getWebRepresentation());
}
{
ResourceValue result = cqc2.query();
LinkValue nullLink = Iterables.first( Iterables.filter( Links.withId( "xyz" ), result.commands().get() ) );
assertThat( nullLink, CoreMatchers.<Object>nullValue());
}
try
{
cqc2.postCommand( xyzLink.rel().get(), new StringRepresentation("{valid:false}") );
Assert.fail("ResourceException should have been thrown");
} catch (ResourceException e)
{
// Ok
}
Form form = new Form();
form.set( "valid", "true" );
cqc2.postCommand("notxyz", form.getWebRepresentation());
}
@Test
public void testRootIndex()
{
ResourceValue result = cqc.query();
ResourceValue expected = moduleInstance.valueBuilderFactory().newValueFromJSON(ResourceValue.class, "{\"commands\":[{\"classes\":\"command\",\"href\":\"delete\",\"id\":\"delete\",\"rel\":\"delete\",\"text\":\"Delete\"},{\"classes\":\"command\",\"href\":\"commandwithvalue\",\"id\":\"commandwithvalue\",\"rel\":\"commandwithvalue\",\"text\":\"Command with value\"},{\"classes\":\"command\",\"href\":\"idempotentcommandwithvalue\",\"id\":\"idempotentcommandwithvalue\",\"rel\":\"idempotentcommandwithvalue\",\"text\":\"Idempotent command with value\"}],\"index\":null,\"queries\":[{\"classes\":\"query\",\"href\":\"querywithvalue\",\"id\":\"querywithvalue\",\"rel\":\"querywithvalue\",\"text\":\"Query with value\"},{\"classes\":\"query\",\"href\":\"querywithoutvalue\",\"id\":\"querywithoutvalue\",\"rel\":\"querywithoutvalue\",\"text\":\"Query without value\"},{\"classes\":\"query\",\"href\":\"querywithstringresult\",\"id\":\"querywithstringresult\",\"rel\":\"querywithstringresult\",\"text\":\"Query with string result\"},{\"classes\":\"query\",\"href\":\"querywithintegerresult\",\"id\":\"querywithintegerresult\",\"rel\":\"querywithintegerresult\",\"text\":\"Query with integer result\"}],\"resources\":[]}");
assertThat( result.commands().get().size(), equalTo( expected.commands().get().size() ));
assertThat( result.queries().get().size(), equalTo( expected.queries().get().size() ));
}
@Test
public void testSubResourceIndex()
{
CommandQueryClient cqc2 = cqc.getSubClient( "subresource" );
ResourceValue result = cqc2.query();
ResourceValue expected = moduleInstance.valueBuilderFactory().newValueFromJSON(ResourceValue.class, "{\"commands\":[{\"classes\":\"command\",\"href\":\"xyz\",\"id\":\"xyz\",\"rel\":\"xyz\",\"text\":\"Xyz\"},{\"classes\":\"command\",\"href\":\"commandwithrolerequirement\",\"id\":\"commandwithrolerequirement\",\"rel\":\"commandwithrolerequirement\",\"text\":\"Command with role requirement\"},{\"classes\":\"command\",\"href\":\"changedescription\",\"id\":\"changedescription\",\"rel\":\"changedescription\",\"text\":\"Change description\"}],\"index\":null,\"queries\":[{\"classes\":\"query\",\"href\":\"querywithvalue\",\"id\":\"querywithvalue\",\"rel\":\"querywithvalue\",\"text\":\"Query with value\"},{\"classes\":\"query\",\"href\":\"querywithrolerequirement\",\"id\":\"querywithrolerequirement\",\"rel\":\"querywithrolerequirement\",\"text\":\"Query with role requirement\"},{\"classes\":\"query\",\"href\":\"genericquery\",\"id\":\"genericquery\",\"rel\":\"genericquery\",\"text\":\"Generic query\"},{\"classes\":\"query\",\"href\":\"description\",\"id\":\"description\",\"rel\":\"description\",\"text\":\"Description\"}],\"resources\":[{\"classes\":\"resource\",\"href\":\"subresource1/\",\"id\":\"subresource1\",\"rel\":\"subresource1\",\"text\":\"Subresource 1\"},{\"classes\":\"resource\",\"href\":\"subresource2/\",\"id\":\"subresource2\",\"rel\":\"subresource2\",\"text\":\"Subresource 2\"}]}");
assertThat( result.commands().get().size(), equalTo( expected.commands().get().size() ));
assertThat( result.queries().get().size(), equalTo( expected.queries().get().size() ));
}
@Test
public void testSubResourceQueryWithRoleRequirement()
{
CommandQueryClient cqc2 = cqc.getSubClient( "subresource" );
TestResult result = cqc2.query( "querywithrolerequirement", TestResult.class, valueBuilderFactory.newValueFromJSON( TestQuery.class, "{'abc':'foo'}" ));
assertThat( result.toJSON(), equalTo( "{\"xyz\":\"bar\"}" ) );
}
@Test
public void testSubResourceGenericQuery()
{
CommandQueryClient cqc2 = cqc.getSubClient( "subresource" );
TestResult result = cqc2.query( "genericquery", TestResult.class, valueBuilderFactory.newValueFromJSON( TestQuery.class, "{'abc':'foo'}" ));
assertThat( result.toJSON(), equalTo( "{\"xyz\":\"bar\"}" ) );
}
@Test
public void testSubResourceCompositeCommandQuery()
{
CommandQueryClient cqc2 = cqc.getSubClient( "subresource" );
Form form = new Form();
form.set("description", "foo");
cqc2.postCommand( "changedescription", form );
String result = cqc2.query( "description", String.class );
assertThat( result, equalTo( "foo" ) );
}
@Test
public void testContext()
{
DescribableContext context = objectBuilderFactory.newObjectBuilder(DescribableContext.class).use(transientBuilderFactory.newTransient(TestComposite.class )).newInstance();
context.changeDescription( "Foo" );
assertThat(context.description(), equalTo("Foo"));
}
public interface TestQuery
extends ValueComposite
{
@UseDefaults
Property<String> abc();
}
public interface TestCommand
extends ValueComposite
{
@UseDefaults
Property<String> abc();
}
public interface TestResult
extends ValueComposite
{
Property<String> xyz();
}
public static class RootRestlet
extends CommandQueryRestlet
{
@Override
protected Uniform createRoot( Request request, Response response )
{
return module.objectBuilderFactory().newObjectBuilder( RootResource.class ).use(this).newInstance();
}
}
public static class RootResource
extends CommandQueryResource
implements SubResources
{
private static TestComposite instance;
public RootResource()
{
super( RootContext.class );
}
public TestResult querywithvalue(TestQuery testQuery ) throws Throwable
{
return context(RootContext.class).queryWithValue(testQuery);
}
public TestResult querywithoutvalue( ) throws Throwable
{
return context(RootContext.class).queryWithoutValue();
}
public String querywithstringresult(TestQuery query ) throws Throwable
{
return context(RootContext.class).queryWithStringResult(query);
}
public void commandwithvalue( TestCommand command ) throws Throwable
{
context(RootContext.class).commandWithValue(command);
}
public void resource( String currentSegment )
{
RoleMap roleMap = RoleMap.current();
roleMap.set( new File( "" ) );
if (instance == null)
roleMap.set( instance = module.transientBuilderFactory().newTransient( TestComposite.class ) );
else
roleMap.set( instance );
subResource( SubResource1.class );
}
}
public static class SubResource1
extends CommandQueryResource
{
public SubResource1()
{
super( SubContext.class, SubContext2.class, DescribableContext.class );
}
public TestResult genericquery( TestQuery query ) throws Throwable
{
return context(SubContext2.class).genericQuery(query);
}
public TestResult querywithvalue(TestQuery query ) throws Throwable
{
return context(SubContext.class).queryWithValue(query);
}
@SubResource
public void subresource1( )
{
subResource( SubResource1.class );
}
@SubResource
public void subresource2( )
{
subResource( SubResource1.class );
}
}
public static class RootContext
implements DeleteContext
{
private static int count = 0;
@Structure
Module module;
public TestResult queryWithValue( TestQuery query )
{
return module.valueBuilderFactory().newValueFromJSON(TestResult.class, "{'xyz':'bar'}");
}
public TestResult queryWithoutValue()
{
return module.valueBuilderFactory().newValueFromJSON(TestResult.class, "{'xyz':'bar'}");
}
public String queryWithStringResult( TestQuery query )
{
return "bar";
}
public int queryWithIntegerResult( TestQuery query )
{
return 7;
}
public void commandWithValue( TestCommand command )
{
if (!command.abc().get().equals( "right" ))
throw new IllegalArgumentException( "Wrong argument" );
// Done
}
public void idempotentCommandWithValue( TestCommand command ) throws ConcurrentEntityModificationException
{
// On all but every third invocation, throw a concurrency exception
// This is to test retries on the server-side
count++;
if (count%3 != 0)
{
module.unitOfWorkFactory().currentUnitOfWork().addUnitOfWorkCallback( new UnitOfWorkCallback()
{
public void beforeCompletion() throws UnitOfWorkCompletionException
{
throw new ConcurrentEntityModificationException( Collections.<EntityComposite>emptyList());
}
public void afterCompletion( UnitOfWorkStatus status )
{
}
});
}
if (!command.abc().get().equals( "right" ))
throw new IllegalArgumentException( "Wrong argument" );
// Done
}
public void delete() throws ResourceException, IOException
{
// Ok!
command = "delete";
}
}
public static class SubContext
implements InteractionValidation
{
@Structure
Module module;
public TestResult queryWithValue( TestQuery query )
{
return module.valueBuilderFactory().newValueFromJSON(TestResult.class, "{'xyz':'bar'}");
}
// Test interaction constraints
@Requires(File.class)
public TestResult queryWithRoleRequirement( TestQuery query )
{
return module.valueBuilderFactory().newValueFromJSON(TestResult.class, "{'xyz':'bar'}");
}
@Requires(File.class)
public void commandWithRoleRequirement()
{
}
// Interaction validation
private static boolean xyzValid = true;
@RequiresValid("xyz")
public void xyz(@Name("valid") boolean valid)
{
xyzValid = valid;
}
@RequiresValid("notxyz")
public void notxyz(@Name("valid") boolean valid)
{
xyzValid = valid;
}
public boolean isValid( String name )
{
if (name.equals("xyz"))
return xyzValid;
else if (name.equals( "notxyz" ))
return !xyzValid;
else
return false;
}
}
public static class SubContext2
{
@Structure
Module module;
public TestResult genericQuery( TestQuery query )
{
return module.valueBuilderFactory().newValueFromJSON(TestResult.class, "{'xyz':'bar'}");
}
}
public static class DescribableContext
{
@Structure
Module module;
Describable describable = new Describable();
public void bind(@Uses DescribableData describableData)
{
describable.bind(describableData);
}
public String description()
{
return describable.description();
}
public void changeDescription( @Name("description") String newDesc )
{
describable.changeDescription(newDesc);
}
public static class Describable
extends Role<DescribableData>
{
public void changeDescription( String newDesc )
{
self.description().set( newDesc );
}
public String description()
{
return self.description().get();
}
}
}
public interface DescribableData
{
@UseDefaults
Property<String> description();
}
public interface TestComposite
extends TransientComposite, DescribableData
{
@Optional
Property<String> foo();
}
}