package eu.musesproject.server.contextdatareceiver; /* * #%L * MUSES Server * %% * Copyright (C) 2013 - 2014 S2 Grupo * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.json.JSONException; import org.json.JSONObject; import eu.musesproject.client.model.JSONIdentifiers; import eu.musesproject.client.model.RequestType; import eu.musesproject.contextmodel.ContextEvent; import eu.musesproject.server.authentication.AuthenticationManager; import eu.musesproject.server.connectionmanager.ConnectionManager; import eu.musesproject.server.connectionmanager.IConnectionCallbacks; import eu.musesproject.server.connectionmanager.Statuses; import eu.musesproject.server.continuousrealtimeeventprocessor.EventProcessor; import eu.musesproject.server.db.handler.DBManager; import eu.musesproject.server.entity.ConnectionConfig; import eu.musesproject.server.entity.DefaultPolicies; import eu.musesproject.server.entity.MusesConfig; import eu.musesproject.server.entity.SensorConfiguration; import eu.musesproject.server.entity.Users; import eu.musesproject.server.entity.Zone; import eu.musesproject.server.eventprocessor.correlator.engine.DroolsEngineService; import eu.musesproject.server.eventprocessor.correlator.model.CepFact; import eu.musesproject.server.eventprocessor.correlator.model.owl.ConfigSyncEvent; import eu.musesproject.server.eventprocessor.impl.EventProcessorImpl; import eu.musesproject.server.eventprocessor.impl.MusesCorrelationEngineImpl; import eu.musesproject.server.eventprocessor.util.EventTypes; import eu.musesproject.server.scheduler.ModuleType; public class ConnectionCallbacksImpl implements IConnectionCallbacks { private static final String MUSES_TAG = "MUSES_TAG"; private Logger logger = Logger.getLogger(ConnectionCallbacksImpl.class.getName()); private ConnectionManager connManager; private static ConnectionCallbacksImpl ConnectionCallbacksImplSingleton = null; //boolean isDataAvailable = false; // not used //public static volatile String lastSessionId = null; // several threads will assign to it. one shared variable for several clients to store their sessionid! //private static volatile String data = ""; // became local //public static volatile String receiveData; // became local private static DBManager dbManager = new DBManager(ModuleType.EP); private static ExecutorService executor = Executors.newFixedThreadPool(100); private ConnectionCallbacksImpl(){ connManager = ConnectionManager.getInstance(); connManager.registerReceiveCb(this); startConnection(); } // we don't want to register a new callback everytime tomcat creates a new instance of the ComMainServlet (or everytime a person refreshes the /server ) public static ConnectionCallbacksImpl getInstance(){ if (ConnectionCallbacksImplSingleton == null) { synchronized(ConnectionCallbacksImpl.class){ if (ConnectionCallbacksImplSingleton == null) { ConnectionCallbacksImplSingleton = new ConnectionCallbacksImpl(); } } } return ConnectionCallbacksImplSingleton; } private static class ProcessThread implements Runnable { List<ContextEvent> list = null; String sessionId = null; String username = null; String deviceId = null; int requestId; public ProcessThread(List<ContextEvent> contextList, String id, String user, String device, int request) { list = contextList; sessionId = id; username = user; deviceId = device; requestId = request; } public void run() { UserContextEventDataReceiver.getInstance().processContextEventList( list, sessionId, username, deviceId, requestId); } } private void startConnection() { logger.info("Start Server Connection"); } @Override public String receiveCb(String sessionId, String rData) { JSONObject root; String requestType = null; String username = null; String deviceId = null; String os = null; String osVersion = null; int requestId = 0; //ConnectionCallbacksImpl.lastSessionId = sessionId; //ConnectionCallbacksImpl.receiveData = rData; // became local variable from being class variable for thread safety String data = ""; String receiveData = rData; logger.log(Level.INFO, MUSES_TAG + " Info SS, received callback from CM with data:"+rData); try { // First, get the request type, in order to differentiate between login // and data exchange //root = new JSONObject(ConnectionCallbacksImpl.receiveData); root = new JSONObject(receiveData); requestType = root.getString(JSONIdentifiers.REQUEST_TYPE_IDENTIFIER); if (requestType.equals(RequestType.LOGIN)) { logger.log(Level.INFO, "Login request"); // Delegate authentication to AuthenticationManager JSONObject authResponse = AuthenticationManager.getInstance().authenticate(root, sessionId); logger.log(Level.INFO, MUSES_TAG + " Info SS, Login request=> authentication response is:"+authResponse.toString()); if (authResponse != null) { try { username = root .getString(JSONIdentifiers.AUTH_USERNAME); deviceId = root .getString(JSONIdentifiers.AUTH_DEVICE_ID); } catch (JSONException e) { // TODO Auto-generated catch block //e.printStackTrace(); logger.log(Level.INFO, "JSON identifier not found:"+e.getMessage()); } UserContextEventDataReceiver.storeEvent(EventTypes.LOG_IN, username, "musesawaew", deviceId, "Geneva", authResponse.toString()); return authResponse.toString(); } }else if (requestType.equals(RequestType.LOGOUT)) { logger.log(Level.INFO, "Logout request"); JSONObject authResponse = AuthenticationManager.getInstance().logout(root, sessionId); logger.log(Level.INFO, MUSES_TAG + "Info SS, Logout request"); try { username = root .getString(JSONIdentifiers.AUTH_USERNAME); deviceId = root .getString(JSONIdentifiers.AUTH_DEVICE_ID); } catch (JSONException e) { // TODO Auto-generated catch block //e.printStackTrace(); logger.log(Level.INFO, "JSON identifier not found:"+e.getMessage()); } UserContextEventDataReceiver.storeEvent(EventTypes.LOG_OUT, username, "musesawaew", deviceId, "Geneva", authResponse.toString()); return authResponse.toString(); }else if (requestType.equals(RequestType.CONFIG_SYNC)) { try{ os = root.getString(JSONIdentifiers.OPERATING_SYSTEM); //osVersion = root.getString(JSONIdentifiers.OPERATING_SYSTEM_VERSION); username = root .getString(JSONIdentifiers.AUTH_USERNAME); deviceId = root .getString(JSONIdentifiers.AUTH_DEVICE_ID); } catch (JSONException je) { logger.log(Level.ERROR, MUSES_TAG+ je.getMessage() + je.getCause()); } logger.log(Level.INFO, MUSES_TAG + " Sending config sync for operating system:" + os); if ((os == null) || (os.contains(eu.musesproject.server.eventprocessor.util.Constants.OS_ANDROID))) {//Android by default, if no operating system specified if (AuthenticationManager.getInstance().isAuthenticated( sessionId)) { logger.log(Level.INFO, MUSES_TAG + "Config sync requested"); // MUSES Configuration MusesConfig config = dbManager.getMusesConfig(); logger.log( Level.INFO, MUSES_TAG + config.getConfigName() + " silent mode:" + config.getSilentMode()); // Connection Configuration ConnectionConfig connectionConfig = dbManager .getConnectionConfig(); logger.log(Level.INFO, MUSES_TAG + " Connection config id:" + connectionConfig.getConfigId()); // Sensor Configuration List<SensorConfiguration> sensorConfig = dbManager .getSensorConfiguration(); // Retrieve zone info List<Zone> zoneConfig = dbManager.getZones(); logger.log(Level.INFO, MUSES_TAG + " Zones information for " + zoneConfig.size() + " zones."); //Retrieve default policies String defaultPoliciesXML = null; username = root .getString(JSONIdentifiers.AUTH_USERNAME); Users user = dbManager.getUserByUsername(username); if (user != null) { String language = user.getLanguage(); List<DefaultPolicies> defaultPolicies = dbManager .getDefaultPolicies(language); if ((defaultPolicies != null) && (defaultPolicies.size() > 0)) { defaultPoliciesXML = JSONManager .createDefaultPoliciesJSON(defaultPolicies); logger.log(Level.INFO, defaultPoliciesXML.toString()); //connManager.sendData(sessionId, defaultPoliciesJSON.toString()); } } JSONObject response = JSONManager .createConfigUpdateJSON( RequestType.CONFIG_UPDATE, config, sensorConfig, connectionConfig, zoneConfig, defaultPoliciesXML); logger.log(Level.INFO, response.toString()); logger.log(Level.INFO, MUSES_TAG + " Response to send:" + response.toString()); logger.log(Level.INFO, MUSES_TAG + " sessionID: " + sessionId); connManager.sendData(sessionId, response.toString()); } else {// Current sessionId has not been authenticated JSONObject response = JSONManager .createJSON(JSONIdentifiers.AUTH_RESPONSE, "FAIL", "Data cannot be processed: Failed authentication"); logger.log(Level.INFO, response.toString()); // According to issue #11, we should not send data when not logged in, so this line is commented:connManager.sendData(sessionId, response.toString()); } }else if ((os != null) && (os.contains(eu.musesproject.server.eventprocessor.util.Constants.OS_WINDOWS))) { //TODO Windows config-sync to be done logger.log(Level.INFO, "Windows config-sync..."); String response = ""; logger.log(Level.INFO, MUSES_TAG + " Response to send:" + response); logger.log(Level.INFO, MUSES_TAG + " sessionID: " + sessionId); connManager.sendData(sessionId, response); //Insert config sync //eu.musesproject.server.eventprocessor.correlator.model.owl.Event formattedEvent = null; //CepFact formattedEvent = null; ConfigSyncEvent csEvent= new ConfigSyncEvent(); csEvent.setOs(os); csEvent.setSessionId(sessionId); //formattedEvent = (eu.musesproject.server.eventprocessor.correlator.model.owl.Event)csEvent; //formattedEvent = csEvent; EventProcessor processor = null; MusesCorrelationEngineImpl engine = null; DroolsEngineService des = EventProcessorImpl.getMusesEngineService(); if (des==null){ processor = new EventProcessorImpl(); engine = (MusesCorrelationEngineImpl)processor.startTemporalCorrelation("drl"); des = EventProcessorImpl.getMusesEngineService(); }else{ logger.info("DroolsEngine Service already available"); } if (csEvent != null){ csEvent.setSessionId(sessionId); csEvent.setUsername(username); csEvent.setDeviceId(deviceId); //if (requestId != 0){ csEvent.setHashId(requestId); //} logger.info("Inserting event into the WM:"+csEvent); try{ des.insertFact(csEvent); }catch(NullPointerException e){ logger.info("formatter Event not inserted due to NullPointerException"); } } } }else { //Data exchange: We should check if sessionId is correctly authenticated if (AuthenticationManager.getInstance().isAuthenticated(sessionId)) { //List<ContextEvent> list = JSONManager // .processJSONMessage(ConnectionCallbacksImpl.receiveData, null, sessionId); List<ContextEvent> list = JSONManager .processJSONMessage(receiveData, null, sessionId); logger.log(Level.INFO, "Starting ProcessThread..."); try { username = root .getString(JSONIdentifiers.AUTH_USERNAME); deviceId = root .getString(JSONIdentifiers.AUTH_DEVICE_ID); if (requestType.equals(RequestType.ONLINE_DECISION)){ requestId = root.getInt(JSONIdentifiers.REQUEST_IDENTIFIER); logger.log(Level.INFO, "requestId"+requestId); } } catch (JSONException e) { // TODO Auto-generated catch block //e.printStackTrace(); logger.log(Level.INFO, "JSON identifier not found:"+e.getMessage()); logger.log(Level.INFO, "Original JSON message:" + receiveData); } // Thread t = new Thread(new ProcessThread(list, sessionId, username, deviceId, requestId)); // t.start(); // the executor is a pool of threads/workers. runnables are work units that we // assign to the workers to execute. this way the number of threads are fixed and under // control. this is important because cpu time is a limited resource. if the running machine // has more capacity then increase the pool size of executor above in class variable section. Runnable runnable = new ProcessThread(list, sessionId, username, deviceId, requestId); executor.execute(runnable); logger.log(Level.INFO, "Resuming receiveCb after calling ProcessThread..."); logger.info("*************Receive Callback called: " //+ ConnectionCallbacksImpl.receiveData + receiveData + "from client ID " + sessionId); if (data != null) { //connManager.sendData(sessionId, // ConnectionCallbacksImpl.data); connManager.sendData(sessionId, data); } } else {//Current sessionId has not been authenticated // According to issue #11, we should not send data when not logged in, so this section is commented: logger.log(Level.INFO, "Authentication has failed, any data coming after this authentication failed, should not be replied. According to issue #11, we don't reply with any auth-message "); } } } catch (JSONException je) { logger.log(Level.ERROR, MUSES_TAG+ je.getMessage() + je.getCause()); je.printStackTrace(); } //return ConnectionCallbacksImpl.data; return data; } @Override public void sessionCb(String sessionId, int status) { logger.info("*************Session Callback called Id: " + sessionId + " Status: " + ((status == Statuses.DISCONNECTED) ? "Disconnected" : "Data sent")); } public static ExecutorService getExecutor() { return executor; } }