/*
* 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.kie.server.services.optaplanner;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import org.kie.server.api.model.ServiceResponse;
import org.kie.server.api.model.instance.ScoreWrapper;
import org.kie.server.api.model.instance.SolverInstance;
import org.kie.server.api.model.instance.SolverInstanceList;
import org.kie.server.services.api.KieContainerInstance;
import org.kie.server.services.api.KieServerRegistry;
import org.kie.server.services.impl.KieContainerInstanceImpl;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Optaplanner solver service
*/
public class SolverServiceBase {
private static final Logger logger = LoggerFactory.getLogger(SolverServiceBase.class);
private final ExecutorService executor;
private KieServerRegistry context;
private Map<String, SolverInstanceContext> solvers = new ConcurrentHashMap<String, SolverInstanceContext>();
public SolverServiceBase(KieServerRegistry context,
ExecutorService executorService) {
this.context = context;
this.executor = executorService;
}
public ServiceResponse<SolverInstance> createSolver(String containerId,
String solverId,
SolverInstance instance) {
if (instance == null || instance.getSolverConfigFile() == null) {
logger.error("Error creating solver. Configuration file name is null: " + instance);
return new ServiceResponse<SolverInstance>(
ServiceResponse.ResponseType.FAILURE,
"Failed to create solver for container " + containerId +
". Solver configuration file is null: " + instance);
}
instance.setContainerId(containerId);
instance.setSolverId(solverId);
try {
KieContainerInstanceImpl ci = context.getContainer(containerId);
if (ci == null) {
logger.error("Error creating solver. Container does not exist: " + containerId);
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.FAILURE,
"Failed to create solver. Container does not exist: " + containerId);
}
// have to synchronize on the ci or a concurrent call to dispose may create inconsistencies
synchronized (ci) {
if (solvers.containsKey(instance.getSolverInstanceKey())) {
logger.error("Error creating solver. Solver '" + solverId + "' already exists for container '" + containerId + "'.");
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.FAILURE,
"Failed to create solver. Solver '" + solverId +
"' already exists for container '" + containerId + "'.");
}
SolverInstanceContext sic = new SolverInstanceContext(instance);
if (instance.getStatus() == null) {
instance.setStatus(SolverInstance.SolverStatus.NOT_SOLVING);
}
try {
SolverFactory<?> solverFactory = SolverFactory.createFromKieContainerXmlResource(ci.getKieContainer(),
instance.getSolverConfigFile());
Solver<?> solver = solverFactory.buildSolver();
sic.setSolver(solver);
updateSolverInstance(sic);
solvers.put(instance.getSolverInstanceKey(),
sic);
logger.info("Solver '" + solverId + "' successfully created in container '" + containerId + "'");
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.SUCCESS,
"Solver '" + solverId + "' successfully created in container '" + containerId + "'",
instance);
} catch (Exception e) {
logger.error("Error creating solver factory for solver " + instance,
e);
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.FAILURE,
"Error creating solver factory for solver: " + e.getMessage(),
instance);
}
}
} catch (Exception e) {
logger.error("Error creating solver '" + solverId + "' in container '" + containerId + "'",
e);
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.FAILURE,
"Error creating solver '" + solverId + "' in container '" + containerId + "': " + e.getMessage(),
instance);
}
}
public ServiceResponse<SolverInstanceList> getSolvers(String containerId) {
try {
List<SolverInstance> sl = getSolversForContainer(containerId);
return new ServiceResponse<SolverInstanceList>(
ServiceResponse.ResponseType.SUCCESS,
"Solvers list successfully retrieved from container '" + containerId + "'",
new SolverInstanceList(sl));
} catch (Exception e) {
logger.error("Error retrieving solvers list from container '" + containerId + "'",
e);
return new ServiceResponse<SolverInstanceList>(
ServiceResponse.ResponseType.FAILURE,
"Error retrieving solvers list from container '" + containerId + "'" + e.getMessage(),
null);
}
}
public ServiceResponse<SolverInstance> getSolver(String containerId,
String solverId) {
try {
SolverInstanceContext sic = solvers.get(SolverInstance.getSolverInstanceKey(containerId,
solverId));
if (sic != null) {
updateSolverInstance(sic);
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.SUCCESS,
"Solver '" + solverId + "' state successfully retrieved from container '" + containerId + "'",
sic.getInstance());
} else {
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' not found in container '" + containerId + "'",
null);
}
} catch (Exception e) {
logger.error("Error retrieving solver '" + solverId + "' state from container '" + containerId + "'",
e);
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.FAILURE,
"Error retrieving solver '" + solverId + "' state from container '" + containerId + "'" + e.getMessage(),
null);
}
}
public ServiceResponse<SolverInstance> getSolverWithBestSolution(String containerId,
String solverId) {
try {
SolverInstanceContext sic = solvers.get(SolverInstance.getSolverInstanceKey(containerId,
solverId));
if (sic != null) {
updateSolverInstance(sic);
sic.getInstance().setBestSolution(sic.getSolver().getBestSolution());
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.SUCCESS,
"Best computed solution for '" + solverId + "' successfully retrieved from container '" + containerId + "'",
sic.getInstance());
} else {
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' not found in container '" + containerId + "'",
null);
}
} catch (Exception e) {
logger.error("Error retrieving solver '" + solverId + "' state from container '" + containerId + "'",
e);
return new ServiceResponse<SolverInstance>(ServiceResponse.ResponseType.FAILURE,
"Error retrieving solver '" + solverId + "' state from container '" + containerId + "'" + e.getMessage(),
null);
}
}
public ServiceResponse<Void> solvePlanningProblem(String containerId,
String solverId,
Object planningProblem) {
try {
SolverInstanceContext sic = solvers.get(SolverInstance.getSolverInstanceKey(containerId,
solverId));
if (sic != null) {
synchronized (sic) {
switch (sic.getInstance().getStatus()) {
case SOLVING: {
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' on container '" + containerId + "' is already executing.");
}
case TERMINATING_EARLY: {
return new ServiceResponse<>(ServiceResponse.ResponseType.SUCCESS,
"Solver '" + solverId + "' on container '" + containerId + "' has already terminated.");
}
case NOT_SOLVING: {
if (planningProblem == null) {
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Planning-problem is a mandatory field when starting the solver.");
}
updateSolverInstance(sic);
solvePlanningProblem(sic,
planningProblem);
return new ServiceResponse<>(ServiceResponse.ResponseType.SUCCESS,
"Solver '" + solverId + "' from container '" + containerId + "' successfully started.");
}
default: {
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' on container '" + containerId + "' is in unrecognized state '" + sic.getInstance().getStatus() + "'.");
}
}
}
} else {
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' not found in container '" + containerId + "'");
}
} catch (Exception e) {
logger.error("Error retrieving solver '" + solverId + "' state from container '" + containerId + "'",
e);
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Unknown error updating solver state.");
}
}
public ServiceResponse<Void> terminateSolverEarly(String containerId,
String solverId) {
try {
SolverInstanceContext sic = solvers.get(SolverInstance.getSolverInstanceKey(containerId,
solverId));
if (sic != null) {
synchronized (sic) {
switch (sic.getInstance().getStatus()) {
case SOLVING: {
terminateSolverEarly(sic);
return new ServiceResponse<>(ServiceResponse.ResponseType.SUCCESS,
"Solver '" + solverId + "' on container '" + containerId + "' successfully terminated.");
}
case TERMINATING_EARLY: {
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' on container '" + containerId + "' already terminated.");
}
case NOT_SOLVING: {
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' from container '" + containerId + "' is not executing.");
}
default: {
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' on container '" + containerId + "' is in unrecognized state '" + sic.getInstance().getStatus() + "'.");
}
}
}
} else {
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' not found in container '" + containerId + "'");
}
} catch (Exception e) {
logger.error("Error retrieving solver '" + solverId + "' state from container '" + containerId + "'",
e);
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Unknown error updating solver state.");
}
}
public ServiceResponse<Void> disposeSolver(String containerId,
String solverId) {
try {
SolverInstanceContext sic = internalDisposeSolver(containerId,
solverId);
if (sic != null) {
return new ServiceResponse<>(ServiceResponse.ResponseType.SUCCESS,
"Solver '" + solverId + "' successfully disposed from container '" + containerId + "'",
null);
}
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Solver '" + solverId + "' from container '" + containerId + "' not found.",
null);
} catch (Exception e) {
logger.error("Error disposing solver '" + solverId + "' from container '" + containerId + "'",
e);
return new ServiceResponse<>(ServiceResponse.ResponseType.FAILURE,
"Error disposing solver '" + solverId + "' from container '" + containerId + "'. Message: " + e.getMessage(),
null);
}
}
public KieServerRegistry getKieServerRegistry() {
return this.context;
}
public void disposeSolversForContainer(String containerId,
KieContainerInstance kci) {
List<SolverInstance> sfc = getSolversForContainer(containerId);
for (SolverInstance si : sfc) {
internalDisposeSolver(containerId,
si.getSolverId());
}
}
private List<SolverInstance> getSolversForContainer(String containerId) {
List<SolverInstance> sl = new ArrayList<SolverInstance>(solvers.size());
for (SolverInstanceContext sic : solvers.values()) {
if (containerId.equalsIgnoreCase(sic.getInstance().getContainerId())) {
updateSolverInstance(sic);
sl.add(sic.getInstance());
}
}
return sl;
}
private SolverInstanceContext internalDisposeSolver(String containerId,
String solverId) {
// need to dispose resources here
SolverInstanceContext sic = solvers.remove(SolverInstance.getSolverInstanceKey(containerId,
solverId));
if (sic != null) {
synchronized (sic) {
if (sic.getInstance().getStatus() == SolverInstance.SolverStatus.SOLVING) {
terminateSolverEarly(sic);
}
}
}
return sic;
}
private void updateSolverInstance(SolverInstanceContext sic) {
synchronized (sic) {
// We keep track of the solver status ourselves, so there's no need to call buggy updateSolverStatus( sic );
Score bestScore = sic.getSolver().getBestScore();
sic.getInstance().setScoreWrapper(new ScoreWrapper(bestScore));
}
}
private void updateSolverStatus(SolverInstanceContext sic) {
Solver solver = sic.getSolver();
if (!solver.isSolving()) {
// TODO BUGGY because a solver might have been a scheduled to start, but not started yet
// (especially immediately after solvePlanningProblem() call).
sic.getInstance().setStatus(SolverInstance.SolverStatus.NOT_SOLVING);
} else {
if (solver.isTerminateEarly()) {
sic.getInstance().setStatus(SolverInstance.SolverStatus.TERMINATING_EARLY);
} else {
sic.getInstance().setStatus(SolverInstance.SolverStatus.SOLVING);
}
}
}
private void solvePlanningProblem(final SolverInstanceContext sic,
final Object planningSolution) {
sic.getInstance().setBestSolution(null);
sic.getInstance().setStatus(SolverInstance.SolverStatus.SOLVING);
this.executor.execute(
new Runnable() {
@Override
public void run() {
try {
// If the executor's queue is full, it's possible that the solver gets canceled before it starts
SolverInstance.SolverStatus status;
synchronized (sic) {
status = sic.getInstance().getStatus();
// TODO Race condition: status turns into non-solving before solver starts
// See https://issues.jboss.org/browse/PLANNER-540
}
if (status == SolverInstance.SolverStatus.SOLVING) {
sic.getSolver().solve(planningSolution);
}
} catch (Exception e) {
logger.error("Exception executing solver '" + sic.getInstance().getSolverId() + "' from container '" + sic.getInstance().getContainerId() + "'. Thread will terminate.",
e);
} finally {
synchronized (sic) {
sic.getInstance().setStatus(SolverInstance.SolverStatus.NOT_SOLVING);
}
}
}
});
}
private void terminateSolverEarly(SolverInstanceContext sic) {
synchronized (sic) {
if (sic.getInstance().getStatus() == SolverInstance.SolverStatus.SOLVING) {
sic.getInstance().setStatus(SolverInstance.SolverStatus.TERMINATING_EARLY);
}
}
sic.getSolver().terminateEarly();
}
}