Skip to content

Commit 176188b

Browse files
authored
feat: identify Solver version and edition in Benchmarker, Spring and Quarkus (#411)
- Allows to detect enterprise edition. - Adds a banner to Spring and Quarkus to show the information. - Enterprise edition banner says "enterprise edition".
1 parent 6f0603f commit 176188b

File tree

18 files changed

+206
-153
lines changed

18 files changed

+206
-153
lines changed

benchmark/src/main/java/ai/timefold/solver/benchmark/impl/result/PlannerBenchmarkResult.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
import ai.timefold.solver.benchmark.impl.report.BenchmarkReport;
2121
import ai.timefold.solver.core.api.score.Score;
2222
import ai.timefold.solver.core.api.solver.Solver;
23-
import ai.timefold.solver.core.api.solver.SolverFactory;
2423
import ai.timefold.solver.core.config.solver.EnvironmentMode;
2524
import ai.timefold.solver.core.config.util.ConfigUtils;
25+
import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService;
2626
import ai.timefold.solver.core.impl.score.definition.ScoreDefinition;
2727

2828
import org.slf4j.Logger;
@@ -270,10 +270,7 @@ public void initSystemProperties() {
270270
availableProcessors = Runtime.getRuntime().availableProcessors();
271271
loggingLevelTimefoldSolverCore = resolveLoggingLevel("ai.timefold.solver.core");
272272
maxMemory = Runtime.getRuntime().maxMemory();
273-
timefoldSolverVersion = SolverFactory.class.getPackage().getImplementationVersion();
274-
if (timefoldSolverVersion == null) {
275-
timefoldSolverVersion = "Unjarred development snapshot";
276-
}
273+
timefoldSolverVersion = TimefoldSolverEnterpriseService.identifySolverVersion();
277274
javaVersion = "Java " + System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")";
278275
javaVM = "Java " + System.getProperty("java.vm.name") + " " + System.getProperty("java.vm.version")
279276
+ " (" + System.getProperty("java.vm.vendor") + ")";

benchmark/src/main/resources/ai/timefold/solver/benchmark/impl/report/benchmarkReport.html.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@
827827
</#if>
828828
</tr>
829829
<tr>
830-
<th>Timefold version</th>
830+
<th>Timefold Solver version</th>
831831
<td>${benchmarkReport.plannerBenchmarkResult.timefoldSolverVersion!"Differs"}</td>
832832
</tr>
833833
<tr>

core/core-impl/src/main/java/ai/timefold/solver/core/enterprise/MultithreadedSolvingEnterpriseService.java

Lines changed: 0 additions & 41 deletions
This file was deleted.

core/core-impl/src/main/java/ai/timefold/solver/core/enterprise/NearbySelectionEnterpriseService.java

Lines changed: 0 additions & 54 deletions
This file was deleted.

core/core-impl/src/main/java/ai/timefold/solver/core/enterprise/PartitionedSearchEnterpriseService.java

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package ai.timefold.solver.core.enterprise;
2+
3+
import java.util.ServiceLoader;
4+
import java.util.function.BiFunction;
5+
6+
import ai.timefold.solver.core.api.solver.SolverFactory;
7+
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
8+
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder;
9+
import ai.timefold.solver.core.config.heuristic.selector.common.nearby.NearbySelectionConfig;
10+
import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig;
11+
import ai.timefold.solver.core.config.heuristic.selector.list.DestinationSelectorConfig;
12+
import ai.timefold.solver.core.config.heuristic.selector.list.SubListSelectorConfig;
13+
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig;
14+
import ai.timefold.solver.core.config.partitionedsearch.PartitionedSearchPhaseConfig;
15+
import ai.timefold.solver.core.config.solver.EnvironmentMode;
16+
import ai.timefold.solver.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider;
17+
import ai.timefold.solver.core.impl.constructionheuristic.decider.forager.ConstructionHeuristicForager;
18+
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
19+
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
20+
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector;
21+
import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector;
22+
import ai.timefold.solver.core.impl.heuristic.selector.list.ElementDestinationSelector;
23+
import ai.timefold.solver.core.impl.heuristic.selector.list.RandomSubListSelector;
24+
import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelector;
25+
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
26+
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector;
27+
import ai.timefold.solver.core.impl.localsearch.decider.LocalSearchDecider;
28+
import ai.timefold.solver.core.impl.localsearch.decider.acceptor.Acceptor;
29+
import ai.timefold.solver.core.impl.localsearch.decider.forager.LocalSearchForager;
30+
import ai.timefold.solver.core.impl.partitionedsearch.PartitionedSearchPhase;
31+
import ai.timefold.solver.core.impl.solver.termination.Termination;
32+
33+
public interface TimefoldSolverEnterpriseService {
34+
35+
static String identifySolverVersion() {
36+
var packaging = TimefoldSolverEnterpriseService.load() == null ? "Community Edition" : "Enterprise Edition";
37+
var version = SolverFactory.class.getPackage().getImplementationVersion();
38+
return packaging + " " + (version == null ? "(Development snapshot)" : "v" + version);
39+
}
40+
41+
static TimefoldSolverEnterpriseService load() {
42+
var serviceLoader = ServiceLoader.load(TimefoldSolverEnterpriseService.class);
43+
var iterator = serviceLoader.iterator();
44+
return iterator.hasNext() ? iterator.next() : null;
45+
}
46+
47+
static TimefoldSolverEnterpriseService loadOrFail(Feature feature) {
48+
var service = load();
49+
if (service == null) {
50+
throw new IllegalStateException("""
51+
%s requested but Timefold Solver Enterprise Edition not found on classpath
52+
Either add the ai.timefold.solver.enterprise:timefold-solver-enterprise-core dependency,
53+
or %s.
54+
"Note: Timefold Solver Enterprise Edition is a commercial product."""
55+
.formatted(feature.getName(), feature.getWorkaround()));
56+
}
57+
return service;
58+
}
59+
60+
<Solution_> ConstructionHeuristicDecider<Solution_> buildConstructionHeuristic(int moveThreadCount,
61+
Termination<Solution_> termination, ConstructionHeuristicForager<Solution_> forager,
62+
EnvironmentMode environmentMode, HeuristicConfigPolicy<Solution_> configPolicy);
63+
64+
<Solution_> LocalSearchDecider<Solution_> buildLocalSearch(int moveThreadCount, Termination<Solution_> termination,
65+
MoveSelector<Solution_> moveSelector, Acceptor<Solution_> acceptor, LocalSearchForager<Solution_> forager,
66+
EnvironmentMode environmentMode, HeuristicConfigPolicy<Solution_> configPolicy);
67+
68+
<Solution_> PartitionedSearchPhase<Solution_> buildPartitionedSearch(int phaseIndex,
69+
PartitionedSearchPhaseConfig phaseConfig, HeuristicConfigPolicy<Solution_> solverConfigPolicy,
70+
Termination<Solution_> solverTermination,
71+
BiFunction<HeuristicConfigPolicy<Solution_>, Termination<Solution_>, Termination<Solution_>> phaseTerminationFunction);
72+
73+
<Solution_> EntitySelector<Solution_> applyNearbySelection(EntitySelectorConfig entitySelectorConfig,
74+
HeuristicConfigPolicy<Solution_> configPolicy, NearbySelectionConfig nearbySelectionConfig,
75+
SelectionCacheType minimumCacheType, SelectionOrder resolvedSelectionOrder,
76+
EntitySelector<Solution_> entitySelector);
77+
78+
<Solution_> ValueSelector<Solution_> applyNearbySelection(ValueSelectorConfig valueSelectorConfig,
79+
HeuristicConfigPolicy<Solution_> configPolicy, EntityDescriptor<Solution_> entityDescriptor,
80+
SelectionCacheType minimumCacheType, SelectionOrder resolvedSelectionOrder, ValueSelector<Solution_> valueSelector);
81+
82+
<Solution_> SubListSelector<Solution_> applyNearbySelection(SubListSelectorConfig subListSelectorConfig,
83+
HeuristicConfigPolicy<Solution_> configPolicy, SelectionCacheType minimumCacheType,
84+
SelectionOrder resolvedSelectionOrder, RandomSubListSelector<Solution_> subListSelector);
85+
86+
<Solution_> DestinationSelector<Solution_> applyNearbySelection(DestinationSelectorConfig destinationSelectorConfig,
87+
HeuristicConfigPolicy<Solution_> configPolicy, SelectionCacheType minimumCacheType,
88+
SelectionOrder resolvedSelectionOrder, ElementDestinationSelector<Solution_> destinationSelector);
89+
90+
public enum Feature {
91+
MULTITHREADED_SOLVING("Multi-threaded solving", "remove moveThreadCount from solver configuration"),
92+
PARTITIONED_SEARCH("Partitioned search", "remove partitioned search phase from solver configuration"),
93+
NEARBY_SELECTION("Nearby selection", "remove nearby selection from solver configuration");
94+
95+
private final String name;
96+
private final String workaround;
97+
98+
Feature(String name, String workaround) {
99+
this.name = name;
100+
this.workaround = workaround;
101+
}
102+
103+
public String getName() {
104+
return name;
105+
}
106+
107+
public String getWorkaround() {
108+
return workaround;
109+
}
110+
111+
}
112+
113+
}

core/core-impl/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner;
2323
import ai.timefold.solver.core.config.solver.EnvironmentMode;
2424
import ai.timefold.solver.core.config.util.ConfigUtils;
25-
import ai.timefold.solver.core.enterprise.MultithreadedSolvingEnterpriseService;
25+
import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService;
2626
import ai.timefold.solver.core.impl.constructionheuristic.decider.ConstructionHeuristicDecider;
2727
import ai.timefold.solver.core.impl.constructionheuristic.decider.forager.ConstructionHeuristicForager;
2828
import ai.timefold.solver.core.impl.constructionheuristic.decider.forager.ConstructionHeuristicForagerFactory;
@@ -179,7 +179,8 @@ private ConstructionHeuristicDecider<Solution_> buildDecider(HeuristicConfigPoli
179179
if (moveThreadCount == null) {
180180
decider = new ConstructionHeuristicDecider<>(configPolicy.getLogIndentation(), termination, forager);
181181
} else {
182-
decider = MultithreadedSolvingEnterpriseService.load(moveThreadCount)
182+
decider = TimefoldSolverEnterpriseService
183+
.loadOrFail(TimefoldSolverEnterpriseService.Feature.MULTITHREADED_SOLVING)
183184
.buildConstructionHeuristic(moveThreadCount, termination, forager, environmentMode, configPolicy);
184185
}
185186
if (environmentMode.isNonIntrusiveFullAsserted()) {

core/core-impl/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder;
1313
import ai.timefold.solver.core.config.heuristic.selector.common.nearby.NearbySelectionConfig;
1414
import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig;
15-
import ai.timefold.solver.core.enterprise.NearbySelectionEnterpriseService;
15+
import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService;
1616
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
1717
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
1818
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
@@ -175,7 +175,8 @@ private boolean hasFiltering(EntityDescriptor<Solution_> entityDescriptor) {
175175
private EntitySelector<Solution_> applyNearbySelection(HeuristicConfigPolicy<Solution_> configPolicy,
176176
NearbySelectionConfig nearbySelectionConfig, SelectionCacheType minimumCacheType,
177177
SelectionOrder resolvedSelectionOrder, EntitySelector<Solution_> entitySelector) {
178-
return NearbySelectionEnterpriseService.load()
178+
return TimefoldSolverEnterpriseService
179+
.loadOrFail(TimefoldSolverEnterpriseService.Feature.NEARBY_SELECTION)
179180
.applyNearbySelection(config, configPolicy, nearbySelectionConfig, minimumCacheType,
180181
resolvedSelectionOrder, entitySelector);
181182
}

core/core-impl/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/DestinationSelectorFactory.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder;
88
import ai.timefold.solver.core.config.heuristic.selector.common.nearby.NearbySelectionConfig;
99
import ai.timefold.solver.core.config.heuristic.selector.list.DestinationSelectorConfig;
10-
import ai.timefold.solver.core.enterprise.NearbySelectionEnterpriseService;
10+
import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService;
1111
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
1212
import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy;
1313
import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory;
@@ -96,7 +96,8 @@ private DestinationSelector<Solution_> applyNearbySelection(HeuristicConfigPolic
9696
if (nearbySelectionConfig == null) {
9797
return destinationSelector;
9898
}
99-
return NearbySelectionEnterpriseService.load()
99+
return TimefoldSolverEnterpriseService
100+
.loadOrFail(TimefoldSolverEnterpriseService.Feature.NEARBY_SELECTION)
100101
.applyNearbySelection(config, configPolicy, minimumCacheType, resolvedSelectionOrder, destinationSelector);
101102
}
102103
}

0 commit comments

Comments
 (0)