mmnaseri / spring-data-mock

Mock facility for Spring Data repositories
MIT License
135 stars 44 forks source link

Support for query by example is broken #78

Closed paulvi closed 8 years ago

paulvi commented 8 years ago

For code case in #69 #54 with 1.1.1

com.mmnaseri.utils.spring.data.error.DataOperationDefinitionException: Encountered an error while resolving operation metadata: public abstract java.util.List com.bee2c.platform.repository.FbpMbpPolicyRepositoryCustom.findByExample(com.bee2c.platform.entity.FbpMbpPolicy)
    at com.mmnaseri.utils.spring.data.proxy.impl.resolvers.DefaultDataOperationResolver.resolve(DefaultDataOperationResolver.java:45)
    at com.mmnaseri.utils.spring.data.proxy.impl.DefaultRepositoryFactory.getInvocationMappings(DefaultRepositoryFactory.java:210)
    at com.mmnaseri.utils.spring.data.proxy.impl.DefaultRepositoryFactory.getInstance(DefaultRepositoryFactory.java:76)
    at com.mmnaseri.utils.spring.data.dsl.mock.RepositoryMockBuilder.mock(RepositoryMockBuilder.java:103)
    at com.mmnaseri.utils.spring.data.dsl.mock.RepositoryMockBuilder.mock(RepositoryMockBuilder.java:101)
    at com.mmnaseri.utils.spring.data.dsl.factory.RepositoryFactoryBuilder.mock(RepositoryFactoryBuilder.java:273)
    at com.bee2c.platform.MbpDatabaseRelationsTestManual.testDemo(MbpDatabaseRelationsTestManual.java:24)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: com.mmnaseri.utils.spring.data.error.QueryParserException: interface com.bee2c.platform.repository.FbpMbpPolicyRepositoryCustom: Could not find property `example` on `class com.bee2c.platform.entity.FbpMbpPolicy`
    at com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor.getPropertyDescriptor(MethodQueryDescriptionExtractor.java:264)
    at com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor.parseExpression(MethodQueryDescriptionExtractor.java:206)
    at com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor.extract(MethodQueryDescriptionExtractor.java:134)
    at com.mmnaseri.utils.spring.data.proxy.impl.resolvers.QueryMethodDataOperationResolver.resolve(QueryMethodDataOperationResolver.java:53)
    at com.mmnaseri.utils.spring.data.proxy.impl.resolvers.DefaultDataOperationResolver.resolve(DefaultDataOperationResolver.java:43)
    ... 29 more
Caused by: java.lang.IllegalStateException: Could not find property `example` on `class com.bee2c.platform.entity.FbpMbpPolicy`
    at com.mmnaseri.utils.spring.data.tools.PropertyUtils.getPropertyDescriptor(PropertyUtils.java:217)
    at com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor.getPropertyDescriptor(MethodQueryDescriptionExtractor.java:262)
    ... 33 more
mmnaseri commented 8 years ago

So this is basically #56 recurring. I think the code I checked in for checking whether or not support for querying by example should be enabled is not working properly. This should be isolated to an issue in DefaultTypeMappingContext.

I will create a separate project with your sample and try to get it to break. Currently the regression test I had written for #56 passes without any problems.

mmnaseri commented 8 years ago

Would you mind sharing what dependencies you have? I am assuming nothing but spring-data-* and some other unrelated libraries?

paulvi commented 8 years ago

app pom

    <properties>
        <spring-boot.version>1.3.5.RELEASE</spring-boot.version>
        <!-- for findByExample while Spring Boot does not includes spring-data-commons v1.12 
        <spring-data-releasetrain.version>Hopper-SR1</spring-data-releasetrain.version>
        -->
        <start-class>com.bee2c.platform.MbpApplication</start-class>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- 1.12 is higher than Hopper-SR1 -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
            <version>1.12.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.10.1.RELEASE</version>       
        </dependency>
        <!--
        <dependency>
            <groupId>com.mysema.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>3.4.3</version>
        </dependency>
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.11</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.mmnaseri.utils</groupId>
            <artifactId>spring-data-mock</artifactId>
            <version>1.1.1</version>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <artifactId>commons-logging</artifactId>
                    <groupId>commons-logging</groupId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

and parent

    <description>Parent (shared settings) for BEE2C Spring Boot apps</description>

    <dependencies>
            <!-- for Java6: exclude any transitive dependencies on javax.transaction:javax.transaction-api 
            and replace them with a dependency on org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.0.Final -->
            <dependency>
                <groupId>org.jboss.spec.javax.transaction</groupId>
                <artifactId>jboss-transaction-api_1.2_spec</artifactId>
            </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>${start-class}</mainClass>
                    <layout>ZIP</layout>
                </configuration>                
            </plugin>
        </plugins>
    </build>
</project>

root pom.xml

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.6</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <spring-boot.version>1.3.3.RELEASE</spring-boot.version>
        <tomcat.version>7.0.59</tomcat.version> <!-- for Java6: default Tomcat 8 requires Java 7-->
        <!-- for Java6: JTA API compatibility - see dependencyManagement section
