Skip to content

Commit

Permalink
PLANNER-387 Add optional classloader parameter into SolverFactory met…
Browse files Browse the repository at this point in the history
…hods, especially for OSGi
  • Loading branch information
ge0ffrey committed Sep 10, 2015
1 parent 815c99a commit 41542aa
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 45 deletions.
Expand Up @@ -42,39 +42,73 @@ public abstract class SolverFactory {
* @return never null
*/
public static SolverFactory createFromXmlResource(String solverConfigResource) {
XStreamXmlSolverFactory solverFactory = new XStreamXmlSolverFactory();
solverFactory.configure(solverConfigResource);
return solverFactory;
return new XStreamXmlSolverFactory().configure(solverConfigResource);
}

/**
* See {@link #createFromXmlResource(String)}.
* @param solverConfigResource never null, a classpath resource
* as defined by {@link ClassLoader#getResource(String)}
* @param classLoader sometimes null, the {@link ClassLoader} to use for loading all resources and {@link Class}es,
* null to use the default {@link ClassLoader}
* @return never null
*/
public static SolverFactory createFromXmlResource(String solverConfigResource, ClassLoader classLoader) {
return new XStreamXmlSolverFactory(classLoader).configure(solverConfigResource);
}

/**
* @param solverConfigFile never null
* @return never null
*/
public static SolverFactory createFromXmlFile(File solverConfigFile) {
XStreamXmlSolverFactory solverFactory = new XStreamXmlSolverFactory();
solverFactory.configure(solverConfigFile);
return solverFactory;
return new XStreamXmlSolverFactory().configure(solverConfigFile);
}

/**
* @param solverConfigFile never null
* @param classLoader sometimes null, the {@link ClassLoader} to use for loading all resources and {@link Class}es,
* null to use the default {@link ClassLoader}
* @return never null
*/
public static SolverFactory createFromXmlFile(File solverConfigFile, ClassLoader classLoader) {
return new XStreamXmlSolverFactory(classLoader).configure(solverConfigFile);
}

/**
* @param in never null, gets closed
* @return never null
*/
public static SolverFactory createFromXmlInputStream(InputStream in) {
XStreamXmlSolverFactory solverFactory = new XStreamXmlSolverFactory();
solverFactory.configure(in);
return solverFactory;
return new XStreamXmlSolverFactory().configure(in);
}

/**
* @param in never null, gets closed
* @param classLoader sometimes null, the {@link ClassLoader} to use for loading all resources and {@link Class}es,
* null to use the default {@link ClassLoader}
* @return never null
*/
public static SolverFactory createFromXmlInputStream(InputStream in, ClassLoader classLoader) {
return new XStreamXmlSolverFactory(classLoader).configure(in);
}

/**
* @param reader never null, gets closed
* @return never null
*/
public static SolverFactory createFromXmlReader(Reader reader) {
XStreamXmlSolverFactory solverFactory = new XStreamXmlSolverFactory();
solverFactory.configure(reader);
return solverFactory;
return new XStreamXmlSolverFactory().configure(reader);
}

/**
* @param reader never null, gets closed
* @param classLoader sometimes null, the {@link ClassLoader} to use for loading all resources and {@link Class}es,
* null to use the default {@link ClassLoader}
* @return never null
*/
public static SolverFactory createFromXmlReader(Reader reader, ClassLoader classLoader) {
return new XStreamXmlSolverFactory(classLoader).configure(reader);
}

// ************************************************************************
Expand Down
Expand Up @@ -194,7 +194,7 @@ public void setAssertionScoreDirectorFactory(ScoreDirectorFactoryConfig assertio
// Builder methods
// ************************************************************************

public InnerScoreDirectorFactory buildScoreDirectorFactory(EnvironmentMode environmentMode,
public InnerScoreDirectorFactory buildScoreDirectorFactory(ClassLoader classLoader, EnvironmentMode environmentMode,
SolutionDescriptor solutionDescriptor) {
ScoreDefinition scoreDefinition = buildScoreDefinition();
Class<? extends Score> solutionScoreClass = solutionDescriptor.extractScoreClass();
Expand All @@ -204,7 +204,7 @@ public InnerScoreDirectorFactory buildScoreDirectorFactory(EnvironmentMode envir
+ ") is the same or a superclass as the scoreDefinition's scoreClass ("
+ scoreDefinition.getScoreClass() + ").");
}
return buildScoreDirectorFactory(environmentMode, solutionDescriptor, scoreDefinition);
return buildScoreDirectorFactory(classLoader, environmentMode, solutionDescriptor, scoreDefinition);
}

public ScoreDefinition buildScoreDefinition() {
Expand Down Expand Up @@ -268,11 +268,11 @@ public ScoreDefinition buildScoreDefinition() {
}
}

protected InnerScoreDirectorFactory buildScoreDirectorFactory(EnvironmentMode environmentMode,
SolutionDescriptor solutionDescriptor, ScoreDefinition scoreDefinition) {
protected InnerScoreDirectorFactory buildScoreDirectorFactory(ClassLoader classLoader,
EnvironmentMode environmentMode, SolutionDescriptor solutionDescriptor, ScoreDefinition scoreDefinition) {
AbstractScoreDirectorFactory easyScoreDirectorFactory = buildEasyScoreDirectorFactory();
AbstractScoreDirectorFactory incrementalScoreDirectorFactory = buildIncrementalScoreDirectorFactory();
AbstractScoreDirectorFactory droolsScoreDirectorFactory = buildDroolsScoreDirectorFactory();
AbstractScoreDirectorFactory droolsScoreDirectorFactory = buildDroolsScoreDirectorFactory(classLoader);
AbstractScoreDirectorFactory scoreDirectorFactory;
if (easyScoreDirectorFactory != null) {
if (incrementalScoreDirectorFactory != null) {
Expand Down Expand Up @@ -316,7 +316,7 @@ protected InnerScoreDirectorFactory buildScoreDirectorFactory(EnvironmentMode en
+ environmentMode + ") of " + EnvironmentMode.FAST_ASSERT + " or lower.");
}
scoreDirectorFactory.setAssertionScoreDirectorFactory(
assertionScoreDirectorFactory.buildScoreDirectorFactory(
assertionScoreDirectorFactory.buildScoreDirectorFactory(classLoader,
EnvironmentMode.PRODUCTION, solutionDescriptor, scoreDefinition));
}
scoreDirectorFactory.setInitializingScoreTrend(InitializingScoreTrend.parseTrend(
Expand Down Expand Up @@ -351,7 +351,7 @@ protected AbstractScoreDirectorFactory buildIncrementalScoreDirectorFactory() {
}
}

protected AbstractScoreDirectorFactory buildDroolsScoreDirectorFactory() {
protected AbstractScoreDirectorFactory buildDroolsScoreDirectorFactory(ClassLoader classLoader) {
if (kieBase != null) {
if (!ConfigUtils.isEmptyCollection(scoreDrlList) || !ConfigUtils.isEmptyCollection(scoreDrlFileList)) {
throw new IllegalArgumentException("If kieBase is not null, the scoreDrlList (" + scoreDrlList
Expand All @@ -367,21 +367,23 @@ protected AbstractScoreDirectorFactory buildDroolsScoreDirectorFactory() {
KieResources kieResources = kieServices.getResources();
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
if (!ConfigUtils.isEmptyCollection(scoreDrlList)) {
ClassLoader actualClassLoader = (classLoader != null) ? classLoader : getClass().getClassLoader();
for (String scoreDrl : scoreDrlList) {
if (scoreDrl == null) {
throw new IllegalArgumentException("The scoreDrl (" + scoreDrl + ") cannot be null.");
}
URL scoreDrlURL = getClass().getClassLoader().getResource(scoreDrl);
URL scoreDrlURL = actualClassLoader.getResource(scoreDrl);
if (scoreDrlURL == null) {
String errorMessage = "The scoreDrl (" + scoreDrl + ") does not exist as a classpath resource.";
String errorMessage = "The scoreDrl (" + scoreDrl + ") does not exist as a classpath resource"
+ " in the classLoader (" + actualClassLoader + ").";
if (scoreDrl.startsWith("/")) {
errorMessage += "\nAs from 6.1, a classpath resource should not start with a slash (/)."
+ " A scoreDrl now adheres to ClassLoader.getResource(String)."
+ " Remove the leading slash from the scoreDrl if you're upgrading from 6.0.";
}
throw new IllegalArgumentException(errorMessage);
}
kieFileSystem.write(kieResources.newClassPathResource(scoreDrl, "UTF-8"));
kieFileSystem.write(kieResources.newClassPathResource(scoreDrl, "UTF-8", classLoader));
}
}
if (!ConfigUtils.isEmptyCollection(scoreDrlFileList)) {
Expand Down
Expand Up @@ -174,6 +174,10 @@ public EnvironmentMode determineEnvironmentMode() {
// ************************************************************************

public Solver buildSolver() {
return buildSolver(null);
}

public Solver buildSolver(ClassLoader classLoader) {
DefaultSolver solver = new DefaultSolver();
EnvironmentMode environmentMode_ = determineEnvironmentMode();
solver.setEnvironmentMode(environmentMode_);
Expand All @@ -187,7 +191,7 @@ public Solver buildSolver() {
= scoreDirectorFactoryConfig == null ? new ScoreDirectorFactoryConfig()
: scoreDirectorFactoryConfig;
InnerScoreDirectorFactory scoreDirectorFactory = scoreDirectorFactoryConfig_.buildScoreDirectorFactory(
environmentMode_, solutionDescriptor);
classLoader, environmentMode_, solutionDescriptor);
solver.setConstraintMatchEnabledPreference(environmentMode_.isAsserted());
solver.setScoreDirectorFactory(scoreDirectorFactory);

Expand Down
Expand Up @@ -52,11 +52,25 @@ public static XStream buildXStream() {
return xStream;
}

protected final ClassLoader classLoader;
protected XStream xStream;

protected SolverConfig solverConfig = null;

public XStreamXmlSolverFactory() {
this(null);
}

/**
* @param classLoader sometimes null, the {@link ClassLoader} to use for loading all resources and {@link Class}es,
* null to use the default {@link ClassLoader}
*/
public XStreamXmlSolverFactory(ClassLoader classLoader) {
this.classLoader = classLoader;
xStream = buildXStream();
if (classLoader != null) {
xStream.setClassLoader(classLoader);
}
}

/**
Expand All @@ -81,10 +95,11 @@ public XStream getXStream() {
* @return this
*/
public XStreamXmlSolverFactory configure(String solverConfigResource) {
InputStream in = getClass().getClassLoader().getResourceAsStream(solverConfigResource);
ClassLoader actualClassLoader = (classLoader != null) ? classLoader : getClass().getClassLoader();
InputStream in = actualClassLoader.getResourceAsStream(solverConfigResource);
if (in == null) {
String errorMessage = "The solverConfigResource (" + solverConfigResource
+ ") does not exist in the classpath.";
+ ") does not exist as a classpath resource in the classLoader (" + actualClassLoader + ").";
if (solverConfigResource.startsWith("/")) {
errorMessage += "\nAs from 6.1, a classpath resource should not start with a slash (/)."
+ " A solverConfigResource now adheres to ClassLoader.getResource(String)."
Expand Down Expand Up @@ -139,7 +154,7 @@ public Solver buildSolver() {
throw new IllegalStateException("The solverConfig (" + solverConfig + ") is null," +
" call configure(...) first.");
}
return solverConfig.buildSolver();
return solverConfig.buildSolver(classLoader);
}

}
@@ -0,0 +1,108 @@
/*
* Copyright 2015 JBoss Inc
*
* 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.optaplanner.core.api.solver;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Enumeration;

import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import static org.junit.Assert.*;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;

public class SolverFactoryTest {

@Test
public void testdataSolverConfig() {
SolverFactory solverFactory = SolverFactory.createFromXmlResource(
"org/optaplanner/core/api/solver/testdataSolverConfig.xml");
Solver solver = solverFactory.buildSolver();
assertNotNull(solver);
}

@Test(expected = IllegalArgumentException.class)
public void nonExistingSolverConfig() {
SolverFactory solverFactory = SolverFactory.createFromXmlResource(
"org/optaplanner/core/api/solver/nonExistingSolverConfig.xml");
Solver solver = solverFactory.buildSolver();
assertNotNull(solver);
}

@Test
public void testdataSolverConfigWithClassLoader() throws ClassNotFoundException, IOException {
ClassLoader classLoader = mockDivertingClassLoader();
SolverFactory solverFactory = SolverFactory.createFromXmlResource(
"divertThroughClassLoader/org/optaplanner/core/api/solver/classloaderTestdataSolverConfig.xml", classLoader);
Solver solver = solverFactory.buildSolver();
assertNotNull(solver);
}

protected ClassLoader mockDivertingClassLoader() throws ClassNotFoundException, IOException {
final String divertedPrefix = "divertThroughClassLoader";
final ClassLoader realClassLoader = getClass().getClassLoader();
ClassLoader divertingClassLoader = mock(ClassLoader.class);
// Mocking loadClass doesn't work well enough, because the className still differs from class.getName()
when(divertingClassLoader.loadClass(anyString())).thenAnswer(new Answer<Class<?>>() {
@Override
public Class<?> answer(InvocationOnMock invocation) throws Throwable {
String className = (String) invocation.getArguments()[0];
if (className.startsWith(divertedPrefix + ".")) {
className = className.substring(divertedPrefix.length() + 1);
}
return realClassLoader.loadClass(className);
}
});
when(divertingClassLoader.getResource(anyString())).thenAnswer(new Answer<URL>() {
@Override
public URL answer(InvocationOnMock invocation) {
String resourceName = (String) invocation.getArguments()[0];
if (resourceName.startsWith(divertedPrefix + "/")) {
resourceName = resourceName.substring(divertedPrefix.length() + 1);
}
return realClassLoader.getResource(resourceName);
}
});
when(divertingClassLoader.getResourceAsStream(anyString())).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock invocation) {
String resourceName = (String) invocation.getArguments()[0];
if (resourceName.startsWith(divertedPrefix + "/")) {
resourceName = resourceName.substring(divertedPrefix.length() + 1);
}
return realClassLoader.getResourceAsStream(resourceName);
}
});
when(divertingClassLoader.getResources(anyString())).thenAnswer(new Answer<Enumeration<URL>>() {
@Override
public Enumeration<URL> answer(InvocationOnMock invocation) throws Throwable {
String resourceName = (String) invocation.getArguments()[0];
if (resourceName.startsWith(divertedPrefix + "/")) {
resourceName = resourceName.substring(divertedPrefix.length() + 1);
}
return realClassLoader.getResources(resourceName);
}
});
// Mocking divertingClassLoader.getParent() fails because it's a final method
return divertingClassLoader;
}

}
@@ -0,0 +1,40 @@
/*
* Copyright 2010 JBoss Inc
*
* 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.optaplanner.examples.cloudbalancing.solver;
dialect "java"

import org.optaplanner.core.api.score.buildin.simple.SimpleScoreHolder;

// Using these imports statements doesn't work because the className still differs from class.getName()
//import divertThroughClassLoader.org.optaplanner.core.impl.testdata.domain.TestdataValue;
//import divertThroughClassLoader.org.optaplanner.core.impl.testdata.domain.TestdataEntity;
import org.optaplanner.core.impl.testdata.domain.TestdataValue;
import org.optaplanner.core.impl.testdata.domain.TestdataEntity;

global SimpleScoreHolder scoreHolder;

// ############################################################################
// Constraints
// ############################################################################

rule "Conflict"
when
TestdataEntity(value != null, $leftValue : value)
TestdataEntity(value == $leftValue)
then
scoreHolder.addConstraintMatch(kcontext, -1);
end
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>

<solver>

<!-- Using these classnames doesn't work because the className still differs from class.getName()-->
<!--<solutionClass>divertThroughClassLoader.org.optaplanner.core.impl.testdata.domain.TestdataSolution</solutionClass>-->
<!--<entityClass>divertThroughClassLoader.org.optaplanner.core.impl.testdata.domain.TestdataEntity</entityClass>-->
<solutionClass>org.optaplanner.core.impl.testdata.domain.TestdataSolution</solutionClass>
<entityClass>org.optaplanner.core.impl.testdata.domain.TestdataEntity</entityClass>

<!-- Score configuration -->
<scoreDirectorFactory>
<scoreDefinitionType>SIMPLE</scoreDefinitionType>
<scoreDrl>divertThroughClassLoader/org/optaplanner/core/api/solver/classloaderTestdataScoreRules.drl</scoreDrl>
</scoreDirectorFactory>
</solver>

0 comments on commit 41542aa

Please sign in to comment.