Skip to content

Commit

Permalink
PLANNER-1168 conference scheduling devoxx rest api (#407)
Browse files Browse the repository at this point in the history
* PLANNER-1168: Setup and create Importer class

* Implement importer methods

* Check response code and redirect the request if necessary

* Fix start/endDateTime zoneId in timeslot

* Implement importing timeslots and update talks accordingly

* Fix managed dependency issue and import talkTypes properly

* Aggregate timeslots that start and end at same time, and implement a workaround inconsistent data in DevoxxFr

* Add breaks as talks and fix talkTypes for rooms

* Add room tag sets for rooms and talks according to room size and talk type

* Use lambda function to get JsonObject and JsonArray and refactor checking redirects into a utility class"

* Add trackId endpoint and use trackId instead of track description

* Add tests for ConnectionFollowRedirects

* Clean-up ConnectionFollowRedirectsTest

* Close unused inputStream in ConnectionFollowRedirectsTest

* Exclude duplicate dependency from mockserver-netty

* Downgrade javax.json to workarount IllegalArgumentException issue Scout24/illegal-transitive-dependency-check#30

* Fix typos and style

* Add workaround for missing speakers in DevoxxBE18

* Replace e.printStackTrace by logging an error

* Move dependencyManagement to parent pom

* Workaround to read talks from local file

* Get timeslot's talkTypes from timeslot's slotId

* Import talk's contentTag and add statistics

* Remove RESTEndpoints class and replace its fields with local variables

* Initialize new Talk's fields

* Convert score to HardMediumSoft and make MutuallyExclusiveTalks a medium constraint

* Color cells with negative mediumScore with mediumPenaltyStyle

* Convert Talk mutually-exclusive-talks to a medium and soft constraint

* Update importer to new version of talks

* Write another roomView with rooms as columns

* Replace roomView 2 with separate view sheet per day

* Color printed views according to theme tracks

* Fix printing daysSheets even if some talks aren't scheduled

* Abbreviate title in DaysView

* Remove mock-server dependency

* Fix typo in configuration sheet

* Fix typo in test xlsx file
  • Loading branch information
MusaTalluzi authored and ge0ffrey committed Sep 21, 2018
1 parent eb68bc0 commit fb8f167
Show file tree
Hide file tree
Showing 18 changed files with 845 additions and 50 deletions.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions optaplanner-examples/pom.xml
Expand Up @@ -175,6 +175,10 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
</dependency>
</dependencies> </dependencies>


</project> </project>
Expand Up @@ -23,8 +23,7 @@
import org.optaplanner.core.api.domain.solution.PlanningSolution; import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty; import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactProperty; import org.optaplanner.core.api.domain.solution.drools.ProblemFactProperty;
import org.optaplanner.core.api.domain.valuerange.ValueRangeProvider; import org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
import org.optaplanner.examples.common.domain.AbstractPersistable; import org.optaplanner.examples.common.domain.AbstractPersistable;


@PlanningSolution @PlanningSolution
Expand All @@ -50,7 +49,7 @@ public class ConferenceSolution extends AbstractPersistable {
private List<Talk> talkList; private List<Talk> talkList;


@PlanningScore @PlanningScore
private HardSoftScore score = null; private HardMediumSoftScore score = null;


public ConferenceSolution() { public ConferenceSolution() {
} }
Expand Down Expand Up @@ -124,11 +123,11 @@ public void setTalkList(List<Talk> talkList) {
this.talkList = talkList; this.talkList = talkList;
} }


public HardSoftScore getScore() { public HardMediumSoftScore getScore() {
return score; return score;
} }


public void setScore(HardSoftScore score) { public void setScore(HardMediumSoftScore score) {
this.score = score; this.score = score;
} }


Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -0,0 +1,91 @@
/*
* Copyright 2018 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.
*/

// credits for https://www.cs.mun.ca/java-api-1.5/guide/deployment/deployment-guide/upgrade-guide/article-17.html

package org.optaplanner.examples.conferencescheduling.persistence;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

public class ConnectionFollowRedirects {

private URLConnection connection;
private boolean isRedirect;
private int redirects = 0;

public ConnectionFollowRedirects(String url) throws IOException {
this.connection = new URL(url).openConnection();
}

public URLConnection getConnection() {
return connection;
}

public int getRedirects() {
return redirects;
}

public InputStream getInputStream() throws IOException {
InputStream in = null;
do {
if (connection instanceof HttpURLConnection) {
((HttpURLConnection) connection).setInstanceFollowRedirects(false);
}
// We want to open the input stream before getting headers
// because getHeaderField() et al swallow IOExceptions.
in = connection.getInputStream();
followRedirects();
}
while (isRedirect);
return in;
}

private void followRedirects() throws IOException {
isRedirect = false;
if (connection instanceof HttpURLConnection) {
HttpURLConnection http = (HttpURLConnection) connection;
int stat = http.getResponseCode();
if (stat >= 300 && stat <= 307 && stat != 306 &&
stat != HttpURLConnection.HTTP_NOT_MODIFIED) {
redirectConnection(http);
}
}
}

private void redirectConnection(HttpURLConnection http) throws IOException {
URL base = http.getURL();
String location = http.getHeaderField("Location");
URL target = null;
if (location != null) {
target = new URL(base, location);
}
http.disconnect();
// Redirection should be allowed only for HTTP and HTTPS
// and should be limited to 5 redirections at most.
if (target == null || !(target.getProtocol().equals("http")
|| target.getProtocol().equals("https"))
|| redirects >= 5) {
throw new SecurityException("illegal URL redirect");
}
isRedirect = true;
connection = target.openConnection();
redirects++;
}
}
Expand Up @@ -24,6 +24,7 @@


import org.optaplanner.examples.common.swingui.SolutionPanel; import org.optaplanner.examples.common.swingui.SolutionPanel;
import org.optaplanner.examples.conferencescheduling.domain.ConferenceSolution; import org.optaplanner.examples.conferencescheduling.domain.ConferenceSolution;
import org.optaplanner.examples.conferencescheduling.persistence.ConferenceSchedulingCfpDevoxxImporter;
import org.optaplanner.examples.conferencescheduling.persistence.ConferenceSchedulingXlsxFileIO; import org.optaplanner.examples.conferencescheduling.persistence.ConferenceSchedulingXlsxFileIO;
import org.optaplanner.persistence.common.api.domain.solution.SolutionFileIO; import org.optaplanner.persistence.common.api.domain.solution.SolutionFileIO;


Expand All @@ -32,6 +33,13 @@ public class ConferenceSchedulingPanel extends SolutionPanel<ConferenceSolution>
public static final String LOGO_PATH = "/org/optaplanner/examples/conferencescheduling/swingui/conferenceSchedulingLogo.png"; public static final String LOGO_PATH = "/org/optaplanner/examples/conferencescheduling/swingui/conferenceSchedulingLogo.png";


public ConferenceSchedulingPanel() { public ConferenceSchedulingPanel() {
JButton importConferenceButton = new JButton("Import conference");
importConferenceButton.addActionListener(event -> {
//TODO: Add a panel to get conferenceBaseUrl from the user
ConferenceSchedulingCfpDevoxxImporter conferenceSchedulingImporter = new ConferenceSchedulingCfpDevoxxImporter("https://dvbe18.confinabox.com/api/conferences/DVBE18");
solutionBusiness.setSolution(conferenceSchedulingImporter.importSolution());
});

JButton button = new JButton("Show in LibreOffice or Excel"); JButton button = new JButton("Show in LibreOffice or Excel");
button.addActionListener(event -> { button.addActionListener(event -> {
SolutionFileIO<ConferenceSolution> solutionFileIO = new ConferenceSchedulingXlsxFileIO(); SolutionFileIO<ConferenceSolution> solutionFileIO = new ConferenceSchedulingXlsxFileIO();
Expand All @@ -48,6 +56,7 @@ public ConferenceSchedulingPanel() {
throw new IllegalStateException("Failed to show temp file (" + tempFile + ") in LibreOffice or Excel.", e); throw new IllegalStateException("Failed to show temp file (" + tempFile + ") in LibreOffice or Excel.", e);
} }
}); });
add(importConferenceButton);
add(button); add(button);
add(new JLabel("Changes to that file are ignored unless you explicitly save it there and open it here.")); add(new JLabel("Changes to that file are ignored unless you explicitly save it there and open it here."));
} }
Expand Down
Expand Up @@ -17,15 +17,15 @@
package org.optaplanner.examples.conferencescheduling.solver; package org.optaplanner.examples.conferencescheduling.solver;
dialect "java" dialect "java"


