From fe66836e2b2f86cddf9afde9cee82c8ef948b860 Mon Sep 17 00:00:00 2001 From: Christopher Chianelli Date: Tue, 15 Jun 2021 16:01:28 -0400 Subject: [PATCH] PLANNER-2411: Add solver config, constraint list, and model to Quarkus Dev UI --- .../deployment/OptaPlannerProcessor.java | 29 +++- .../deployment/SolverConfigBuildStep.java | 33 +++++ .../resources/dev-templates/constraints.html | 35 +++++ .../resources/dev-templates/embedded.html | 27 ++++ .../main/resources/dev-templates/model.html | 74 ++++++++++ .../resources/dev-templates/solverConfig.html | 47 ++++++ .../devui/OptaPlannerDevUIProperties.java | 44 ++++++ .../OptaPlannerDevUIPropertiesSupplier.java | 137 ++++++++++++++++++ .../devui/OptaPlannerModelProperties.java | 69 +++++++++ 9 files changed, 493 insertions(+), 2 deletions(-) create mode 100644 optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/java/org/optaplanner/quarkus/deployment/SolverConfigBuildStep.java create mode 100644 optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/constraints.html create mode 100644 optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/embedded.html create mode 100644 optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/model.html create mode 100644 optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/solverConfig.html create mode 100644 optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerDevUIProperties.java create mode 100644 optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerDevUIPropertiesSupplier.java create mode 100644 optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerModelProperties.java diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/java/org/optaplanner/quarkus/deployment/OptaPlannerProcessor.java b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/java/org/optaplanner/quarkus/deployment/OptaPlannerProcessor.java index 4a750853374..13fcaa5b29d 100644 --- a/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/java/org/optaplanner/quarkus/deployment/OptaPlannerProcessor.java +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/java/org/optaplanner/quarkus/deployment/OptaPlannerProcessor.java @@ -18,6 +18,7 @@ import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; +import java.io.StringWriter; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; @@ -54,10 +55,12 @@ import org.optaplanner.core.config.solver.SolverConfig; import org.optaplanner.core.config.solver.SolverManagerConfig; import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor; +import org.optaplanner.core.impl.io.jaxb.SolverConfigIO; import org.optaplanner.quarkus.OptaPlannerBeanProvider; import org.optaplanner.quarkus.OptaPlannerRecorder; import org.optaplanner.quarkus.config.OptaPlannerRuntimeConfig; import org.optaplanner.quarkus.deployment.config.OptaPlannerBuildTimeConfig; +import org.optaplanner.quarkus.devui.OptaPlannerDevUIPropertiesSupplier; import org.optaplanner.quarkus.gizmo.OptaPlannerGizmoBeanFactory; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -67,6 +70,7 @@ import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.GeneratedClassGizmoAdaptor; +import io.quarkus.deployment.IsDevelopment; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; @@ -79,6 +83,7 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.pkg.steps.NativeBuild; import io.quarkus.deployment.recording.RecorderContext; +import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; @@ -120,9 +125,24 @@ void makeGizmoBeanFactoryUnremovable(BuildProducer unr unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(OptaPlannerGizmoBeanFactory.class)); } + @BuildStep(onlyIf = IsDevelopment.class) + public DevConsoleRuntimeTemplateInfoBuildItem getSolverConfig(SolverConfigBuildStep solverConfigBuildStep) { + SolverConfig solverConfig = solverConfigBuildStep.getSolverConfig(); + if (solverConfig != null) { + StringWriter effectiveSolverConfigWriter = new StringWriter(); + SolverConfigIO solverConfigIO = new SolverConfigIO(); + solverConfigIO.write(solverConfig, effectiveSolverConfigWriter); + return new DevConsoleRuntimeTemplateInfoBuildItem("solverConfigProperties", + new OptaPlannerDevUIPropertiesSupplier(effectiveSolverConfigWriter.toString())); + } else { + return new DevConsoleRuntimeTemplateInfoBuildItem("solverConfigProperties", + new OptaPlannerDevUIPropertiesSupplier()); + } + } + @BuildStep @Record(STATIC_INIT) - void recordAndRegisterBeans(OptaPlannerRecorder recorder, RecorderContext recorderContext, + SolverConfigBuildStep recordAndRegisterBeans(OptaPlannerRecorder recorder, RecorderContext recorderContext, CombinedIndexBuildItem combinedIndex, Capabilities capabilities, BuildProducer reflectiveHierarchyClass, BuildProducer syntheticBeanBuildItemBuildProducer, @@ -142,7 +162,7 @@ void recordAndRegisterBeans(OptaPlannerRecorder recorder, RecorderContext record + " the Jandex index by using the jandex-maven-plugin in that dependency, or by adding" + "application.properties entries (quarkus.index-dependency..group-id" + " and quarkus.index-dependency..artifact-id)."); - return; + return new SolverConfigBuildStep(null); } // Quarkus extensions must always use getContextClassLoader() @@ -204,6 +224,10 @@ void recordAndRegisterBeans(OptaPlannerRecorder recorder, RecorderContext record GizmoMemberAccessorEntityEnhancer.getDroolsInitializer(recorderContext))) .done()); + StringWriter effectiveSolverConfigXMLWriter = new StringWriter(); + SolverConfigIO solverConfigIO = new SolverConfigIO(); + solverConfigIO.write(solverConfig, effectiveSolverConfigXMLWriter); + syntheticBeanBuildItemBuildProducer.produce(SyntheticBeanBuildItem.configure(SolverManagerConfig.class) .scope(Singleton.class) .defaultBean() @@ -211,6 +235,7 @@ void recordAndRegisterBeans(OptaPlannerRecorder recorder, RecorderContext record additionalBeans.produce(new AdditionalBeanBuildItem(OptaPlannerBeanProvider.class)); unremovableBeans.produce(UnremovableBeanBuildItem.beanTypes(OptaPlannerRuntimeConfig.class)); + return new SolverConfigBuildStep(solverConfig); } private void generateConstraintVerifier(SolverConfig solverConfig, diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/java/org/optaplanner/quarkus/deployment/SolverConfigBuildStep.java b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/java/org/optaplanner/quarkus/deployment/SolverConfigBuildStep.java new file mode 100644 index 00000000000..98f49e7ff50 --- /dev/null +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/java/org/optaplanner/quarkus/deployment/SolverConfigBuildStep.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021 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.quarkus.deployment; + +import org.optaplanner.core.config.solver.SolverConfig; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class SolverConfigBuildStep extends SimpleBuildItem { + SolverConfig solverConfig; + + public SolverConfigBuildStep(SolverConfig solverConfig) { + this.solverConfig = solverConfig; + } + + public SolverConfig getSolverConfig() { + return solverConfig; + } +} diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/constraints.html b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/constraints.html new file mode 100644 index 00000000000..4a301c48961 --- /dev/null +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/constraints.html @@ -0,0 +1,35 @@ + + +{#include main} + +{#title}Constraints{/title} + +{#body} + + + + + + + + {#for constraint in info:solverConfigProperties.constraintList} + + {/for} + +
Constraints
{constraint}
+{/body} +{/include} \ No newline at end of file diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/embedded.html b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/embedded.html new file mode 100644 index 00000000000..15ac5b85f2a --- /dev/null +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/embedded.html @@ -0,0 +1,27 @@ + + + + View Effective Solver Configuration + + + + View Model + + + + View Constraints + \ No newline at end of file diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/model.html b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/model.html new file mode 100644 index 00000000000..5c4f2924225 --- /dev/null +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/model.html @@ -0,0 +1,74 @@ + +{#include main} + +{#title}Model{/title} + + + +{#body} +
+
Solution: {info:solverConfigProperties.optaPlannerModelProperties.solutionClass}
+
+ {#for entityClass in info:solverConfigProperties.optaPlannerModelProperties.entityClassList} +
+
Entity: {entityClass}
+
+ + + + + + + + {#for genuineVariable in info:solverConfigProperties.optaPlannerModelProperties.entityClassToGenuineVariableListMap.get(entityClass)} + + {/for} + +
Genuine Variables
{genuineVariable}
+ + + + + + + + {#for shadowVariable in info:solverConfigProperties.optaPlannerModelProperties.entityClassToShadowVariableListMap.get(entityClass)} + + {/for} + +
Shadow Variables
{shadowVariable}
+
+
+ {/for} +
+
+{/body} +{/include} \ No newline at end of file diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/solverConfig.html b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/solverConfig.html new file mode 100644 index 00000000000..9957c3f8321 --- /dev/null +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/deployment/src/main/resources/dev-templates/solverConfig.html @@ -0,0 +1,47 @@ + +{#include main} + +{#style} +.solver-config { +white-space: pre-wrap; +} +{/style} + +{#title}Solver Configuration{/title} + + + +{#body} +
+ {info:solverConfigProperties.effectiveSolverConfig} +
+{/body} +{/include} \ No newline at end of file diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerDevUIProperties.java b/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerDevUIProperties.java new file mode 100644 index 00000000000..8a71aa0985f --- /dev/null +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerDevUIProperties.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 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.quarkus.devui; + +import java.util.List; + +public class OptaPlannerDevUIProperties { + OptaPlannerModelProperties optaPlannerModelProperties; + String effectiveSolverConfigXML; + List constraintList; + + public OptaPlannerDevUIProperties(OptaPlannerModelProperties optaPlannerModelProperties, String effectiveSolverConfigXML, + List constraintList) { + this.optaPlannerModelProperties = optaPlannerModelProperties; + this.effectiveSolverConfigXML = effectiveSolverConfigXML; + this.constraintList = constraintList; + } + + public OptaPlannerModelProperties getOptaPlannerModelProperties() { + return optaPlannerModelProperties; + } + + public String getEffectiveSolverConfig() { + return effectiveSolverConfigXML; + } + + public List getConstraintList() { + return constraintList; + } +} diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerDevUIPropertiesSupplier.java b/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerDevUIPropertiesSupplier.java new file mode 100644 index 00000000000..ec748070c75 --- /dev/null +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerDevUIPropertiesSupplier.java @@ -0,0 +1,137 @@ +/* + * Copyright 2021 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.quarkus.devui; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.optaplanner.core.api.domain.entity.PlanningEntity; +import org.optaplanner.core.api.domain.solution.PlanningSolution; +import org.optaplanner.core.api.score.stream.Constraint; +import org.optaplanner.core.api.solver.SolverFactory; +import org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor; +import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor; +import org.optaplanner.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; +import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor; +import org.optaplanner.core.impl.score.director.stream.AbstractConstraintStreamScoreDirectorFactory; +import org.optaplanner.core.impl.solver.DefaultSolverFactory; + +import io.quarkus.arc.Arc; + +public class OptaPlannerDevUIPropertiesSupplier implements Supplier { + String effectiveSolverConfigXml; + + public OptaPlannerDevUIPropertiesSupplier() { + this.effectiveSolverConfigXml = null; + } + + public OptaPlannerDevUIPropertiesSupplier(String effectiveSolverConfigXml) { + this.effectiveSolverConfigXml = effectiveSolverConfigXml; + } + + public String getEffectiveSolverConfigXml() { + return effectiveSolverConfigXml; + } + + public void setEffectiveSolverConfigXml(String effectiveSolverConfigXml) { + this.effectiveSolverConfigXml = effectiveSolverConfigXml; + } + + @Override + public OptaPlannerDevUIProperties get() { + if (effectiveSolverConfigXml != null) { + // SolverConfigIO does not work at runtime, + // but the build time SolverConfig does not have properties + // that can be set at runtime (ex: termination), so the + // effective solver config will be missing some properties + return new OptaPlannerDevUIProperties(getModelInfo(), + getXmlContentWithComment("Properties that can be set at runtime are not included"), + getConstraintList()); + } else { + return new OptaPlannerDevUIProperties(getModelInfo(), + "\n", + Collections.emptyList()); + } + } + + private OptaPlannerModelProperties getModelInfo() { + if (effectiveSolverConfigXml != null) { + DefaultSolverFactory solverFactory = + (DefaultSolverFactory) Arc.container().instance(SolverFactory.class).get(); + SolutionDescriptor solutionDescriptor = solverFactory.getScoreDirectorFactory().getSolutionDescriptor(); + OptaPlannerModelProperties out = new OptaPlannerModelProperties(); + out.setSolutionClass(solutionDescriptor.getSolutionClass().getName()); + List entityClassList = new ArrayList<>(); + Map> entityClassToGenuineVariableListMap = new HashMap<>(); + Map> entityClassToShadowVariableListMap = new HashMap<>(); + for (EntityDescriptor entityDescriptor : solutionDescriptor.getEntityDescriptors()) { + entityClassList.add(entityDescriptor.getEntityClass().getName()); + List entityClassToGenuineVariableList = new ArrayList<>(); + List entityClassToShadowVariableList = new ArrayList<>(); + for (VariableDescriptor variableDescriptor : entityDescriptor.getDeclaredVariableDescriptors()) { + if (variableDescriptor instanceof GenuineVariableDescriptor) { + entityClassToGenuineVariableList.add(variableDescriptor.getVariableName()); + } else { + entityClassToShadowVariableList.add(variableDescriptor.getVariableName()); + } + } + entityClassToGenuineVariableListMap.put(entityDescriptor.getEntityClass().getName(), + entityClassToGenuineVariableList); + entityClassToShadowVariableListMap.put(entityDescriptor.getEntityClass().getName(), + entityClassToShadowVariableList); + } + out.setEntityClassList(entityClassList); + out.setEntityClassToGenuineVariableListMap(entityClassToGenuineVariableListMap); + out.setEntityClassToShadowVariableListMap(entityClassToShadowVariableListMap); + return out; + } else { + return new OptaPlannerModelProperties(); + } + } + + private List getConstraintList() { + if (effectiveSolverConfigXml != null) { + DefaultSolverFactory solverFactory = + (DefaultSolverFactory) Arc.container().instance(SolverFactory.class).get(); + if (solverFactory.getScoreDirectorFactory() instanceof AbstractConstraintStreamScoreDirectorFactory) { + AbstractConstraintStreamScoreDirectorFactory scoreDirectorFactory = + (AbstractConstraintStreamScoreDirectorFactory) solverFactory.getScoreDirectorFactory(); + return Arrays.stream(scoreDirectorFactory.getConstraints()).map(Constraint::getConstraintId) + .collect(Collectors.toList()); + } + } + return Collections.emptyList(); + } + + private String getXmlContentWithComment(String comment) { + int indexOfPreambleEnd = effectiveSolverConfigXml.indexOf("?>"); + if (indexOfPreambleEnd != -1) { + return effectiveSolverConfigXml.substring(0, indexOfPreambleEnd + 2) + + "\n\n" + + effectiveSolverConfigXml.substring(indexOfPreambleEnd + 2); + } else { + return "\n" + effectiveSolverConfigXml; + } + } +} diff --git a/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerModelProperties.java b/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerModelProperties.java new file mode 100644 index 00000000000..3d3f79a4456 --- /dev/null +++ b/optaplanner-quarkus-integration/optaplanner-quarkus/runtime/src/main/java/org/optaplanner/quarkus/devui/OptaPlannerModelProperties.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021 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.quarkus.devui; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class OptaPlannerModelProperties { + String solutionClass; + List entityClassList; + Map> entityClassToGenuineVariableListMap; + Map> entityClassToShadowVariableListMap; + + public OptaPlannerModelProperties() { + solutionClass = "null"; + entityClassList = Collections.emptyList(); + entityClassToGenuineVariableListMap = Collections.emptyMap(); + entityClassToShadowVariableListMap = Collections.emptyMap(); + } + + public String getSolutionClass() { + return solutionClass; + } + + public void setSolutionClass(String solutionClass) { + this.solutionClass = solutionClass; + } + + public List getEntityClassList() { + return entityClassList; + } + + public void setEntityClassList(List entityClassList) { + this.entityClassList = entityClassList; + } + + public Map> getEntityClassToGenuineVariableListMap() { + return entityClassToGenuineVariableListMap; + } + + public void setEntityClassToGenuineVariableListMap( + Map> entityClassToGenuineVariableListMap) { + this.entityClassToGenuineVariableListMap = entityClassToGenuineVariableListMap; + } + + public Map> getEntityClassToShadowVariableListMap() { + return entityClassToShadowVariableListMap; + } + + public void setEntityClassToShadowVariableListMap( + Map> entityClassToShadowVariableListMap) { + this.entityClassToShadowVariableListMap = entityClassToShadowVariableListMap; + } +}