Skip to content

Commit a8b60d8

Browse files
committed
refactor: enable Enterprise license
1 parent b374d38 commit a8b60d8

2 files changed

Lines changed: 41 additions & 107 deletions

File tree

core/src/main/java/ai/timefold/solver/core/enterprise/TimefoldSolverEnterpriseService.java

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package ai.timefold.solver.core.enterprise;
22

33
import java.lang.reflect.InvocationTargetException;
4-
import java.lang.reflect.Method;
54
import java.util.function.BiFunction;
65
import java.util.function.Function;
76
import java.util.function.Supplier;
@@ -42,6 +41,14 @@
4241

4342
public interface TimefoldSolverEnterpriseService {
4443

44+
final class InstanceCarrier {
45+
46+
// Workaround to be able to have a lazy singleton inside of an interface.
47+
// Otherwise load() would be calling the reflective constructor every time, which is expensive.
48+
private static volatile TimefoldSolverEnterpriseService INSTANCE;
49+
50+
}
51+
4552
String SOLVER_NAME = "Timefold Solver";
4653
String COMMUNITY_NAME = "Community Edition";
4754
String COMMUNITY_COORDINATES = "ai.timefold.solver:timefold-solver-core";
@@ -62,14 +69,21 @@ static String identifySolverVersion() {
6269
}
6370

6471
@SuppressWarnings("unchecked")
65-
static TimefoldSolverEnterpriseService load()
66-
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
67-
// Avoids ServiceLoader by using reflection directly.
68-
var clz = (Class<? extends TimefoldSolverEnterpriseService>) Class
69-
.forName("ai.timefold.solver.enterprise.core.DefaultTimefoldSolverEnterpriseService");
70-
Method method = clz.getMethod("getInstance", Function.class);
71-
return (TimefoldSolverEnterpriseService) method.invoke(null,
72-
(Function<Class<?>, String>) TimefoldSolverEnterpriseService::getVersionString);
72+
static TimefoldSolverEnterpriseService load() throws ClassNotFoundException, NoSuchMethodException,
73+
InvocationTargetException, IllegalAccessException, InstantiationException {
74+
if (InstanceCarrier.INSTANCE == null) {
75+
synchronized (InstanceCarrier.class) {
76+
if (InstanceCarrier.INSTANCE == null) {
77+
// Avoids ServiceLoader by using reflection directly.
78+
var clz = (Class<? extends TimefoldSolverEnterpriseService>) Class
79+
.forName("ai.timefold.solver.enterprise.core.DefaultTimefoldSolverEnterpriseService");
80+
var ctor = clz.getDeclaredConstructor(Function.class);
81+
InstanceCarrier.INSTANCE =
82+
ctor.newInstance((Function<Class<?>, String>) TimefoldSolverEnterpriseService::getVersionString);
83+
}
84+
}
85+
}
86+
return InstanceCarrier.INSTANCE;
7387
}
7488

7589
static String getVersionString(Class<?> clz) {
@@ -84,17 +98,15 @@ static TimefoldSolverEnterpriseService loadOrFail(Feature feature) {
8498
throw new IllegalStateException("""
8599
No valid Timefold Enterprise License was found.
86100
Please contact Timefold to obtain a valid license,
87-
or if you believe that this message was given in error.""",
88-
cause);
101+
or if you believe that this message was given in error.""", cause);
89102
} catch (Exception cause) {
90103
throw new IllegalStateException("""
91104
%s requested but %s %s could not be loaded.
92105
Maybe add the %s dependency, or %s.
93106
Note: %s %s is a commercial product.
94-
Visit https://timefold.ai to find out more, or contact Timefold customer support."""
95-
.formatted(feature.getName(), SOLVER_NAME, ENTERPRISE_NAME, feature.getWorkaround(),
96-
ENTERPRISE_COORDINATES, SOLVER_NAME, ENTERPRISE_NAME),
97-
cause);
107+
Visit https://timefold.ai to find out more, or contact Timefold customer support.""".formatted(
108+
feature.getName(), SOLVER_NAME, ENTERPRISE_NAME, feature.getWorkaround(), ENTERPRISE_COORDINATES,
109+
SOLVER_NAME, ENTERPRISE_NAME), cause);
98110
}
99111
}
100112

@@ -146,21 +158,18 @@ <Solution_> DestinationSelector<Solution_> applyNearbySelection(DestinationSelec
146158
SelectionOrder resolvedSelectionOrder, ElementDestinationSelector<Solution_> destinationSelector);
147159

148160
<Solution_> AbstractMoveSelectorFactory<Solution_, MultistageMoveSelectorConfig>
149-
buildBasicMultistageMoveSelectorFactory(
150-
MultistageMoveSelectorConfig moveSelectorConfig);
161+
buildBasicMultistageMoveSelectorFactory(MultistageMoveSelectorConfig moveSelectorConfig);
151162

152163
<Solution_> AbstractMoveSelectorFactory<Solution_, ListMultistageMoveSelectorConfig>
153-
buildListMultistageMoveSelectorFactory(
154-
ListMultistageMoveSelectorConfig moveSelectorConfig);
164+
buildListMultistageMoveSelectorFactory(ListMultistageMoveSelectorConfig moveSelectorConfig);
155165

156166
InnerConstraintProfiler buildConstraintProfiler();
157167