import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScoreHolder; import org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScoreHolder;


import org.optaplanner.examples.conferencescheduling.domain.ConferenceParametrization; import org.optaplanner.examples.conferencescheduling.domain.ConferenceParametrization;
import org.optaplanner.examples.conferencescheduling.domain.Room; import org.optaplanner.examples.conferencescheduling.domain.Room;
import org.optaplanner.examples.conferencescheduling.domain.Speaker; import org.optaplanner.examples.conferencescheduling.domain.Speaker;
import org.optaplanner.examples.conferencescheduling.domain.Talk; import org.optaplanner.examples.conferencescheduling.domain.Talk;
import org.optaplanner.examples.conferencescheduling.domain.Timeslot; import org.optaplanner.examples.conferencescheduling.domain.Timeslot;


global HardSoftScoreHolder scoreHolder; global HardMediumSoftScoreHolder scoreHolder;


// ############################################################################ // ############################################################################
// Hard constraints // Hard constraints
Expand Down Expand Up @@ -153,23 +153,32 @@ rule "Talk prohibited room tags"
scoreHolder.addHardConstraintMatch(kcontext, - $count * $weight); scoreHolder.addHardConstraintMatch(kcontext, - $count * $weight);
end end


rule "Talk prerequisite talks"
when
ConferenceParametrization($weight : talkPrerequisiteTalks != 0)
$talk : Talk(timeslot != null, missingPrerequisiteCount() > 0)
then
scoreHolder.addHardConstraintMatch(kcontext, - $weight * $talk.missingPrerequisiteCount());
end

