WillShackleford
WillShackleford

Reputation: 7018

Exception Child services have no parent when starting Optaplanner application

I have a test program that uses optaplanner. There is no direct use of KIE API's but it looks like they are being invoked behind the scenes. This may be related to the fact that I am using DROOLS for the score calculation. The program works from the IDE or through maven, but I want to create a standalone jar that will not require maven. I used the maven assembly plugin to build a fat jar with all dependencies included to be run standalone.

When I run java -jar target/OptaPlannerTest-1.4-SNAPSHOT-jar-with-dependencies.jar I get :

Exception in thread "main" java.lang.ExceptionInInitializerError
        at org.kie.api.internal.utils.ServiceRegistry.getInstance(ServiceRegistry.java:27)
        at org.kie.api.KieServices$Factory$LazyHolder.<clinit>(KieServices.java:332)
        at org.kie.api.KieServices$Factory.get(KieServices.java:339)
        at org.optaplanner.core.config.score.director.ScoreDirectorFactoryConfig.buildDroolsScoreDirectorFactory(ScoreDirectorFactoryConfig.java:460)
        at org.optaplanner.core.config.score.director.ScoreDirectorFactoryConfig.buildScoreDirectorFactory(ScoreDirectorFactoryConfig.java:331)
        at org.optaplanner.core.config.solver.SolverConfig.buildSolver(SolverConfig.java:220)
        at org.optaplanner.core.impl.solver.AbstractSolverFactory.buildSolver(AbstractSolverFactory.java:61)
        at com.github.wshackle.optaplannertest.Main.main(Main.java:38)
Caused by: java.lang.RuntimeException: Child services [org.kie.api.internal.assembler.KieAssemblers] have no parent
        at org.kie.api.internal.utils.ServiceDiscoveryImpl.buildMap(ServiceDiscoveryImpl.java:186)
        at org.kie.api.internal.utils.ServiceDiscoveryImpl.getServices(ServiceDiscoveryImpl.java:97)
        at org.kie.api.internal.utils.ServiceRegistryImpl.<init>(ServiceRegistryImpl.java:36)
        at org.kie.api.internal.utils.ServiceRegistryImpl$LazyHolder.<clinit>(ServiceRegistryImpl.java:32)

Line 38 of Main.java is only two lines into the application, so all it has done is load the config file and try to build the solver.

    SolverFactory<Plan> solverFactory = SolverFactory.createFromXmlResource(
            "com/github/wshackle/optaplannertest/solverConfig.xml");
    Solver<Plan> solver = solverFactory.buildSolver();

solverConfig.xml is:

<solver>
  <!-- Domain model configuration -->
   <scanAnnotatedClasses>
    <packageInclude>com.github.wshackle.optaplannertest.model</packageInclude>
  </scanAnnotatedClasses>


  <!-- Score configuration -->
  <scoreDirectorFactory>
      <scoreDrl>com/github/wshackle/optaplannertest/scoreRules.drl</scoreDrl>
  </scoreDirectorFactory>

  <!-- Optimization algorithms configuration -->
  <termination>
    <secondsSpentLimit>5</secondsSpentLimit>
  </termination>
</solver>

