Java tutorial
/******************************************************************************* * Copyright 2014 United States Government as represented by the * Administrator of the National Aeronautics and Space Administration. * All Rights Reserved. * * 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 gov.nasa.arc.spife.europa.clientside; import gov.nasa.ensemble.common.CommonPlugin; import gov.nasa.ensemble.common.EnsembleProperties; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.Socket; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.emf.ecore.resource.URIConverter; import org.osgi.framework.Bundle; public class EuropaServerManager { private static final String CONFIG_ELEMENT = "config-element"; private static final String AD_IDENTIFIER = "ADidentifier"; private static final String UPLOAD_DIR = "Uploads"; private static final Logger LOGGER = Logger.getLogger(EuropaServerManager.class); private static final Map<Integer, Level> loggerLevels; private static final String TYPE = "type"; private static final String FILES = "files"; private static final String CONTENTS = "contents"; private static final String FILE_TYPE = "filetype"; private static final String SERVER_MANAGER_CONFIG = "europa.servermanager.config"; private static EuropaServerManager s_manager = null; private final EuropaServerManagerConfig m_config; private final Map<String, String> m_replacements; private final Map<String, EuropaServerProxy> m_sessions; public EuropaServerManager(EuropaServerManagerConfig config) { this(config, new TreeMap<String, String>()); } public EuropaServerManager(EuropaServerManagerConfig config, Map<String, String> replacements) { m_config = config; m_replacements = replacements; m_sessions = new HashMap<String, EuropaServerProxy>(); } public static EuropaServerManager getInstance() { return s_manager; } public static synchronized void startServerManager(String configFile, boolean wait) { startServerManager(configFile, null, wait); } public static synchronized void startServerManager(String configFile, Map<String, String> configReplacements, boolean wait) { startServerManager(configFile, configReplacements, wait, EuropaClientSidePlugin.getDefault().getBundle()); } public static synchronized void startServerManager(String configFile, Map<String, String> configReplacements, boolean wait, Bundle bundle) { if (s_manager != null) stopServerManager(); assert (s_manager == null); EuropaServerManagerConfig cfg = null; String eventualName = null; try { eventualName = getConfigFile(configFile, bundle); } catch (IOException ioe) { LOGGER.error("Failed to produce configuration file", ioe); } cfg = ((eventualName == null || eventualName.length() < 1) ? new EuropaServerManagerConfig() : EuropaServerManagerConfig.fromEsmConfig(eventualName, configReplacements)); setupNddlIncludes(); s_manager = new EuropaServerManager(cfg, configReplacements); } public static synchronized void stopServerManager() { if (s_manager == null) return; try { s_manager.KillAllServers(); } catch (Exception e) { LOGGER.error("Failed to stop all existing sessions.", e); return; } s_manager = null; } public static String copyFileToStateArea(String fileName, String outputFileName, Bundle fromBundle) throws IOException { File outputFile = localStatePath().append(outputFileName).toFile(); outputFile.createNewFile(); OutputStream stream = new FileOutputStream(outputFile); InputStream input = FileLocator.openStream(fromBundle, new Path(fileName), false); IOUtils.copy(input, stream); input.close(); stream.close(); return outputFile.getAbsolutePath(); } static { loggerLevels = new HashMap<Integer, Level>(); loggerLevels.put(DynamicEuropaLogger.ERROR, Level.ERROR); loggerLevels.put(DynamicEuropaLogger.TRACE, Level.DEBUG); loggerLevels.put(DynamicEuropaLogger.TRACE_ALL, Level.ALL); loggerLevels.put(DynamicEuropaLogger.RETURN, Level.ALL); loggerLevels.put(DynamicEuropaLogger.TIMER, Level.TRACE); } public synchronized Map<String, Object> DeleteModel(Map<String, Object> modelInfo) { if (!modelInfo.containsKey(AD_IDENTIFIER)) return null; String version = (String) modelInfo.get(AD_IDENTIFIER); String type = (String) modelInfo.get(TYPE); writeLog("[method=DeleteModel] [type=" + type + "] [" + AD_IDENTIFIER + "=" + version + "]"); String safeVersion = version.replaceAll("\\W", "_"); String safeType = type.replaceAll("\\W", "_"); if (!typeExists(version)) { writeLog("[method=DeleteModel] [error=" + 1008 + "] [message=Server version " + version + " doesn't exist. Not deleting." + "]"); throw new RuntimeException( new RuntimeException("Server version " + version + " doesn't exist. Not deleting.")); } LinkedList<File> notDeleted = new LinkedList<File>(); File dir = localStatePath().append(UPLOAD_DIR).append(safeType).append(safeVersion).toFile(); if (dir.exists()) { for (File file : dir.listFiles()) { if (!file.delete()) notDeleted.add(file); } if (!dir.delete()) notDeleted.add(dir); } m_config.removeConfig(version); try { m_config.save(); } catch (IOException ioe) { writeLog("[method=DeleteModel] [error=666] [message=Unable to save modified configuration to disk: " + ioe + "]"); } return new HashMap<String, Object>(); } //TODO: Implement this public synchronized String GetLog(final String type, final String session) { writeLog("[method=GetLog] [session=" + session + "] [type=" + type + "]"); //return the contents of the log file for the given session as a string. return ""; } //not called anywhere, as far as I can tell public synchronized String[] GetLogSessions(final String type) { writeLog("[method=GetLogSessions] [type=" + type + "]"); //return the values of [session=(value)] in every line with TRACE_DETAIL.*StartSession in it return new String[0]; } public synchronized String[] GetModelVersions() { return m_config.getConfigs().keySet().toArray(new String[0]); } public synchronized String[] KillAllServers() { writeLog("[method=KillAllServers]"); String[] retval = m_sessions.keySet().toArray(new String[0]); for (String session : retval) { StopSession(session); } return retval; } //not called anywhere, as far as I can tell public synchronized String RollLogs() { return RollLogs(""); } public synchronized String RollLogs(String type) { return ""; } //returns a "session" hash table consisting of the session string, the port number, and the host name //or one of several faults public synchronized EuropaServerProxy StartSession() { return StartSession(generateSessionName()); } public synchronized EuropaServerProxy StartSession(final String session) { return StartSession(session, m_config.getDefaultType()); } public synchronized EuropaServerProxy StartSession(final String session, final String type) { long timer = System.currentTimeMillis(); writeLog("[method=StartSession] [session=" + session + "] [type=" + type + "]", DynamicEuropaLogger.TRACE); if (!typeExists(type)) { writeLog("[method=StartSession] [error=1001] [session=" + session + "] [message=Server type '" + type + "' doesn't exist.]", DynamicEuropaLogger.ERROR); throw new RuntimeException("Server type " + type + " doesn't exist."); } if (m_sessions.containsKey(session)) { writeLog("[method=StartSession] [session=" + session + "] [message=not starting, session already exists]", DynamicEuropaLogger.ERROR); throw new RuntimeException("Tried to start session more than once :" + session); } // For now each session has its own europa server EuropaServerProxy newServer = makeNewEuropaServer(session, type); writeLog("[method=StartSession] [session=" + session + "] [message=now forking child]", DynamicEuropaLogger.TRACE); newServer.startServer(session); try { newServer.startSession(session, type); } catch (Exception e) { newServer.stopServer(); String msg = "Failed to Start session on the server after connecting successfully"; throw new RuntimeException(msg, e); } m_sessions.put(session, newServer); writeLog("[method=StartSession] [session=" + session + "] [type=" + type + "] [time=" + (System.currentTimeMillis() - timer) + "]", DynamicEuropaLogger.TIMER); return newServer; } protected EuropaServerConfig makeNewEuropaServerConfig(final String session, final String type) { EuropaServerConfig config = m_config.getConfig(type); int port = (useXmlRpc() ? findPort(config.GetPort()) : 0); if (port < 0) { writeLog("[method=StartSession] [error=1000] [session=" + session + "] [message=could not find open port]", DynamicEuropaLogger.ERROR); throw new RuntimeException("Could not find open port for child"); } String logFile = generateLogFileName(session, config.GetLogFile()); EuropaServerConfig actualConfig = new EuropaServerConfig(port, config.GetDebug(), config.GetVerbosity(), config.GetInitialStateFilename(), EuropaServerManagerConfig.nddlIncludePath(config.GetInitialStateFilename()), config.GetHostLocal(), config.GetHost(), config.GetVersion(), config.GetServerTimeout(), logFile, config.GetPlannerConfigFilename(), config.GetPlannerConfigElement()); actualConfig.SetValue("engine_type", getEngineType()); return actualConfig; } /* * Creates a new server to handle a EuropaSession, currently there is a DynamicEuropa server for each client session */ protected EuropaServerProxy makeNewEuropaServer(final String session, final String type) { EuropaServerConfig serverConfig = makeNewEuropaServerConfig(session, type); EuropaServerProxy retval = null; if (useXmlRpc()) { retval = new EuropaServerProxyXmlRpc(serverConfig, useRemoteServer()); LOGGER.info("Connected to DynamicEuropa through xml-rpc on " + serverConfig.GetHost() + ":" + serverConfig.GetPort() + ". " + session); } else { retval = new EuropaServerProxyJNI(serverConfig); LOGGER.info("Connected to DynamicEuropa through JNI. " + session); } return retval; } public synchronized String StopSession(final String session) { writeLog("[method=StopSession] [session=" + session + "]", DynamicEuropaLogger.TRACE_ALL); EuropaServerProxy sessionServer = m_sessions.get(session); if (sessionServer != null) { m_sessions.remove(session); sessionServer.stopSession(); sessionServer.stopServer(); } return session; } public boolean getServerExists(String type, String version) { return typeExists(version) || typeExists(type); } public synchronized Map<String, Object> UploadModel(Map<String, Object> modelInfo) { if (!modelInfo.containsKey(AD_IDENTIFIER)) return null; //the Perl does this, String type = (String) modelInfo.get(TYPE); String version = (String) modelInfo.get(AD_IDENTIFIER); String configElement = (modelInfo.containsKey(CONFIG_ELEMENT) ? (String) modelInfo.get(CONFIG_ELEMENT) : "MER2PassiveSolver"); //man, this default makes me feel dirty writeLog("[method=UploadModel] [type=" + type + "] [" + AD_IDENTIFIER + "=" + version + "]"); if (getServerExists(type, version)) { writeLog("[method=UploadModel] [error=" + 1008 + "] [message=Server version " + version + " already exists, not uploading" + "]"); throw new RuntimeException("Server version " + version + " already exists, not uploading"); } String safeVersion = version.replaceAll("\\W", "_"); String safeType = type.replaceAll("\\W", "_"); //System.out.println("safe version: " + safeVersion + " safe type: " + safeType); //create the upload directory, type directory, and the specific directory into which to write the model files String fullPath = UPLOAD_DIR + File.separator + safeType + File.separator + safeVersion; File versionDir = null; try { createDirInStateArea(UPLOAD_DIR); createDirInStateArea(UPLOAD_DIR + File.separator + safeType); versionDir = createDirInStateArea(fullPath, true); //force creation, since this should fail if the version already exists in the filesystem } catch (Exception e) { throw new RuntimeException("Failed to create upload directory: " + e); } LinkedList<File> createdFiles = new LinkedList<File>(); File initialState = null; File solverConfig = null; //wrapped so we don't leave too many droppings try { //create the model files Map<String, Object> files = (Map<String, Object>) modelInfo.get(FILES); for (Map.Entry<String, Object> entry : files.entrySet()) { String fileName = entry.getKey(); Map<String, String> contents = (Map<String, String>) entry.getValue(); File file = new File(versionDir.getAbsolutePath() + File.separator + fileName); try { //System.out.println("Creating file " + file.getAbsolutePath()); file.createNewFile(); FileWriter writer = new FileWriter(file); writer.write(contents.get(CONTENTS)); writer.flush(); writer.close(); } catch (Exception ce) { writeLog("[method=UploadModel] [error=1010] [message=Unable to create file'" + file + "': " + ce + "]"); throw new RuntimeException("Unable to create file'" + file + "'"); } if (file.exists()) createdFiles.add(file); if (fileName.indexOf("initial-state.nddl") != -1) initialState = file; if (contents.get(FILE_TYPE).equals("solverconfig")) solverConfig = file; } if (initialState == null) throw new RuntimeException("No initial state file uploaded."); if (solverConfig == null) throw new RuntimeException("No solver configuration file uploaded."); } //clean up any droppings on an error, or at least make a token effort catch (Exception xre) { for (File file : createdFiles) file.delete(); versionDir.delete(); throw new RuntimeException(xre); } String nddlIncludePath = EuropaServerManagerConfig.nddlIncludePath(initialState.getAbsolutePath()); m_config.addConfig(version, new EuropaServerConfig(EuropaServerManagerConfig.DEFAULT_SERVER_PORT, DynamicEuropaLogger.ERROR | DynamicEuropaLogger.TRACE | DynamicEuropaLogger.TRACE_ALL | DynamicEuropaLogger.RETURN | DynamicEuropaLogger.TIMER, 0, initialState.getAbsolutePath(), nddlIncludePath, EuropaServerManagerConfig.LOCALHOST.getCanonicalHostName(), EuropaServerManagerConfig.LOCALHOST.getCanonicalHostName(), version, 0.5 * 3600, //from the autoxmlconf.pl script EuropaServerManagerConfig.DEFAULT_SERVER_LOG_FILENAME, solverConfig.getAbsolutePath(), configElement)); try { m_config.save(); } catch (IOException ioe) { writeLog("[method=UploadModel] [error=666] [message=Unable to save modified configuration to disk: " + ioe + "]"); } return new HashMap<String, Object>(); } /* END XML-RPC methods */ private String generateSessionName() { return String.format("S%16d", System.currentTimeMillis()); } private void writeLog(String value, int type) { if ((m_config.getLogLevel() & type) != 0) { LOGGER.log(loggerLevels.get(type), value); //System.out.println(value); } } private void writeLog(String value) { writeLog(value, DynamicEuropaLogger.TRACE_ALL); } @SuppressWarnings("unused") private int findPort() { return findPort(m_config.getPort()); } private int findPort(int startAt) { Socket temp = null; for (int testPort = startAt; testPort < startAt + 1000; ++testPort) { try { writeLog("[findPort] Testing port " + testPort); temp = new Socket(EuropaServerManagerConfig.LOCALHOST, testPort); if (temp.isBound()) writeLog("[findPort] Successfully bound port. Skipping " + testPort); else writeLog("[findPort] Port " + testPort + " is in use. Moving on."); } catch (IOException ioe) { if (ioe instanceof ConnectException) return testPort; } //you cannot get ye port catch (SecurityException se) { writeLog("[findPort] Security exception testing a port. Returning -1.", DynamicEuropaLogger.ERROR); return -1; } finally { try { if (temp != null) temp.close(); } catch (Exception e) { writeLog("[findPort] Failed to close a test-socket.", DynamicEuropaLogger.ERROR); } } } writeLog("[findPort] Exhausted 1000 possible ports. Returning -1.", DynamicEuropaLogger.ERROR); return -1; } private boolean typeExists(final String type) { return m_config.hasConfig(type); } private static String getConfigFile(String configFile, Bundle bundle) throws IOException { String fileName = getConfigFileFromConfig(configFile); if (!isFileWritable(fileName)) { fileName = copyFileToStateArea(fileName, EuropaServerManagerConfig.DEFAULT_CFG_FILENAME, bundle); } org.eclipse.emf.common.util.URI uri = org.eclipse.emf.common.util.URI.createURI(fileName); if (URIConverter.INSTANCE.exists(uri, null)) return fileName; return "file:" + fileName; } private static String getConfigFileFromConfig(String configFile) throws IOException { if (configFile == null || configFile.length() < 1) { //prefer local state path file to the one in properties if (localStatePath().append(EuropaServerManagerConfig.DEFAULT_CFG_FILENAME).toFile().exists()) { return localStatePath().append(EuropaServerManagerConfig.DEFAULT_CFG_FILENAME).toFile() .getAbsolutePath(); } else if (EnsembleProperties.getProperty(SERVER_MANAGER_CONFIG) != null) { return EnsembleProperties.getProperty(SERVER_MANAGER_CONFIG); } //fake one up in the plugin state area else { return createDefaultConfigFile(); } } return configFile; } private static String createDefaultConfigFile() throws IOException { (new EuropaServerManagerConfig()).save(); return localStatePath().append(EuropaServerManagerConfig.DEFAULT_CFG_FILENAME).toFile().getAbsolutePath(); } private static boolean isFileWritable(String file) { org.eclipse.emf.common.util.URI uri = org.eclipse.emf.common.util.URI.createURI(file); if (URIConverter.INSTANCE.exists(uri, null)) { Map<String, Set<String>> options = new TreeMap<String, Set<String>>(); Set<String> attributes = new TreeSet<String>(); attributes.add(URIConverter.ATTRIBUTE_READ_ONLY); options.put(URIConverter.OPTION_REQUESTED_ATTRIBUTES, attributes); Map<String, ?> readOnly = URIConverter.INSTANCE.getAttributes(uri, options); return !((Boolean) readOnly.get(URIConverter.ATTRIBUTE_READ_ONLY)); } else { File foo = new File(file); return foo.canWrite(); } } @SuppressWarnings("unused") private String replace(String string) { if (CommonPlugin.isJunitRunning()) { //System.out.print(string + "=>"); for (Map.Entry<String, String> entry : m_replacements.entrySet()) { string = string.replaceAll(entry.getKey(), entry.getValue()); } //System.out.println(string); } return string; } private String generateLogFileName(String session, String file) { File parentDir = localStatePath().toFile(); String retval = parentDir.getAbsolutePath(); return retval + File.separator + session + file; } public static IPath localStatePath() { return Platform.getStateLocation(EuropaClientSidePlugin.getDefault().getBundle()); } public static File createDirInStateArea(String name) throws IOException { return createDirInStateArea(name, false); } public static File createDirInStateArea(IPath path) throws IOException { return createDirInStateArea(path, false); } public static File createDirInStateArea(String name, boolean forceCreate) throws IOException { IPath realPath = localStatePath().append(name); return doCreateDir(realPath, forceCreate); } public static File createDirInStateArea(IPath path, boolean forceCreate) throws IOException { IPath realPath = localStatePath().append(path); return doCreateDir(realPath, forceCreate); } private static File doCreateDir(IPath realPath, boolean forceCreate) throws IOException { File dir = realPath.toFile(); //System.out.println("Creating directory " + dir); if (!dir.exists() || forceCreate) dir.mkdirs(); if (!dir.exists()) throw new IOException("Failed to create " + dir); return dir; } private static void setupNddlIncludes() { try { if (!localStatePath().append("nddl").toFile().exists()) { Bundle bundle = EuropaClientSidePlugin.getDefault().getBundle(); createDirInStateArea("nddl"); copyFileToStateArea("data/PlannerConfig.nddl", "nddl" + File.separator + "PlannerConfig.nddl", bundle); copyFileToStateArea("data/Plasma.nddl", "nddl" + File.separator + "Plasma.nddl", bundle); copyFileToStateArea("data/Resources.nddl", "nddl" + File.separator + "Resources.nddl", bundle); copyFileToStateArea("data/StringData.nddl", "nddl" + File.separator + "StringData.nddl", bundle); } } catch (IOException e) { throw new RuntimeException("Failed to set up Nddl inlude files", e); } } public static void clearLocalStateArea() throws IOException { StringBuilder errors = new StringBuilder(); for (String entry : localStatePath().toFile().list()) { try { File file = localStatePath().append(entry).toFile(); if (file.isDirectory()) FileUtils.deleteDirectory(file); else file.delete(); } catch (IOException ioe) { errors.append(ioe.toString()).append("\n"); } } if (errors.length() > 0) throw new IOException(errors.toString()); } public static interface EuropaServerManagerPreferences { public boolean useXmlRpc(); public boolean useRemoteServer(); public String engineType(); } protected static EuropaServerManagerPreferences prefs_ = null; public static EuropaServerManagerPreferences getPreferences() { return prefs_; } public static void setPreferences(EuropaServerManagerPreferences p) { prefs_ = p; } protected static boolean useXmlRpc() { return (prefs_ != null ? prefs_.useXmlRpc() : false); } protected static boolean useRemoteServer() { return (prefs_ != null ? prefs_.useRemoteServer() : false); } protected static String getEngineType() { return (prefs_ != null ? prefs_.engineType() : "dual"); } }