package eu.musesproject.server.connectionmanager; /* * #%L * MUSES Server * %% * Copyright (C) 2013 - 2015 Sweden Connectivity * %% * 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. * #L% */ import java.io.IOException; import java.io.PrintWriter; import java.util.Queue; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.log4j.Level; import org.apache.log4j.Logger; import eu.musesproject.server.contextdatareceiver.ConnectionCallbacksImpl; /** * Class ComMainServlet * * @author Yasir Ali * @version Jan 27, 2014 */ public class ComMainServlet extends HttpServlet { // a servlet has to be thread safe so no instance variables are allowed (this is not optional, it is a rule) // read this for more info http://www.javaworld.com/article/2072798/java-web-development/write-thread-safe-servlets.html // there is no issue with those that are final int, final long or final string because they are read-only // the rest needs to fulfill one of these options: become local variables or be thread safe or be stateless private static final long serialVersionUID = 1L; private static Logger logger = Logger.getLogger(ComMainServlet.class.getName()); private Helper helper; // became stateless //private SessionHandler sessionHandler; // tomcat handles sessions for us private ConnectionManager connectionManager; // removing this requires going from pull to push //private String dataAttachedInCurrentReuqest; // became local variable //private String dataToSendBackInResponse=""; // became local variable private static final String CONNECTION_TYPE = "connection-type"; private static final String DATA = "data"; private static final int INTERVAL_TO_WAIT = 5; private static final long SLEEP_INTERVAL = 1000; private static final String MUSES_TAG = "MUSES_TAG"; //private static String connectionType = "connect"; // became local variable /** * * @param sessionHandler * @param helper * @param communicationManager */ public ComMainServlet(SessionHandler sessionHandler, Helper helper, ConnectionManager communicationManager){ //this.sessionHandler=sessionHandler; this.helper=helper; this.connectionManager=communicationManager; } public ComMainServlet() { } @Override public void init(ServletConfig config) throws ServletException { super.init(config); logger.log(Level.INFO, MUSES_TAG + " init"); helper = new Helper(); connectionManager = ConnectionManager.getInstance(); //sessionHandler = SessionHandler.getInstance(getServletContext()); ConnectionCallbacksImpl.getInstance(); // register callback // the connection manager should be moved to ApplicationScope like this //ConnectionManager connectionManager = ConnectionManager.getInstance(); //config.getServletContext().setAttribute("ConnectionManager.instance", connectionManager); // the retrieval in doPost() would look like this. bear in mind that objects in application // scope like ConnectionManager will be accessed by many threads so they must be thread-safe //connectionManager = (ConnectionManager) this.getServletContext().getAttribute("ConnectionManager.instance"); } /** * Handle POST http/https requests * @param HttpServletRequest request * @param com.swedenconnectivity.comserverHttpServletResponse response * @throws ServletException, IOException */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // by calling getSession() we send a signal to tomcat that we want a session created if there // isn't any already. bear in mind that tomcat will put the JSESSIONID as a cookie in the // response for us so we don't have to do it ourselves. btw every jsp file already contains this // method call. HttpSession session = request.getSession(); String dataAttachedInCurrentReuqest; String dataToSendBackInResponse=""; String connectionType = "connect"; // create cookie if not in the request //Cookie cookie = helper.extractCookie(request); //String currentJSessionID = cookie.getValue(); String currentJSessionID = session.getId(); // Retrieve data in the request if (request.getMethod().equalsIgnoreCase("POST")) { // Retrieve connection-type from request header connectionType = request.getHeader(CONNECTION_TYPE); dataAttachedInCurrentReuqest = Helper.getRequestData(request); }else { // Retrieve connection-type from request parameter connectionType = DATA; dataAttachedInCurrentReuqest = request.getParameter(DATA); } // if "connect" request if (connectionType!=null && connectionType.equalsIgnoreCase(RequestType.CONNECT)) { logger.log(Level.INFO, MUSES_TAG + " Request type:"+connectionType+" with *ID*: "+currentJSessionID+ " with **dataInRequest**: "+dataAttachedInCurrentReuqest); } // if "send-data" request if (connectionType!=null && connectionType.equalsIgnoreCase(RequestType.DATA)) { // Callback the FL to receive data from the client and get the response data back into string dataToSendBackInResponse=""; if (dataAttachedInCurrentReuqest != null){ dataToSendBackInResponse = ConnectionManager.toReceive(currentJSessionID, dataAttachedInCurrentReuqest); // FIXME needs to be tested properly if (dataToSendBackInResponse == null) { dataToSendBackInResponse = ""; } } if (dataToSendBackInResponse.equals("")) { dataToSendBackInResponse = waitForDataIfAvailable(INTERVAL_TO_WAIT, currentJSessionID); } response.setContentType("application/json"); response.setHeader("Cache-Control", "nocache"); response.setCharacterEncoding("utf-8"); PrintWriter writer = response.getWriter(); writer.write(dataToSendBackInResponse); //response.addHeader(DATA,dataToSendBackInResponse); // Now data is added in the body instead logger.log(Level.INFO, MUSES_TAG + " Data avaialble Request type:"+connectionType+" with *ID*: "+currentJSessionID+ " with **dataInResponse**: "+dataToSendBackInResponse); } // if "poll" request if (connectionType!= null && connectionType.equalsIgnoreCase(RequestType.POLL)) { for (DataHandler dataHandler : connectionManager.getDataHandlerQueue()){ // FIXME concurrent thread if (dataHandler.getSessionId().equalsIgnoreCase(currentJSessionID)){ dataToSendBackInResponse = dataHandler.getData(); response.setHeader("Content-Type", "text/plain"); PrintWriter writer = response.getWriter(); writer.write(dataToSendBackInResponse); //response.addHeader(DATA,dataToSendBackInResponse); // Now data is added in the body instead connectionManager.removeDataHandler(dataHandler); Queue<DataHandler> dQueue = connectionManager.getDataHandlerQueue(); if (dQueue.size() > 1) { response.addHeader("more-packets", "YES"); }else { response.addHeader("more-packets", "NO"); } logger.log(Level.INFO, "Data avaialble Request type:"+connectionType+" with *ID*: "+currentJSessionID+ " with **dataInResponse**: "+dataToSendBackInResponse); break; // FIXME temporary as multiple same session ids are in the list right now } } } // if "ack" request if (connectionType!=null && connectionType.equalsIgnoreCase(RequestType.ACK)) { logger.log(Level.INFO, "Request type:"+connectionType+" with *ID*: "+currentJSessionID); // Clean up the data handler object from the list connectionManager.removeDataHandler(connectionManager.getDataHandlerObject(currentJSessionID)); ConnectionManager.toSessionCb(currentJSessionID, Statuses.DATA_SENT_SUCCESFULLY); } // if disconnect request // invalidate session from Servlet // remove it from the session id list // Callback the Functional layer about the disconnect if (connectionType!= null && connectionType.equalsIgnoreCase(RequestType.DISCONNECT)) { logger.log(Level.INFO, "Request type:"+connectionType+" with *ID*: "+currentJSessionID); Helper.disconnect(request); //sessionHandler.removeCookieToList(cookie); ConnectionManager.toSessionCb(currentJSessionID, Statuses.DISCONNECTED); } // Add session id to the List // if (currentJSessionID != null && !connectionType.equalsIgnoreCase(RequestType.DISCONNECT) ) { // //sessionHandler.addCookieToList(cookie); // } // the cookie should only be set by tomcat once when the session has been created. // after that the client will include the cookie inside every request until the cookie // expires. btw this is the correct mime type for json (Source: RFC 4627) // should already be in response because we called the getSession above //response.addCookie(cookie); response.setContentType("application/json"); } // we don't have any instance variable so we can not have a getter either. this method is called // by some unit test assert methods. those methods should instead extract the payload from httpsresponse //public String getResponseData(){ // return dataToSendBackInResponse; //} public String waitForDataIfAvailable(int timeout, String currentJSessionID){ int i=1; while(i<=timeout){ Queue<DataHandler> dQueue = connectionManager.getDataHandlerQueue(); if (dQueue.size()>=1) { for (DataHandler dataHandler : connectionManager.getDataHandlerQueue()){ // FIXME concurrent thread if (dataHandler.getSessionId().equalsIgnoreCase(currentJSessionID)){ connectionManager.removeDataHandler(dataHandler); return dataHandler.getData(); } } } sleep(SLEEP_INTERVAL); i++; } return ""; } private void sleep(long millis){ try { Thread.sleep(millis); } catch (InterruptedException e) { logger.log(Level.INFO, e); } } /** * Handle GET http/https requests // Muses will not use this method * @param HttpServletRequest request * @param HttpServletResponse response * @throws ServletException, IOException */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } }