Java tutorial
/* * Copyright (c) 2012 Diamond Light Source Ltd. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.dawnsci.python.rpc; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.dawb.common.util.eclipse.BundleUtils; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.preference.IPreferenceStore; import org.python.pydev.core.IInterpreterInfo; import org.python.pydev.core.IInterpreterManager; import org.python.pydev.core.IPythonNature; import org.python.pydev.core.MisconfigurationException; import org.python.pydev.core.NotConfiguredInterpreterException; import org.python.pydev.core.PythonNatureWithoutProjectException; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.plugin.nature.PythonNature; import org.python.pydev.runners.SimpleRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.diamond.scisoft.analysis.rpc.AnalysisRpcException; /** * Subclass of {@link AnalysisRpcPythonService} that uses PyDev's * InterpreterInfos to generate PYTHONPATHs and path to Python executable. * * TODO Instead of having concrete constructors of AnalysisRpcPythonPyDevService * around, this service should be contributed using OSGI and have an * associated interface. */ public class AnalysisRpcPythonPyDevService extends AnalysisRpcPythonService { private static final Logger logger = LoggerFactory.getLogger(AnalysisRpcPythonPyDevService.class); // TODO should we add bundle dependency on uk.ac.diamond.scisoft.python? private static final String UK_AC_DIAMOND_SCISOFT_PYTHON = "uk.ac.diamond.scisoft.python"; /** * Create new service using the default (first listed) Python * InterpreterInfo. * * @param autoConfig * if true, prompt user to configure a new Python Interpreter * @throws NotConfiguredInterpreterException * if no interpreters are configured. As autoConfig is a long * running process, PyDev runs this asynchronously so this * exception is still thrown at this point. This is a * recoverable error that should generally be handled in the * code, with a user prompt. * @throws MisconfigurationException * if any error occurs in the configuration of the python * interpreter. NOTE NotConfiguredInterpreterException is a * subclass of MisconfigurationException. This is a recoverable * error that should generally be handled in the code, with a * user prompt. * @throws AnalysisRpcException * if an error occurs setting up the AnalysisRpc remote Python * server or the Java client */ public AnalysisRpcPythonPyDevService(boolean autoConfig) throws MisconfigurationException, AnalysisRpcException { this(getDefaultInfo(autoConfig), null); } /** * Create new service using the named Python InterpreterInfo. * * @param interpreterName * name of the interpreter to use (as listed in Python * Interpreters) * @throws MisconfigurationException * if any error occurs in the configuration of the Python * interpreter. This is raised if the interpreterName is not * found. This is a recoverable error that should generally be * handled in the code, with a user prompt. * @throws AnalysisRpcException * if an error occurs setting up the AnalysisRpc remote Python * server or the Java client */ public AnalysisRpcPythonPyDevService(String interpreterName) throws MisconfigurationException, AnalysisRpcException { this(getInfoFromName(interpreterName), null); } /** * Create new service using the Python InterpreterInfo as configured for the * given project. * * @param project * project to use for InterpreterInfo. This means that the * PYTHONPATH used for the launched Python will match that of the * project. * @throws MisconfigurationException * if any error occurs in the configuration of the Python * interpreter. This is raised if the project does not have a * PythonNature. This is a recoverable error that should * generally be handled in the code, with a user prompt. * @throws AnalysisRpcException * if an error occurs setting up the AnalysisRpc remote Python * server or the Java client */ public AnalysisRpcPythonPyDevService(IProject project) throws MisconfigurationException, AnalysisRpcException { this(getInfoFromProject(project), project); } /** * Create new service using the Python InterpreterInfo as configured for the * given project. * * @param project * project to use for InterpreterInfo. This means that the * PYTHONPATH used for the launched Python will match that of the * project. * @throws MisconfigurationException * if any error occurs in the configuration of the Python * interpreter. This is raised if the project does not have a * PythonNature. This is a recoverable error that should * generally be handled in the code, with a user prompt. * @throws AnalysisRpcException * if an error occurs setting up the AnalysisRpc remote Python * server or the Java client */ public AnalysisRpcPythonPyDevService(IInterpreterInfo interpreter, IProject project) throws AnalysisRpcException { this(getJobUserDescription(interpreter), getPythonExe(interpreter), getEnv(interpreter, project)); } private AnalysisRpcPythonPyDevService(String jobUserDescription, File pythonExe, Map<String, String> env) throws AnalysisRpcException { super(jobUserDescription, pythonExe, null, env); // Default the port in the launched PyDev to this server getClient().setPyDevSetTracePort(getPyDevDebugServerPort()); } private static IInterpreterInfo getDefaultInfo(boolean autoConfig) throws MisconfigurationException { IInterpreterManager pythonInterpreterManager = PydevPlugin.getPythonInterpreterManager(); return pythonInterpreterManager.getDefaultInterpreterInfo(autoConfig); } private static IInterpreterInfo getInfoFromName(String interpreterName) throws MisconfigurationException { IInterpreterManager pythonInterpreterManager = PydevPlugin.getPythonInterpreterManager(); return pythonInterpreterManager.getInterpreterInfo(interpreterName, new NullProgressMonitor()); } private static IInterpreterInfo getInfoFromProject(IProject project) throws MisconfigurationException { PythonNature nature = PythonNature.getPythonNature(project); if (nature == null) { throw new MisconfigurationException("The project does not appear to have a " + "valid Python Nature, it needs to " + "be set as a PyDev Project"); } IInterpreterInfo info; try { info = nature.getProjectInterpreter(); } catch (PythonNatureWithoutProjectException e) { // Simplify the interface of the users of getInfoFromProject. // PythonNatureWithoutProjectException is only thrown from one place // and it probably should be a subclass of MisconfigurationException throw new MisconfigurationException(e.getMessage(), e); } return info; } private static String getJobUserDescription(IInterpreterInfo interpreter) { return "Python Service (" + interpreter.getExecutableOrJar() + ")"; } private static File getPythonExe(IInterpreterInfo interpreter) { return new File(interpreter.getExecutableOrJar()); } private static Map<String, String> getEnv(IInterpreterInfo interpreter, IProject project) { IPythonNature pythonNature = null; if (project != null) { pythonNature = PythonNature.getPythonNature(project); } IInterpreterManager manager = PydevPlugin.getPythonInterpreterManager(); String[] envp = null; try { waitForPythonNaturesToLoad(); envp = SimpleRunner.getEnvironment(pythonNature, interpreter, manager); } catch (CoreException e) { // Should be unreachable logger.error("exception occurred while setting environemt", e); } final Map<String, String> env = new HashMap<String, String>(System.getenv()); for (String s : envp) { String kv[] = s.split("=", 2); env.put(kv[0], kv[1]); } // To support this flow, we need both Diamond and PyDev's python // paths in the PYTHONPATH. We add the expected ones here. // NOTE: This can be problematic in cases where the user really // wanted a different Diamond or PyDev python path. Therefore we // force the paths in here. // TODO consider if scisoftpath should be added // in AnalysisRpcPythonService instead String path = env.get("PYTHONPATH"); if (path == null) { path = ""; } StringBuilder pythonpath = new StringBuilder(path); if (pythonpath.length() > 0) { pythonpath.append(File.pathSeparator); } String pyDevPySrc = getPyDevPySrc(); if (pyDevPySrc != null) { pythonpath.append(pyDevPySrc).append(File.pathSeparator); } String scisoftpath = getScisoftPath(); if (scisoftpath != null) { pythonpath.append(scisoftpath).append(File.pathSeparator); pythonpath.append(scisoftpath + "/src").append(File.pathSeparator); } env.put("PYTHONPATH", pythonpath.toString()); return env; } /** * PyDev defers the full loading of PythonNature to a background job. The * result is if you try to access the nature before it is ready you can't * get all the information. In our case we want to get the information about * how to construct the PYTHONPATH, therefore we need the natures to be * loaded. So this function blocks until all projects with a PythonNature * have that nature loaded. */ private static void waitForPythonNaturesToLoad() { IWorkspace workspace = ResourcesPlugin.getWorkspace(); for (int i = 100; i > 0; i--) { List<String> list = new ArrayList<String>(); IProject[] projects = workspace.getRoot().getProjects(); for (IProject project : projects) { if (project.isAccessible()) { try { if (project.isNatureEnabled(PythonNature.PYTHON_NATURE_ID)) { PythonNature nature = PythonNature.getPythonNature(project); if (!nature.isOkToUse()) { list.add(project.getName()); } } } catch (CoreException e) { // No PyDev Nature or otherwise unusable, so no // workaround to fix } } } if (list.isEmpty()) { logger.info("Python Natures for all PyDev projects loaded"); break; } String waiting = "Waiting (up to " + i / 10.0 + " seconds)" + " for Python Natures to load for these projects: "; String join = StringUtils.join(list.toArray(), ", "); logger.info(waiting + join); try { Thread.sleep(100); } catch (InterruptedException e) { } } } private static String getScisoftPath() { String scisoftpath = null; try { scisoftpath = BundleUtils.getBundleLocation(UK_AC_DIAMOND_SCISOFT_PYTHON).getAbsolutePath(); } catch (IOException e) { logger.error(UK_AC_DIAMOND_SCISOFT_PYTHON + " not available, import of scisoftpy.rpc may fail", e); } catch (NullPointerException e) { logger.error(UK_AC_DIAMOND_SCISOFT_PYTHON + " not available, import of scisoftpy.rpc may fail", e); } return scisoftpath; } private static String getPyDevPySrc() { String pyDevPySrc = null; try { pyDevPySrc = PydevPlugin.getPySrcPath().getAbsolutePath(); } catch (CoreException e) { logger.error("PydevPlugin's Src Path not available, debugging launched Python may not work", e); } return pyDevPySrc; } /** * Get the PyDev Debug Server Listening Port. * XXX This method is a reimplementation of: * DebugPluginPrefsInitializer.getRemoteDebuggerPort * which suffers from two problems: * 1) com.python.pydev.debug is not an exported package * 2) The DebugPluginPrefsInitializer is a preference initializer, but it * violates this rule for preferences: * Note: Clients should only set default preference values for their own bundle. * Therefore this method attempts to get the current value set for the port, * but in the case that the default-default value of 0 is returned (meaning * preference has not been initialised) we return the default value. */ public static int getPyDevDebugServerPort() { IPreferenceStore store = PydevPlugin.getDefault().getPreferenceStore(); // XXX should use DebugPluginPrefsInitializer.PYDEV_REMOTE_DEBUGGER_PORT int port = store.getInt("PYDEV_REMOTE_DEBUGGER_PORT"); if (port == 0) { // XXX should use DebugPluginPrefsInitializer.DEFAULT_REMOTE_DEBUGGER_PORT port = 5678; } return port; } }