package org.optaconf.bridge.devoxx; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.ejb.LocalBean; import javax.enterprise.context.ApplicationScoped; import javax.json.Json; import javax.json.JsonArray; import javax.json.JsonObject; import javax.json.JsonReader; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.apache.commons.io.IOUtils; import org.optaconf.domain.Conference; import org.optaconf.domain.ConferenceParametrization; import org.optaconf.domain.Day; import org.optaconf.domain.Room; import org.optaconf.domain.Speaker; import org.optaconf.domain.SpeakingRelation; import org.optaconf.domain.Talk; import org.optaconf.domain.Timeslot; import org.optaconf.domain.Track; import org.optaconf.domain.UnavailableTimeslotRoomPenalty; import org.optaconf.util.TangoCssFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ApplicationScoped @LocalBean public class DevoxxImporter { private static final Logger LOG = LoggerFactory.getLogger(DevoxxImporter.class); @PersistenceContext(unitName = "optaconf-webapp-persistence-unit") private EntityManager em; private static final String REST_URL_ROOT = "http://cfp.devoxx.be/api/conferences/DV16"; // private static final String REST_URL_ROOT = "http://cfp.devoxx.fr/api/conferences/DevoxxFR2015"; private static class ImportData { protected Conference conference; protected Map<String, Track> trackMap; protected Map<String, Speaker> speakerMap; protected Map<String, Speaker> fullNameToSpeakerMap; protected Map<String, Talk> talkMap; protected Map<String, Room> roomMap; protected Map<String, Timeslot> timeslotMap; } public Conference importConference(boolean persist) { ImportData importData = new ImportData(); Conference conference = new Conference(); conference.setName("DEVOXX BE 2016"); // conference.setName("DEVOXX FR 2015"); conference.setComment("Imported on " + new Date().toString() + " from " + REST_URL_ROOT); conference.setConferenceParametrization(ConferenceParametrization.createDefault(conference)); importData.conference = conference; mapTracks(importData, persist); mapSpeakers(importData, persist); mapTalks(importData, persist); mapRooms(importData, persist); mapDays(importData, persist); buildUnavailableTimeslotRoomPenaltyList(importData, persist); if (persist) { em.persist(conference); } return conference; } private void mapTracks(ImportData importData, boolean persist) { importData.trackMap = new LinkedHashMap<String, Track>(); JsonObject rootObject = readJsonObject(REST_URL_ROOT + "/tracks"); JsonArray array = rootObject.getJsonArray("tracks"); TangoCssFactory tangoColorFactory = new TangoCssFactory(); for (int i = 0; i < array.size(); i++) { JsonObject dTrack = array.getJsonObject(i); String id = dTrack.getString("id"); String title = dTrack.getString("title"); String colorHex = tangoColorFactory.pickCssClass(id); Track track = new Track(importData.conference, id, title, colorHex); if (persist) { em.persist(track); } importData.conference.getTrackList().add(track); importData.trackMap.put(id, track); } } private void mapSpeakers(ImportData importData, boolean persist) { importData.speakerMap = new LinkedHashMap<String, Speaker>(); importData.fullNameToSpeakerMap = new LinkedHashMap<String, Speaker>(); JsonArray array = readJsonArray(REST_URL_ROOT + "/speakers/"); for (int i = 0; i < array.size(); i++) { JsonObject dSpeaker = array.getJsonObject(i); String id = dSpeaker.getString("uuid"); String firstName = dSpeaker.getString("firstName"); String lastName = dSpeaker.getString("lastName"); String fullName = firstName + " " + lastName; Speaker speaker = new Speaker(importData.conference, id, fullName); if (persist) { em.persist(speaker); } importData.conference.getSpeakerList().add(speaker); importData.speakerMap.put(id, speaker); importData.fullNameToSpeakerMap.put(fullName, speaker); } Collections.sort(importData.conference.getSpeakerList()); } private void mapTalks(ImportData importData, boolean persist) { importData.talkMap = new LinkedHashMap<String, Talk>(); JsonObject rootObject = readJsonObject(REST_URL_ROOT + "/talks/"); JsonObject subRootObject = rootObject.getJsonObject("talks"); JsonArray approvedArray = subRootObject.getJsonArray("approved"); JsonArray acceptedArray = subRootObject.getJsonArray("accepted"); for (int i = 0; i < approvedArray.size() + acceptedArray.size(); i++) { JsonObject dTalk = i < approvedArray.size() ? approvedArray.getJsonObject(i) : acceptedArray.getJsonObject(i - approvedArray.size()); // TODO Add hands on etc too JsonObject dTalkType = dTalk.getJsonObject("talkType"); if (!dTalkType.getString("id").equalsIgnoreCase("conf")) { continue; } String id = dTalk.getString("id"); String title = dTalk.getString("title"); JsonObject dTrack = dTalk.getJsonObject("track"); String trackId = dTrack.getString("id"); Track track = importData.trackMap.get(trackId); if (track == null) { throw new IllegalArgumentException("The trackId (" + trackId + ") is not part of the trackMap (" + importData.trackMap + ")."); } Talk talk = new Talk(importData.conference, id, title, track); if (persist) { em.persist(talk); } importData.conference.getTalkList().add(talk); importData.talkMap.put(id, talk); String mainSpeaker = dTalk.getString("mainSpeaker"); addSpeakerRelation(importData, talk, mainSpeaker); String secondarySpeaker = dTalk.getString("secondarySpeaker", null); if (secondarySpeaker != null) { addSpeakerRelation(importData, talk, secondarySpeaker); } JsonArray otherSpeakersArray = dTalk.getJsonArray("otherSpeakers"); for (int j = 0; j < otherSpeakersArray.size(); j++) { String otherSpeaker = otherSpeakersArray.getString(j); addSpeakerRelation(importData, talk, otherSpeaker); } } } private void addSpeakerRelation(ImportData importData, Talk talk, String speakerFullName) { // String speakerId = speakerFullName.getJsonObject("link").getString("href").replaceAll(".*/", ""); // Speaker speaker = importData.speakerMap.get(speakerId); Speaker speaker = importData.fullNameToSpeakerMap.get(speakerFullName); if (speaker == null) { LOG.warn("Ignoring speaking relation for speaker ({}) to talk ({}) because the speaker doesn't exist in the speaker list.", speakerFullName, talk.getTitle()); return; // throw new IllegalArgumentException("The speakerId (" + speakerFullName // + ") is not part of the fullNameToSpeakerMap (" + importData.fullNameToSpeakerMap + ")."); } SpeakingRelation speakingRelation = new SpeakingRelation( importData.conference, talk.getExternalId() + "_" + speaker.getExternalId(), talk, speaker); importData.conference.getSpeakingRelationList().add(speakingRelation); } private void mapRooms(ImportData importData, boolean persist) { importData.roomMap = new LinkedHashMap<String, Room>(); JsonObject rootObject = readJsonObject(REST_URL_ROOT + "/rooms/"); JsonArray array = rootObject.getJsonArray("rooms"); for (int i = 0; i < array.size(); i++) { JsonObject dRoom = array.getJsonObject(i); String id = dRoom.getString("id"); String name = dRoom.getString("name"); int capacity = dRoom.getInt("capacity"); // TODO Add RoomType and store BOF rooms etc too if (!dRoom.getString("setup").equals("theatre")) { continue; } Room room = new Room(importData.conference, id, name, capacity); if (persist) { em.persist(room); } importData.conference.getRoomList().add(room); importData.roomMap.put(id, room); } Collections.sort(importData.conference.getRoomList()); } private void mapDays(ImportData importData, boolean persist) { JsonObject rootObject = readJsonObject(REST_URL_ROOT + "/schedules/"); JsonArray array = rootObject.getJsonArray("links"); Pattern[] dTitlePatterns = { Pattern.compile("Schedule for (\\w+) (\\d+.*\\d{4})"), // Used in Devoxx FR 2015 Pattern.compile("(\\w+), (\\d+.*\\d{4})") // Used in Devoxx BE 2015 }; for (int i = 0; i < array.size(); i++) { JsonObject dDay = array.getJsonObject(i); String dHref = dDay.getString("href"); String dTitle = dDay.getString("title"); String name = null; String date = null; for (Pattern dTitlePattern : dTitlePatterns) { Matcher dTitleMatcher = dTitlePattern.matcher(dTitle); if (dTitleMatcher.find() && dTitleMatcher.groupCount() == 2) { name = dTitleMatcher.group(1); date = dTitleMatcher.group(2); break; } } if (name == null) { throw new IllegalStateException("A schedules title (" + dTitle + ") does not match any of the patterns (" + Arrays.toString(dTitlePatterns) + ")."); } Day day = new Day(importData.conference, dHref.replaceAll(".*\\/(.*)/", "$1"), name, date); if (persist) { em.persist(day); } importData.conference.getDayList().add(day); mapTalksToRoomsAndTimeslots(importData, dHref, day, persist); } Collections.sort(importData.conference.getDayList()); } private void mapTalksToRoomsAndTimeslots(ImportData importData, String dayUrl, Day day, boolean persist) { importData.timeslotMap = new LinkedHashMap<String, Timeslot>(); JsonObject rootObject = readJsonObject(dayUrl); JsonArray array = rootObject.getJsonArray("slots"); for (int i = 0; i < array.size(); i++) { JsonObject dSlot = array.getJsonObject(i); if (dSlot.isNull("talk")) { continue; } JsonObject dTalk = dSlot.getJsonObject("talk"); String roomId = dSlot.getString("roomId"); Room room = importData.roomMap.get(roomId); String fromTime = dSlot.getString("fromTime"); String toTime = dSlot.getString("toTime"); String timeslotId = fromTime + " - " + toTime; Timeslot timeslot = importData.timeslotMap.get(timeslotId); if (timeslot == null) { timeslot = new Timeslot(importData.conference, timeslotId, timeslotId, day, fromTime, toTime); if (persist) { em.persist(timeslot); } importData.conference.getTimeslotList().add(timeslot); importData.timeslotMap.put(timeslotId, timeslot); } // TODO FIXME // Talk talk = new Talk(conference, id, title, room, track, timeslot); // // if (persist) { // em.persist(talk); // } // // conference.getTalkList().add(talk); } Collections.sort(importData.conference.getTimeslotList()); } private void buildUnavailableTimeslotRoomPenaltyList(ImportData importData, boolean persist) { Map<Timeslot, Map<Room, Talk>> timeslotRoomToTalkMap = new LinkedHashMap<>(); for (Talk talk : importData.conference.getTalkList()) { Timeslot timeslot = talk.getTimeslot(); Room room = talk.getRoom(); if (timeslot == null || room == null) { continue; } Map<Room, Talk> roomToTalkMap = timeslotRoomToTalkMap.get(timeslot); if (roomToTalkMap == null) { roomToTalkMap = new LinkedHashMap<>(); timeslotRoomToTalkMap.put(timeslot, roomToTalkMap); } roomToTalkMap.put(room, talk); } for (Timeslot timeslot : importData.conference.getTimeslotList()) { Map<Room, Talk> roomToTalkMap = timeslotRoomToTalkMap.get(timeslot); for (Room room : importData.conference.getRoomList()) { if (roomToTalkMap == null || !roomToTalkMap.containsKey(room)) { UnavailableTimeslotRoomPenalty penalty = new UnavailableTimeslotRoomPenalty( importData.conference, timeslot.getExternalId() + "_" + room.getExternalId(), timeslot, room); if (persist) { em.persist(penalty); } importData.conference.getUnavailableTimeslotRoomPenaltyList().add(penalty); } } } } private JsonObject readJsonObject(String url) { InputStream schedulesIn = null; try { schedulesIn = new URL(url).openConnection().getInputStream(); JsonReader jsonReader = Json.createReader(schedulesIn); return jsonReader.readObject(); } catch (UnknownHostException e) { throw new IllegalStateException( "Check your network connection. Import from Devoxx CFP failed on URL (" + url + ").", e); } catch (MalformedURLException e) { throw new IllegalStateException( "Check the Devoxx CFP URL. Import from Devoxx CFP failed on URL (" + url + ").", e); } catch (IOException e) { throw new IllegalStateException( "Import from Devoxx CFP failed on URL (" + url + ").", e); } finally { IOUtils.closeQuietly(schedulesIn); } } private JsonArray readJsonArray(String url) { InputStream schedulesIn = null; try { schedulesIn = new URL(url).openConnection().getInputStream(); JsonReader jsonReader = Json.createReader(schedulesIn); return jsonReader.readArray(); } catch (MalformedURLException e) { throw new IllegalStateException( "Import from Devoxx CFP failed on URL (" + url + ").", e); } catch (IOException e) { throw new IllegalStateException( "Import from Devoxx CFP failed on URL (" + url + ").", e); } finally { IOUtils.closeQuietly(schedulesIn); } } }