/*
* 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.
* 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.config.domain;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import org.apache.commons.lang3.StringUtils;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.config.AbstractConfig;
import org.optaplanner.core.config.SolverConfigContext;
import org.optaplanner.core.config.util.ConfigUtils;
import org.optaplanner.core.impl.domain.solution.AbstractSolution;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.core.impl.score.definition.ScoreDefinition;
import org.reflections.Reflections;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
@XStreamAlias("scanAnnotatedClasses")
public class ScanAnnotatedClassesConfig extends AbstractConfig<ScanAnnotatedClassesConfig> {
@XStreamImplicit(itemFieldName = "packageInclude")
private List<String> packageIncludeList = null;
public List<String> getPackageIncludeList() {
return packageIncludeList;
}
public void setPackageIncludeList(List<String> packageIncludeList) {
this.packageIncludeList = packageIncludeList;
}
// ************************************************************************
// Builder methods
// ************************************************************************
public SolutionDescriptor buildSolutionDescriptor(SolverConfigContext configContext, ScoreDefinition deprecatedScoreDefinition) {
ClassLoader[] classLoaders;
if (configContext.getClassLoader() != null) {
classLoaders = new ClassLoader[] {configContext.getClassLoader()};
} else if (configContext.getKieContainer() != null) {
classLoaders = new ClassLoader[] {configContext.getKieContainer().getClassLoader()};
ReflectionsKieVfsUrlType.register(configContext.getKieContainer());
} else {
classLoaders = new ClassLoader[0];
}
ConfigurationBuilder builder = new ConfigurationBuilder();
if (!ConfigUtils.isEmptyCollection(packageIncludeList)) {
FilterBuilder filterBuilder = new FilterBuilder();
for (String packageInclude : packageIncludeList) {
if (StringUtils.isEmpty(packageInclude)) {
throw new IllegalArgumentException("The scanAnnotatedClasses (" + this
+ ") has a packageInclude (" + packageInclude
+ ") that is empty or null. Remove it or fill it in.");
}
builder.addUrls(ReflectionsWorkaroundClasspathHelper.forPackage(packageInclude, classLoaders));
filterBuilder.includePackage(packageInclude);
}
builder.filterInputsBy(filterBuilder);
} else {
builder.addUrls(ReflectionsWorkaroundClasspathHelper.forPackage("", classLoaders));
}
builder.setClassLoaders(classLoaders);
Reflections reflections = new Reflections(builder);
Class<?> solutionClass = loadSolutionClass(reflections);
List<Class<?>> entityClassList = loadEntityClassList(reflections);
return SolutionDescriptor.buildSolutionDescriptor(solutionClass, entityClassList, deprecatedScoreDefinition);
}
protected Class<?> loadSolutionClass(Reflections reflections) {
Set<Class<?>> solutionClassSet = reflections.getTypesAnnotatedWith(PlanningSolution.class);
retainOnlyClassesWithDeclaredAnnotation(solutionClassSet, PlanningSolution.class);
if (solutionClassSet.contains(AbstractSolution.class)) {
// Remove that core class to avoid a pointless fail-fast.
// (if users have a class like this, they need to use packageIncludeList)
solutionClassSet.remove(AbstractSolution.class);
// Note: Another abstract solution class might be fine, if extended by an unannotated solution class.
}
if (ConfigUtils.isEmptyCollection(solutionClassSet)) {
throw new IllegalStateException("The scanAnnotatedClasses (" + this
+ ") did not find any classes with a " + PlanningSolution.class.getSimpleName()
+ " annotation.\n"
+ "Maybe you forgot to annotate a class with a " + PlanningSolution.class.getSimpleName()
+ " annotation.\n"
+ (ConfigUtils.isEmptyCollection(packageIncludeList) ? ""
: "Maybe the annotated class does match the packageIncludeList (" + packageIncludeList + ").\n")
+ "Maybe you're using special classloading mechanisms (OSGi, ...) and this is a bug."
+ " If you can confirm that, report it to our issue tracker"
+ " and workaround it by defining the classes explicitly in the solver configuration.");
} else if (solutionClassSet.size() > 1) {
throw new IllegalStateException("The scanAnnotatedClasses (" + this
+ ") found multiple classes (" + solutionClassSet
+ ") with a " + PlanningSolution.class.getSimpleName() + " annotation.");
}
Class<?> solutionClass = solutionClassSet.iterator().next();
return solutionClass;
}
protected List<Class<?>> loadEntityClassList(Reflections reflections) {
Set<Class<?>> entityClassSet = reflections.getTypesAnnotatedWith(PlanningEntity.class);
retainOnlyClassesWithDeclaredAnnotation(entityClassSet, PlanningEntity.class);
if (ConfigUtils.isEmptyCollection(entityClassSet)) {
throw new IllegalStateException("The scanAnnotatedClasses (" + this
+ ") did not find any classes with a " + PlanningEntity.class.getSimpleName()
+ " annotation.");
}
return new ArrayList<>(entityClassSet);
}
// TODO We need unit test for this: annotation scanning with TestdataUnannotatedExtendedEntity
private void retainOnlyClassesWithDeclaredAnnotation(Set<Class<?>> classSet, Class<? extends Annotation> annotation) {
classSet.removeIf(clazz -> !clazz.isAnnotationPresent(annotation));
}
@Override
public void inherit(ScanAnnotatedClassesConfig inheritedConfig) {
packageIncludeList = ConfigUtils.inheritMergeableListProperty(
packageIncludeList, inheritedConfig.getPackageIncludeList());
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" + (packageIncludeList == null ? "" : packageIncludeList) + ")";
}
}