// ############################################################################
// Medium constraints
// ############################################################################
rule "Talk mutually-exclusive-talks tags" rule "Talk mutually-exclusive-talks tags"
when when
ConferenceParametrization($weight : talkMutuallyExclusiveTalksTags != 0) ConferenceParametrization($weight : talkMutuallyExclusiveTalksTags != 0)
$leftTalk : Talk(timeslot != null, $timeslot : timeslot, $leftId : id) $leftTalk : Talk(timeslot != null, $timeslot : timeslot, $leftId : id)
$rightTalk : Talk(timeslot != null, overlappingMutuallyExclusiveTalksTagCount($leftTalk) > 0, $rightTalk : Talk(timeslot != null, overlappingMutuallyExclusiveTalksTagCount($leftTalk) > 0,
getTimeslot().overlaps($timeslot), timeslot.isOnSameDayAs($timeslot),
id > $leftId) id > $leftId)
then then
scoreHolder.addHardConstraintMatch(kcontext, - $weight * $rightTalk.overlappingMutuallyExclusiveTalksTagCount($leftTalk)); if ($rightTalk.getTimeslot().overlaps($leftTalk.getTimeslot())) { // Medium constraint if they overlap as well:
end scoreHolder.addMultiConstraintMatch(kcontext, 0,

- $weight * $rightTalk.overlappingMutuallyExclusiveTalksTagCount($leftTalk),
rule "Talk prerequisite talks" - $weight * $rightTalk.overlappingMutuallyExclusiveTalksTagCount($leftTalk));
when } else {
ConferenceParametrization($weight : talkPrerequisiteTalks != 0) scoreHolder.addSoftConstraintMatch(kcontext, - $weight * $rightTalk.overlappingMutuallyExclusiveTalksTagCount($leftTalk));
$talk : Talk(timeslot != null, missingPrerequisiteCount() > 0) }
then
scoreHolder.addHardConstraintMatch(kcontext, - $weight * $talk.missingPrerequisiteCount());
end end


// ############################################################################ // ############################################################################
Expand Down
Expand Up @@ -41,13 +41,13 @@ protected ConferenceSchedulingApp createCommonApp() {
@Test(timeout = 600000) @Test(timeout = 600000)
public void solveModel() { public void solveModel() {
File unsolvedDataFile = new File("data/conferencescheduling/unsolved/72talks-12timeslots-10rooms.xlsx"); File unsolvedDataFile = new File("data/conferencescheduling/unsolved/72talks-12timeslots-10rooms.xlsx");
runSpeedTest(unsolvedDataFile, "-1hard/-250soft"); runSpeedTest(unsolvedDataFile, "-1hard/0medium/-250soft");
} }


@Test(timeout = 600000) @Test(timeout = 600000)
public void solveModelFastAssert() { public void solveModelFastAssert() {
File unsolvedDataFile = new File("data/conferencescheduling/unsolved/72talks-12timeslots-10rooms.xlsx"); File unsolvedDataFile = new File("data/conferencescheduling/unsolved/72talks-12timeslots-10rooms.xlsx");
runSpeedTest(unsolvedDataFile, "-1hard/-290soft", EnvironmentMode.FAST_ASSERT); runSpeedTest(unsolvedDataFile, "-1hard/0medium/-290soft", EnvironmentMode.FAST_ASSERT);
} }


} }
Expand Up @@ -15,13 +15,13 @@
import org.optaplanner.examples.conferencescheduling.domain.Talk; import org.optaplanner.examples.conferencescheduling.domain.Talk;
import org.optaplanner.examples.conferencescheduling.domain.TalkType; import org.optaplanner.examples.conferencescheduling.domain.TalkType;
import org.optaplanner.examples.conferencescheduling.domain.Timeslot; import org.optaplanner.examples.conferencescheduling.domain.Timeslot;
import org.optaplanner.test.impl.score.buildin.hardsoft.HardSoftScoreVerifier; import org.optaplanner.test.impl.score.buildin.hardmediumsoft.HardMediumSoftScoreVerifier;