While the Java Transaction API itself doesn’t require Java 7 the official API jar contains classes that
have been built to require Java 7. If you are using JTA then you will need to replace the official JTA 1.2
API jar with one that has been built to work on Java 6. To do so, exclude any transitive dependencies
on javax.transaction:javax.transaction-api and replace them with a dependency on
org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.0.Final
         -->
    </properties>
    <packaging>pom</packaging>
    <modules>
        ...
    </modules>

    <dependencyManagement>
         <dependencies>
            <dependency>
                <!-- Import dependency management from Spring Boot -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- for Java6: from spring-boot-starter-tomcat -->       
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-core</artifactId>
                <version>${tomcat.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-el</artifactId>
                <version>${tomcat.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-logging-juli</artifactId>
                <version>${tomcat.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-websocket</artifactId>
                <version>${tomcat.version}</version>
            </dependency>
            <!-- for Java6: 2 more from spring-boot-starter-jdbc--> 
            <dependency>
                <groupId>org.apache.tomcat</groupId>
                <artifactId>tomcat-jdbc</artifactId>
                <version>${tomcat.version}</version>
            </dependency>
            <dependency>
                <groupId>org.apache.tomcat</groupId>
                <artifactId>tomcat-juli</artifactId>
                <version>${tomcat.version}</version>
            </dependency>
            <!-- for Java6: exclude any transitive dependencies on javax.transaction:javax.transaction-api 
            and replace them with a dependency on org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.0.Final -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
                <version>${spring-boot.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>javax.transaction</groupId>
                        <artifactId>javax.transaction-api</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.jboss.spec.javax.transaction</groupId>
                <artifactId>jboss-transaction-api_1.2_spec</artifactId>
                <version>1.0.0.Final</version>
            </dependency>

            <dependency>
                <groupId>commons-httpclient</groupId>
                <artifactId>commons-httpclient</artifactId>
                <version>3.1</version>
                <exclusions>
                    <exclusion>
                        <artifactId>commons-logging</artifactId>
                        <groupId>commons-logging</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
          <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${spring-boot.version}</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>                   
                </plugin>

                <!-- fix warning: maven-enforcer-plugin (goal "enforce") is ignored by m2e. 
                -->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                      <lifecycleMappingMetadata>
                        <pluginExecutions>
                          <pluginExecution>
                            <pluginExecutionFilter>
                              <groupId>org.apache.maven.plugins</groupId>
                              <artifactId>maven-enforcer-plugin</artifactId>
                              <versionRange>[1.0.0,)</versionRange>
                              <goals>
                                <goal>enforce</goal>
                              </goals>
                            </pluginExecutionFilter>
                            <action>
                              <ignore />
                            </action>
                          </pluginExecution>
                        </pluginExecutions>
                      </lifecycleMappingMetadata>
                    </configuration>
                </plugin>               
            </plugins>      
        </pluginManagement>

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-enforcer-plugin</artifactId>
                <version>1.3.1</version>
                <executions>
                    <execution>
                        <id>enforce-version</id>
                        <goals>
                            <goal>enforce</goal>
                        </goals>
                        <configuration><!-- as in spring-boot-starters pom -->
                            <rules>
                                <bannedDependencies>
                                    <excludes>
                                        <!-- exclude all log4j:log4j, commons-logging -->                                   
                                        <exclude>log4j:log4j:[0.0,9)</exclude>
                                        <exclude>commons-logging:*:*</exclude>
                                    </excludes>
                                    <searchTransitive>true</searchTransitive>
                                </bannedDependencies>
                            </rules>
                            <fail>true</fail>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>
mmnaseri commented 8 years ago

Thanks. I'm gonna build on top of this to make sure I am unblocking you.

mmnaseri commented 8 years ago

QueryByExampleExecutor (which is what I look for) was introduced in spring-data-commons:1.12.0.RELEASE. When you use spring-boot-starter-data-jpa:1.3.3.RELEASE, it includes spring-data-commons:1.9.4.RELEASE, which doesn't include said interface, and as such, spring-data-mock assumes that Querying by examples is not enabled.

This can be remedied by adding said versions of the dependencies explicitly at a higher priority than the Spring Boot artifacts in your POM.

That being said, the query parser method does not currently support querying by example without the method being derive from the QueryByExampleExecutor interface (same as with Spring Data itself). to support custom methods, you will need to provide custom implementations.

For instance, the custom implementation for the code in #56 would be:

public class CustomerRepositoryExampleSupport implements RepositoryAware<CustomerRepository> {

    private CustomerRepository repository;

    public List<Customer> findByExample(Example<Customer> example) {
        return repository.findAll(example);
    }

    @Override
    public void setRepository(CustomerRepository repository) {
        this.repository = repository;
    }

}

which you must then register as a custom implementation prior to instantiating the mock:

final CustomerRepository repository = builder()
    .usingImplementation(CustomerRepositoryExampleSupport.class)
    .mock(CustomerRepository.class);

The reasons I cannot reuse your custom implementation are twofold:

  1. I cannot support dependency injection (I don't want spring-data-mock to have any dependencies on Spring Core and Spring Context)
  2. I cannot guarantee that the code written in the custom implementation does not do anything outside the scope of the mock repositories without very thorough (and for the most part experimental) code analysis which is -- I feel -- beyond the scope of this project.
paulvi commented 8 years ago

too many things here to continue in this thread.