In cast it is relevant my pom is this:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.github.wshackle</groupId>
    <artifactId>OptaPlannerTest</artifactId>
    <version>1.4-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <optiplanner.version>7.3.0.Final</optiplanner.version>
        <main.class>com.github.wshackle.optaplannertest.Main</main.class>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.optaplanner</groupId>
            <artifactId>optaplanner-core</artifactId>
            <version>${optiplanner.version}</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>${optiplanner.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.25</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <scope>runtime</scope>
            <version>1.2.3</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.5.5</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>${main.class}</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
                        <phase>package</phase> <!-- bind to the packaging phase -->
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The complete list of files in the jar is shown at https://gist.github.com/wshackle/8887aac8a10e8c4b1f862a4bda288e41

I used grep to verify they seem to include the expected classes for each jar dependancy:

> grep -c org/kie/api jarlisting.txt 
391
> grep -c org/kie/internal jarlisting.txt 
364
> grep -c org/optaplanner/core jarlisting.txt 
841
> grep -c org/drools/core jarlisting.txt 
2175
> grep -c org/drools/compiler jarlisting.txt 
832

Upvotes: 3

Views: 1224

Answers (6)

S.K.UZUN
S.K.UZUN

Reputation: 41

I have also met this error but i found a solution with different way. Instead of generating a jar file with all dependencies, generating jar file with library folder of dependencies is easier way for getting runnable jar file. To produce jar file with lib folder modify your pom.xml file as indicated below.

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-dependency-plugin</artifactId>
 <executions>
   <execution>
    <id>copy-dependencies</id>
    <phase>prepare-package</phase>
    <goals>
     <goal>copy-dependencies</goal>
    </goals>
    <configuration>
     <outputDirectory>${project.build.directory}/lib</outputDirectory>
     <overWriteReleases>false</overWriteReleases>
     <overWriteSnapshots>false</overWriteSnapshots>
     <overWriteIfNewer>true</overWriteIfNewer>
    </configuration>
   </execution>
  </executions>
 </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
  <configuration>
   <archive>
    <manifest>
     <addClasspath>true</addClasspath>
     <classpathPrefix>lib/</classpathPrefix>
     <mainClassMain-Class</mainClass>
    </manifest>
   </archive>
  </configuration>
</plugin>

Upvotes: 0

wasperen
wasperen

Reputation: 103

I agree that the problem is as @WillSchackleford describes:

The problem is that the following jar files all contain different versions of META-INF/kie.conf:

   optaplanner-core-7.3.0.Final.jar
   kie-internal/7.3.0.Final/kie-internal-7.3.0.Final.jar 
   drools-core-7.3.0.Final.jar
   drools-compiler-7.3.0.Final.jar

When the maven-assembly-plugin puts them together only one version of the META-INF/kie.conf can be included.

The best trick to combine all of these kie.conf files is to use a transformer in your maven config, like so:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <!-- get all project dependencies -->
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <!-- MainClass in mainfest make a executable jar -->
        <archive>
            <manifest>
                <mainClass>com.paconsulting.powerpeers.PowerPeersDemo</mainClass>
            </manifest>
        </archive>
        <transformers>
            <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                <resource>META-INF/kie.conf</resource>
            </transformer>
        </transformers>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <!-- bind to the packaging phase -->
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

This creates one META-INF/kie.conf file with the content of any of the kie.conf files it finds.

Upvotes: 2

Charles Brocchiero
Charles Brocchiero

Reputation: 303

For people using maven-shade-plugin, here is a proposed fix that is going to merge the META-INF/kie.conf that are duplicated into a single file using the AppendingTransformer https://stackoverflow.com/a/53273253/5903731

Upvotes: 0

WillShackleford
WillShackleford

Reputation: 7018

The problem is that the following jar files all contain different versions of META-INF/kie.conf:

optaplanner-core-7.3.0.Final.jar
kie-internal/7.3.0.Final/kie-internal-7.3.0.Final.jar 
drools-core-7.3.0.Final.jar
drools-compiler-7.3.0.Final.jar

When the maven-assembly-plugin puts them together only one version of the META-INF/kie.conf can be included. When building the solver the Optaplanner library will indirectly call getResources("META-INF/kie.conf") on the current Thread context classloader. If there are multiple jars on the classpath then all of them will be found and the resulting configuration will be the product of parsing all of them. In order to make this work in a single fat uber jar the kie.conf files need to be moved to different file names and a classloader overloaded to direct the library to use them at the new names. (It might also be possible to combine them into a single kie.conf file)

Extract and move the kie.conf files:

 jar -xf ~/.m2/repository/org/optaplanner/optaplanner-core/7.3.0.Final/optaplanner-core-7.3.0.Final.jar META-INF/kie.conf
 mv META-INF/kie.conf src/main/resources/optaplanner-core-kie.conf
 jar -xf ~/.m2/repository/org/kie/kie-internal/7.3.0.Final/kie-internal-7.3.0.Final.jar META-INF/kie.conf
 mv META-INF/kie.conf src/main/resources/kie-internal-kie.conf
 jar -xf ~/.m2/repository/org/drools/drools-core/7.3.0.Final/drools-core-7.3.0.Final.jar META-INF/kie.conf
 mv META-INF/kie.conf src/main/resources/drools-core-kie.conf
 jar -xf ~/.m2/repository/org/drools/drools-compiler/7.3.0.Final/drools-compiler-7.3.0.Final.jar META-INF/kie.conf
 mv META-INF/kie.conf src/main/resources/drools-compiler-kie.conf

Then overload and set the thread context loader.

    ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
    URL[] localKieConfUrls = new URL[]{
        ClassLoader.getSystemResource("optaplanner-core-kie.conf"),
        ClassLoader.getSystemResource("kie-internal-kie.conf"),
        ClassLoader.getSystemResource("drools-core-kie.conf"),
        ClassLoader.getSystemResource("drools-compiler-kie.conf")
    };
    ClassLoader newClassLoader = new ClassLoader(oldClassLoader) {

        private final URL[] kieConfUrls = localKieConfUrls;
        @Override
        public Enumeration<URL> getResources(String name) throws IOException {
            if ("META-INF/kie.conf".equals(name)) {
                return new Enumeration<URL>() {
                    int index;
                    @Override
                    public boolean hasMoreElements() {
                        return index < kieConfUrls.length;
                    }

                    @Override
                    public URL nextElement() {
                        return kieConfUrls[index++];
                    }
                };
            }
            return super.getResources(name);
        }

    };
    Thread.currentThread().setContextClassLoader(newClassLoader);

Upvotes: 3

kafsinkaf
kafsinkaf

Reputation: 11

I was having the same problem with the following pom

    <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
..
    <dependencies>
        <dependency>
            <groupId>org.optaplanner</groupId>
            <artifactId>optaplanner-core</artifactId>
            <version>${optaPlanner.version}</version>
        </dependency>    
...
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>${jar.plugin.version}</version>
                <configuration>
                    <archive>
                        <addMavenDescriptor>false</addMavenDescriptor>
                        <compress>false</compress>                
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>libs/</classpathPrefix>
                            <mainClass>${mainClass}</mainClass>
                        </manifest> 
                        <index>true</index>
                        <manifestEntries>
                            <impl-version>${project.version}</impl-version>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
           <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>${dependency.plugin.version}</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>${assembly.plugin.version}</version>
                <configuration>
                    <descriptors>
                        <descriptor>assembly/release.xml</descriptor>
                    </descriptors>
                    <finalName>${distribution.file.name}</finalName>
                    <outputDirectory>${project.build.directory}/dist</outputDirectory>
                    <workDirectory>${project.build.directory}/assembly/work</workDirectory>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

Removing

<index>true</index>

solved my problem. Hope this helps others as well.

Upvotes: 1

Geoffrey De Smet
Geoffrey De Smet

Reputation: 27312

Run "mvn dependency:tree" and you'll see that optaplanner-core depends on kie-api, kie-internal-api, drools-core and drools-compiler. One of those will be missing in your fat jar.

Upvotes: 1

Related Questions