import static org.optaplanner.examples.conferencescheduling.domain.ConferenceParametrization.*; import static org.optaplanner.examples.conferencescheduling.domain.ConferenceParametrization.*;


public class ConferenceSchedulingScoreHardConstraintTest { public class ConferenceSchedulingScoreHardConstraintTest {


private HardSoftScoreVerifier<ConferenceSolution> scoreVerifier = new HardSoftScoreVerifier<>( private HardMediumSoftScoreVerifier<ConferenceSolution> scoreVerifier = new HardMediumSoftScoreVerifier<>(
SolverFactory.createFromXmlResource(ConferenceSchedulingApp.SOLVER_CONFIG)); SolverFactory.createFromXmlResource(ConferenceSchedulingApp.SOLVER_CONFIG));


@Test @Test
Expand Down
Expand Up @@ -41,7 +41,7 @@
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Parameterized; import org.junit.runners.Parameterized;
import org.optaplanner.core.api.domain.solution.cloner.SolutionCloner; import org.optaplanner.core.api.domain.solution.cloner.SolutionCloner;
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; import org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore;
import org.optaplanner.core.api.solver.SolverFactory; import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor; import org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor;
import org.optaplanner.examples.common.persistence.AbstractXlsxSolutionFileIO; import org.optaplanner.examples.common.persistence.AbstractXlsxSolutionFileIO;
Expand All @@ -51,27 +51,27 @@
import org.optaplanner.examples.conferencescheduling.domain.Talk; import org.optaplanner.examples.conferencescheduling.domain.Talk;
import org.optaplanner.examples.conferencescheduling.domain.Timeslot; import org.optaplanner.examples.conferencescheduling.domain.Timeslot;
import org.optaplanner.examples.conferencescheduling.persistence.ConferenceSchedulingXlsxFileIO; import org.optaplanner.examples.conferencescheduling.persistence.ConferenceSchedulingXlsxFileIO;
import org.optaplanner.test.impl.score.buildin.hardsoft.HardSoftScoreVerifier; import org.optaplanner.test.impl.score.buildin.hardmediumsoft.HardMediumSoftScoreVerifier;


import static org.optaplanner.examples.common.persistence.AbstractXlsxSolutionFileIO.*; import static org.optaplanner.examples.common.persistence.AbstractXlsxSolutionFileIO.*;


@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class ConferenceSchedulingScoreRulesXlsxTest { public class ConferenceSchedulingScoreRulesXlsxTest {


private static final String testFileName = "testConferenceSchedulingScoreRules.xlsx"; private static final String testFileName = "testConferenceSchedulingScoreRules.xlsx";
private static final HardSoftScore unassignedScore = HardSoftScore.ZERO; private static final HardMediumSoftScore unassignedScore = HardMediumSoftScore.ZERO;


private String constraintPackage; private String constraintPackage;
private String constraintName; private String constraintName;
private HardSoftScore expectedScore; private HardMediumSoftScore expectedScore;
private ConferenceSolution solution; private ConferenceSolution solution;
private String testSheetName; private String testSheetName;


private static HardSoftScoreVerifier<ConferenceSolution> scoreVerifier = new HardSoftScoreVerifier<>( private static HardMediumSoftScoreVerifier<ConferenceSolution> scoreVerifier = new HardMediumSoftScoreVerifier<>(
SolverFactory.createFromXmlResource(ConferenceSchedulingApp.SOLVER_CONFIG)); SolverFactory.createFromXmlResource(ConferenceSchedulingApp.SOLVER_CONFIG));


public ConferenceSchedulingScoreRulesXlsxTest(String constraintPackage, String constraintName, public ConferenceSchedulingScoreRulesXlsxTest(String constraintPackage, String constraintName,
HardSoftScore expectedScore, ConferenceSolution solution, String testSheetName) { HardMediumSoftScore expectedScore, ConferenceSolution solution, String testSheetName) {
this.constraintPackage = constraintPackage; this.constraintPackage = constraintPackage;
this.constraintName = constraintName; this.constraintName = constraintName;
this.expectedScore = expectedScore; this.expectedScore = expectedScore;
Expand Down Expand Up @@ -104,6 +104,7 @@ public static Collection testSheetParameters() {
@Test @Test
public void scoreRules() { public void scoreRules() {
scoreVerifier.assertHardWeight(constraintPackage, constraintName, expectedScore.getHardScore(), solution); scoreVerifier.assertHardWeight(constraintPackage, constraintName, expectedScore.getHardScore(), solution);
scoreVerifier.assertMediumWeight(constraintPackage, constraintName, expectedScore.getMediumScore(), solution);
scoreVerifier.assertSoftWeight(constraintPackage, constraintName, expectedScore.getSoftScore(), solution); scoreVerifier.assertSoftWeight(constraintPackage, constraintName, expectedScore.getSoftScore(), solution);
} }


Expand Down Expand Up @@ -143,7 +144,7 @@ public ConferenceSolution read() {
private Object[] nextTestSheetParameterList() { private Object[] nextTestSheetParameterList() {
String constraintPackage; String constraintPackage;
String constraintName; String constraintName;
HardSoftScore expectedScore; HardMediumSoftScore expectedScore;
ConferenceSolution nextSheetSolution; ConferenceSolution nextSheetSolution;
String testSheetName; String testSheetName;


Expand All @@ -163,7 +164,7 @@ private Object[] nextTestSheetParameterList() {
nextRow(false); nextRow(false);
nextRow(false); nextRow(false);
readHeaderCell("Score"); readHeaderCell("Score");
expectedScore = HardSoftScore.parseScore(nextStringCell().getStringCellValue()); expectedScore = HardMediumSoftScore.parseScore(nextStringCell().getStringCellValue());


nextSheetSolution = solutionCloner.cloneSolution(initialSolution); nextSheetSolution = solutionCloner.cloneSolution(initialSolution);
Map<String, Talk> talkMap = nextSheetSolution.getTalkList().stream().collect( Map<String, Talk> talkMap = nextSheetSolution.getTalkList().stream().collect(
Expand Down
Expand Up @@ -15,13 +15,13 @@
import org.optaplanner.examples.conferencescheduling.domain.Talk; import org.optaplanner.examples.conferencescheduling.domain.Talk;
import org.optaplanner.examples.conferencescheduling.domain.TalkType; import org.optaplanner.examples.conferencescheduling.domain.TalkType;
import org.optaplanner.examples.conferencescheduling.domain.Timeslot; import org.optaplanner.examples.conferencescheduling.domain.Timeslot;
import org.optaplanner.test.impl.score.buildin.hardsoft.HardSoftScoreVerifier; import org.optaplanner.test.impl.score.buildin.hardmediumsoft.HardMediumSoftScoreVerifier;


import static org.optaplanner.examples.conferencescheduling.domain.ConferenceParametrization.*; import static org.optaplanner.examples.conferencescheduling.domain.ConferenceParametrization.*;


public class ConferenceSchedulingScoreSoftConstraintTest { public class ConferenceSchedulingScoreSoftConstraintTest {


private HardSoftScoreVerifier<ConferenceSolution> scoreVerifier = new HardSoftScoreVerifier<>( private HardMediumSoftScoreVerifier<ConferenceSolution> scoreVerifier = new HardMediumSoftScoreVerifier<>(
SolverFactory.createFromXmlResource(ConferenceSchedulingApp.SOLVER_CONFIG)); SolverFactory.createFromXmlResource(ConferenceSchedulingApp.SOLVER_CONFIG));


@Test @Test
Expand Down
Binary file not shown.
6 changes: 6 additions & 0 deletions pom.xml
Expand Up @@ -84,6 +84,12 @@
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<!-- Old version until this issue is resolved: https://github.com/ImmobilienScout24/illegal-transitive-dependency-check/issues/30 -->
<version>1.0.4</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>


Expand Down

0 comments on commit fb8f167

Please sign in to comment.