/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.drools.compiler.factmodel.traits;
import org.drools.core.common.ProjectClassLoader;
import org.drools.core.factmodel.traits.LogicalTypeInconsistencyException;
import org.drools.core.factmodel.traits.Trait;
import org.drools.core.factmodel.traits.TraitFactory;
import org.drools.core.factmodel.traits.Traitable;
import org.drools.core.factmodel.traits.VirtualPropertyMode;
import org.drools.core.util.StandaloneTraitFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.kie.api.definition.type.PropertyReactive;
import org.kie.api.io.ResourceType;
import org.kie.internal.KnowledgeBase;
import org.kie.internal.KnowledgeBaseFactory;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;
import org.kie.internal.runtime.StatefulKnowledgeSession;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@RunWith(Parameterized.class)
public class LegacyTraitTest {
public VirtualPropertyMode mode;
@Parameterized.Parameters
public static Collection modes() {
return Arrays.asList( new VirtualPropertyMode[][]
{
{ VirtualPropertyMode.MAP },
{ VirtualPropertyMode.TRIPLES }
} );
}
public LegacyTraitTest( VirtualPropertyMode m ) {
this.mode = m;
}
private StatefulKnowledgeSession getSessionFromString( String drl ) {
KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
knowledgeBuilder.add( ResourceFactory.newByteArrayResource( drl.getBytes() ),
ResourceType.DRL );
if (knowledgeBuilder.hasErrors()) {
throw new RuntimeException( knowledgeBuilder.getErrors().toString() );
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( knowledgeBuilder.getKnowledgePackages() );
StatefulKnowledgeSession session = kbase.newStatefulKnowledgeSession();
return session;
}
// Getters and setters are both needed. They should refer to an attribute with the same name
public static class PatientImpl implements Patient {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
public static interface Pers {
public String getName();
}
public static interface Patient extends Pers {
}
public static interface Procedure {
public Patient getSubject();
public void setSubject(Patient p);
}
public static interface ExtendedProcedure extends Procedure {
public Pers getPers();
public void setPers(Pers pers);
}
public class ProcedureImpl implements Procedure {
public ProcedureImpl() {
}
private Patient subject;
@Override
public Patient getSubject() {
return this.subject;
}
public void setSubject(Patient patient) {
this.subject = patient;
}
}
public class ExtendedProcedureImpl extends ProcedureImpl implements ExtendedProcedure {
public ExtendedProcedureImpl() {
}
private Pers pers;
@Override
public Pers getPers() {
return this.pers;
}
public void setPers(Pers pers) {
this.pers = pers;
}
}
@Test
public void traitWithPojoInterface() {
String source = "package org.drools.compiler.test;" +
"import org.drools.compiler.factmodel.traits.LegacyTraitTest.Procedure; " +
"import org.drools.compiler.factmodel.traits.LegacyTraitTest; " +
"import org.drools.compiler.factmodel.traits.LegacyTraitTest.ExtendedProcedureImpl; " +
"import org.drools.compiler.factmodel.traits.LegacyTraitTest.ExtendedProcedure; " +
// enhanced so that declaration is not needed
// "declare ProcedureImpl end " +
"declare trait ExtendedProcedure " +
" @role( event )" +
"end " +
// Surgery must be declared as trait, since it does not extend Thing
"declare trait Surgery extends ExtendedProcedure end " +
"declare ExtendedProcedureImpl " +
" @Traitable " +
"end " +
"rule 'Don Procedure' " +
"when " +
" $p : ExtendedProcedure() " +
"then " +
" don( $p, Surgery.class ); " +
"end " +
"rule 'Test 1' " +
"dialect 'mvel' " +
"when " +
" $s1 : ExtendedProcedure( $subject : subject ) " +
" $s2 : ExtendedProcedure( subject == $subject ) " +
"then " +
"end " +
"rule 'Test 2' " +
"dialect 'mvel' " +
"when " +
" $s1 : ExtendedProcedure( $subject : subject.name ) " +
" $s2 : ExtendedProcedure( subject.name == $subject ) " +
"then " +
"end " +
"rule 'Test 3' " +
"dialect 'mvel' " +
"when " +
" $s1 : ExtendedProcedure( ) " +
"then " +
" update( $s1 ); " +
"end " +
"\n";
StatefulKnowledgeSession ks = getSessionFromString( source );
TraitFactory.setMode( mode, ks.getKieBase() );
ExtendedProcedureImpl procedure1 = new ExtendedProcedureImpl();
ExtendedProcedureImpl procedure2 = new ExtendedProcedureImpl();
PatientImpl patient1 = new PatientImpl();
patient1.setName("John");
procedure1.setSubject( patient1 );
procedure1.setPers(new PatientImpl());
PatientImpl patient2 = new PatientImpl();
patient2.setName("John");
procedure2.setSubject( patient2 );
procedure2.setPers(new PatientImpl());
ks.insert( procedure1 );
ks.insert( procedure2 );
ks.fireAllRules( 500 );
}
@Traitable
@PropertyReactive
public static class BarImpl implements Foo { }
public static interface Root { }
public static interface Trunk extends Root { }
@PropertyReactive
@Trait
public static interface Foo extends Trunk { }
@Test
public void traitWithMixedInterfacesExtendingEachOther() {
String source = "package org.drools.compiler.test;" +
"import " + BarImpl.class.getCanonicalName() + "; " +
"import " + Foo.class.getCanonicalName() + "; " +
"import " + Trunk.class.getCanonicalName() + "; " +
"global java.util.List list; " +
// We need to redeclare the interfaces as traits, the annotation on the original class is not enough here
"declare trait Foo end " +
// notice that the declarations do not include supertypes, and are out of order. The engine will figure out what to do
"declare trait Root end " +
"declare trait Foo2 extends Foo " +
" @propertyReactive " +
"end " +
"rule 'Bar Don'" +
"when " +
" $b : BarImpl( this isA Foo.class, this not isA Foo2.class )\n" +
" String()\n" +
"then " +
" list.add( 3 ); " +
" retract( $b ); " +
"end " +
"rule 'Don Bar' " +
"no-loop " +
"when " +
" $b : Foo( ) " +
"then " +
" list.add( 1 ); " +
" don( $b, Foo2.class ); " +
"end " +
"rule 'Cant really shed Foo but Foo2' " +
"when " +
" $b : Foo2() " +
"then " +
" list.add( 2 ); " +
" shed( $b, Foo.class ); " +
" insert( \"done\" );" +
"end " +
"";
StatefulKnowledgeSession ks = getSessionFromString( source );
TraitFactory.setMode( mode, ks.getKieBase() );
ArrayList list = new ArrayList();
ks.setGlobal( "list", list );
ks.insert( new BarImpl() );
int n = ks.fireAllRules();
System.out.println( list );
assertEquals( Arrays.asList( 1, 2, 3 ), list );
assertEquals( 3, n );
}
@Test
public void testTraitWithNonAccessorMethodShadowing() {
StandaloneTraitFactory factory = new StandaloneTraitFactory( ProjectClassLoader.createProjectClassLoader() );
try {
SomeInterface r = (SomeInterface) factory.don( new SomeClass(), SomeInterface.class );
r.prepare();
assertEquals( 42, r.getFoo() );
assertEquals( "I did that", r.doThis( "that" ) );
} catch ( LogicalTypeInconsistencyException e ) {
e.printStackTrace();
fail( e.getMessage() );
}
}
}