/* * Copyright (c) 2013 Technische Universitat Wien (TUW), Distributed Systems Group. http://dsg.tuwien.ac.at * * This work was partially supported by the European Commission in terms of the CELAR FP7 project (FP7-ICT-2011-8 #317790), http://www.celarcloud.eu/ * * 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 at.ac.tuwien.dsg.cloud.elise.master.RESTService; import at.ac.tuwien.dsg.cloud.elise.master.RESTImp.EliseCommunicationInterface; import at.ac.tuwien.dsg.cloud.elise.master.Communication.QueryManager; import at.ac.tuwien.dsg.cloud.salsa.messaging.protocol.EliseQueueTopic; import at.ac.tuwien.dsg.cloud.elise.master.QueryManagement.utils.EliseConfiguration; import at.ac.tuwien.dsg.cloud.salsa.messaging.protocol.SalsaMessage; import at.ac.tuwien.dsg.cloud.elise.model.runtime.UnitInstance; import at.ac.tuwien.dsg.cloud.elise.model.wrapper.UnitInstanceWrapper; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessageClientFactory; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessagePublishInterface; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.MessageSubscribeInterface; import at.ac.tuwien.dsg.cloud.salsa.messaging.messageInterface.SalsaMessageHandling; import at.ac.tuwien.dsg.cloud.elise.collectorinterfaces.models.ConductorDescription; import at.ac.tuwien.dsg.cloud.salsa.messaging.model.Elise.EliseQuery; import at.ac.tuwien.dsg.cloud.salsa.messaging.model.Elise.EliseQueryProcessNotification; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.logging.Level; import javax.ws.rs.DefaultValue; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; import org.apache.cxf.jaxrs.client.JAXRSClientFactory; import org.slf4j.Logger; /** * This service contains APIs to communicate with distributed collectors to * gather information * * @author Duc-Hung Le */ @Path("/communication") public class EliseCommunicationService implements EliseCommunicationInterface { static Logger logger = EliseConfiguration.logger; static String listenerTopic = "at.ac.tuwien.dsg.comot.elise.listener"; MessageClientFactory factory = MessageClientFactory.getFactory(EliseConfiguration.getBroker(), EliseConfiguration.getBrokerType()); static int eliseCounter = 0; /** * Get the information of available conductors and collectors which are * running and registered. * * @return The description of all conductors. */ // @GET // @Path("/count") @Override public String count() { String uuid = UUID.randomUUID().toString(); final List<ConductorDescription> conductors = new ArrayList<>(); eliseCounter = 0; MessageSubscribeInterface sub = factory.getMessageSubscriber(new SalsaMessageHandling() { @Override public void handleMessage(SalsaMessage salsaMessage) { ConductorDescription conductor = ConductorDescription.fromJson(salsaMessage.getPayload()); if (conductor == null) { logger.error("Cannot unmarshall the ConductorDescription: {}", salsaMessage.getPayload()); return; } eliseCounter += 1; logger.debug("Get a response from ELISE: " + eliseCounter + "," + conductor.toJson()); conductors.add(conductor); } }); sub.subscribe(EliseQueueTopic.getFeedBackTopic(uuid)); MessagePublishInterface pub = factory.getMessagePublisher(); pub.pushMessage(new SalsaMessage(SalsaMessage.MESSAGE_TYPE.discover, EliseConfiguration.getEliseID(), EliseQueueTopic.QUERY_TOPIC, EliseQueueTopic.getFeedBackTopic(uuid), "")); try { Thread.sleep(5000L); // wait 5 secs for other elises to answer } catch (InterruptedException ex) { logger.debug(ex.getMessage()); } logger.debug("Found " + eliseCounter + " Conductor(s)"); String result = eliseCounter + ""; for (ConductorDescription s : conductors) { result += "," + s.getId(); } //sub.disconnect(); return result; } static long startTime; int count; String rtLogFile; /** * Send a query to all the conductors, which then will trigger collector * module, collect the information, and update to the central service. * * @param query A query to specify which information to be collected * @param isUpdated The information will be update continously or this is an * one-time query * @param isNotified The change will be notify to the topic * @return An UUID of the query, which can be used to trace the status of * this query. */ // @POST // @Path("/queryUnitInstance") // @Consumes(MediaType.APPLICATION_JSON) @Override public String querySetOfInstance(EliseQuery query, @DefaultValue("false") @QueryParam("isUpdated") final boolean isUpdated, @DefaultValue("false") @QueryParam("notify") final boolean isNotified) { logger.debug("Broadcast a query to gather instances... Query: " + query.toString()); final String uuid = UUID.randomUUID().toString(); logger.debug("UUID of the request: " + uuid); // clean local DB //EliseManager eliseDB = (EliseManager) JAXRSClientFactory.create(EliseConfiguration.getRESTEndpointLocal(), EliseManager.class, Collections.singletonList(new JacksonJsonProvider())); //eliseDB.cleanDB(); count = 0; startTime = Calendar.getInstance().getTimeInMillis(); rtLogFile = ("log/rt_log_" + startTime + "." + uuid); MessageSubscribeInterface sub = factory.getMessageSubscriber(new SalsaMessageHandling() { Map<String, String> answeredElises = new HashMap(); @Override public void handleMessage(SalsaMessage message) { toHandleMessageAndSaveData(message, answeredElises, isNotified, uuid); } }); logger.debug("Subscribing the topic: " + EliseQueueTopic.getFeedBackTopic(uuid)); sub.subscribe(EliseQueueTopic.getFeedBackTopic(uuid)); logger.debug("Subscribe to feedback topic done, now push request ..."); MessagePublishInterface pub = factory.getMessagePublisher(); pub.pushMessage(new SalsaMessage(SalsaMessage.MESSAGE_TYPE.elise_queryManyInstances, EliseConfiguration.getEliseID(), EliseQueueTopic.QUERY_TOPIC, EliseQueueTopic.getFeedBackTopic(uuid), query.toJson())); logger.debug("Push message done, just waiting for the message ..."); //unsubscribe(sub, 120); return uuid; } @Override public String querySetOfInstances(EliseQuery query) { return querySetOfInstance(query, false, false); } // the collector module can execute with single domainID, but from this level, it is imposible? // must use the querySetOfInstance. // @Deprecated // @POST // @Path("/queryUnitInstance/{domainID}") // @Consumes(MediaType.APPLICATION_JSON) // public String querySingleInstance(@PathParam("domainID") String instanceID, // @DefaultValue("false") @QueryParam("isUpdated") final boolean isUpdated, // @DefaultValue("false") @QueryParam("notify") final boolean isNotified) { // String uuid = UUID.randomUUID().toString(); // MessageSubscribeInterface sub = factory.getMessageSubscriber(new SalsaMessageHandling() { // Map<String, String> answeredElises = new HashMap(); // // @Override // public void handleMessage(SalsaMessage message) { // toHandleMessageAndSaveData(message, answeredElises, isNotified, uuid); // } // }); // // logger.debug("Subscribing the topic: " + EliseQueueTopic.getFeedBackTopic(uuid)); // sub.subscribe(EliseQueueTopic.getFeedBackTopic(uuid)); // // logger.debug("Subscribe to feedback topic done, now push request ..."); // MessagePublishInterface pub = factory.getMessagePublisher(); // // pub.pushMessage(new SalsaMessage(SalsaMessage.MESSAGE_TYPE.elise_querySingleInstance, EliseConfiguration.getEliseID(), EliseQueueTopic.QUERY_TOPIC, EliseQueueTopic.getFeedBackTopic(uuid), instanceID)); // logger.debug("Push message done, just waiting for the message ..."); // // return uuid; // } private void toHandleMessageAndSaveData(SalsaMessage message, Map<String, String> answeredElises, boolean isNotified, String uuid) { String fromElise = message.getFromSalsa(); String fromTopic = message.getTopic(); long originTime = message.getTimeStamp(); String jsonHeader = "Count, FromElise, responseTime, updateTime \n"; //increateCountAndWriteData(jsonHeader); logger.debug("Retrieve the answer from ELISE: " + fromElise + ", topic: " + fromTopic + ", orginial timestamp: " + originTime); if (message.getFromSalsa().equals(EliseConfiguration.getEliseID())) { logger.debug("Message from the same one, no need to add: " + fromElise + ", topic: " + fromTopic); answeredElises.put(fromElise, fromTopic); // however, still need to update query status to Done (local update) QueryManager.updateQueryStatus(new EliseQueryProcessNotification(uuid, EliseConfiguration.getEliseID(), fromElise, EliseQueryProcessNotification.QueryProcessStatus.DONE)); return; } if (fromTopic != null) { if (fromTopic.equals(answeredElises.get(fromElise))) { logger.debug("Duplicate subscribing message from ELISE: " + fromElise + ", topic: " + fromTopic); return; } answeredElises.put(fromElise, fromTopic); } ObjectMapper mapper = new ObjectMapper(); JavaType javatype = mapper.getTypeFactory().constructCollectionType(Set.class, UnitInstance.class); try { UnitInstanceWrapper wrapper = (UnitInstanceWrapper) mapper.readValue(message.getPayload(), UnitInstanceWrapper.class); Set<UnitInstance> uis = wrapper.getUnitInstances(); logger.debug("Recieved " + uis.size() + " unitinstance. Saving...."); EliseRepository unitInstanceDAO = (EliseRepository) JAXRSClientFactory.create(EliseConfiguration.getRESTEndpointLocal(), EliseRepository.class, Collections.singletonList(new JacksonJsonProvider())); for (UnitInstance u : uis) { unitInstanceDAO.saveUnitInstance(u); } long now = Calendar.getInstance().getTimeInMillis(); long responseTime = now - startTime; long updateTime = now - message.getTimeStamp(); String jsonLine = padLeft(count + "", 3) + "," + padLeft(message.getFromSalsa(), 20) + "," + padLeft(responseTime + "", 7) + "," + updateTime + "\n"; //String jsonLine = count + "," + message.getFromElise() + "," + responseTime + "," + updateTime + "\n"; logger.debug("Adding done in: " + responseTime + " ms, for " + uis.size() + " instances"); increateCountAndWriteData(jsonLine); } catch (IOException ex) { java.util.logging.Logger.getLogger(EliseCommunicationService.class.getName()).log(Level.SEVERE, null, ex); } if (isNotified) { // notify that a ELISE has answered. record the answer time MessagePublishInterface pub = factory.getMessagePublisher(); // this is the response, so its "fromTopic" is the feedback topic of the query SalsaMessage msg = new SalsaMessage(SalsaMessage.MESSAGE_TYPE.discover, EliseConfiguration.getEliseID(), EliseQueueTopic.NOTIFICATION_TOPIC, "", buildNotification(fromTopic, originTime)); //pub.pushCustomData(buildNotification(fromTopic, originTime), EliseQueueTopic.NOTIFICATION_TOPIC); // long currentTimeStamp = System.currentTimeMillis(); // long derivation = currentTimeStamp - originTime; } // update query status to Done (local update) QueryManager.updateQueryStatus(new EliseQueryProcessNotification(uuid, EliseConfiguration.getEliseID(), fromElise, EliseQueryProcessNotification.QueryProcessStatus.DONE)); } private void unsubscribe(final MessageSubscribeInterface sub, long delayInSecond) { new java.util.Timer().schedule( new java.util.TimerTask() { @Override public void run() { sub.disconnect(); } }, delayInSecond * 1000 ); } private String buildNotification(String feedbackTopic, long timeStamp) { Date date = new Date(timeStamp); return feedbackTopic + "," + timeStamp + "," + date.toString(); } public String health() { return "Listening"; } private synchronized void increateCountAndWriteData(String line) { try { File file = new File(this.rtLogFile); if (!file.exists()) { file.createNewFile(); } FileWriter fileWritter = new FileWriter(file.getName(), true); BufferedWriter bufferWritter = new BufferedWriter(fileWritter); bufferWritter.write(line); bufferWritter.close(); this.count += 1; } catch (IOException e) { this.logger.error("Cannot create log file for response time !"); } } public static String padLeft(String s, int n) { return String.format("%1$" + n + "s", s); } /** * Query the status of a query. * * @param queryUUID The UUID of the query * @return A list of conductors that are working for this query and the * status of each */ // @GET // @Path("/query/{queryUUID}") // @Produces(MediaType.APPLICATION_JSON) @Override public String getQueryProcessStatus(String queryUUID) { logger.debug("Writing query management info to Json"); Map<String, EliseQueryProcessNotification.QueryProcessStatus> map = QueryManager.getQueryStatusAll(queryUUID); ObjectMapper mapper = new ObjectMapper(); try { logger.debug("writing ..."); String s = mapper.writeValueAsString(map); logger.debug("done..."); return s; } catch (IOException ex) { ex.printStackTrace(); return null; } } }