158168
enum Feature {
159169
MULTITHREADED_SOLVING("Multi-threaded solving", "remove moveThreadCount from solver configuration"),
160170
PARTITIONED_SEARCH("Partitioned search", "remove partitioned search phase from solver configuration"),
161171
NEARBY_SELECTION("Nearby selection", "remove nearby selection from solver configuration"),
162-
AUTOMATIC_NODE_SHARING("Automatic node sharing",
163-
"remove automatic node sharing from solver configuration"),
172+
AUTOMATIC_NODE_SHARING("Automatic node sharing", "remove automatic node sharing from solver configuration"),
164173
MULTISTAGE_MOVE("Multistage move selector",
165174
"remove multistageMoveSelector and/or listMultistageMoveSelector from the solver configuration"),
166175
CONSTRAINT_PROFILING("Constraint profiling", "remove constraintStreamProfilingEnabled from the solver configuration");

docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc

Lines changed: 11 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ such as <<nearbySelection,nearby selection>> and <<multithreadedSolving,multi-th
99
These features are essential to scale out to huge datasets.
1010

1111
Unlike Timefold Solver Community Edition, the Enterprise Edition is not open-source.
12-
You are allowed to use Timefold Solver Enterprise Edition for evaluation and development.
12+
We offer free licenses to non-profit organizations and for academic research.
13+
Everyone else may be entitled to a trial license to evaluate the product before purchasing.
1314
Please https://timefold.ai/contact[contact Timefold]
14-
to obtain the credentials necessary to start your evaluation.
15+
to obtain an Enterprise license.
1516

1617
TIP: Looking for quicker time-to-value?
1718
Timefold offers https://docs.timefold.ai/[pre-built, fully tuned optimization models], no constraint building required.
@@ -23,92 +24,16 @@ see https://timefold.ai/pricing[Timefold Pricing].
2324
[#switchToEnterpriseEdition]
2425
== Switch to Enterprise Edition
2526

26-
// Uncomment the following when ready to enable the Enterprise license
27-
// =============================
28-
// To switch from Timefold Solver Community Edition to Enterprise Edition,
29-
// https://timefold.ai/contact[contact us first] to obtain an Enterprise License.
30-
// This license is represented by a file named `timefold-solver-enterprise-license.pem`,
31-
// which you can introduce to your project by one of the following methods:
32-
//
33-
// * Place the file in the user's home directory.
34-
// * Store the path to the file in `TIMEFOLD_ENTERPRISE_LICENSE` system property.
35-
// * Place the file in the root of your application classpath.
36-
//
37-
// Having done that, reference the Enterprise Edition Maven repository in your project:
38-
// =============================
39-
// Also delete the next line of text.
40-
41-
First reference the Enterprise Edition Maven repository in your project:
27+
To switch from Timefold Solver Community Edition to Enterprise Edition,
28+
https://timefold.ai/contact[contact us first] to obtain an Enterprise License.
29+
This license is represented by a file named `timefold-solver-enterprise-license.pem`,
30+
which you can introduce to your project by one of the following methods:
4231

43-
[tabs]
44-
====
45-
Maven::
46-
+
47-
--
48-
Add the following repository to your `pom.xml`:
49-
50-
[source,xml,options="nowrap"]
51-
----
52-
<project>
53-
...
54-
<repositories>
55-
<repository>
56-
<id>timefold-solver-enterprise</id>
57-
<name>Timefold Solver Enterprise Edition</name>
58-
<url>https://timefold.jfrog.io/artifactory/releases/</url>
59-
</repository>
60-
</repositories>
61-
...
62-
</project>
63-
----
64-
65-
Then create `.m2/settings.xml` in your home directory with the following content:
66-
67-
[source,xml,options="nowrap"]
68-
----
69-
<settings>
70-
...
71-
<servers>
72-
<server>
73-
<!-- Replace "my_username" and "my_password" with credentials obtained from a Timefold representative. -->
74-
<id>timefold-solver-enterprise</id>
75-
<username>my_username</username>
76-
<password>my_password</password>
77-
</server>
78-
</servers>
79-
...
80-
</settings>
81-
----
82-
83-
See https://maven.apache.org/settings.html[Settings Reference] for more information on Maven settings.
84-
--
85-
86-
Gradle::
87-
+
88-
--
89-
Add the following in your `build.gradle`:
90-
91-
[source,groovy,options="nowrap"]
92-
----
93-
repositories {
94-
mavenCentral()
95-
maven {
96-
url "https://timefold.jfrog.io/artifactory/releases/"
97-
credentials { // Replace "my_username" and "my_password" with credentials obtained from a Timefold representative.
98-
username "my_username"
99-
password "my_password"
100-
}
101-
authentication {
102-
basic(BasicAuthentication)
103-
}
104-
}
105-
}
106-
----
107-
--
108-
====
32+
* Place the file in the user's home directory.
33+
* Store the path to the file in `TIMEFOLD_ENTERPRISE_LICENSE` system property.
34+
* Place the file in the root of your application classpath.
10935
110-
Finally, having done all of the above,
111-
replace references to Community Edition artifacts by their Enterprise Edition counterparts
36+
Having done that, replace references to Community Edition artifacts by their Enterprise Edition counterparts
11237
as shown in the following table:
11338

11439
|===

0 commit comments

Comments
 (0)