diff --git a/CHANGES b/CHANGES index 48813cbd6b0a796db72bfb2e222d68bb3bd704fb..1eaa08cb207378db109c8b8d945c3a1c739af56a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,24 @@ +v1.9 +---- + +- By default, client.StorageManager now ignores update requests for unknown + results. A delegate can be provided to examine each of those requests and + decide whether they should be accepted --for details, see + StorageManager.StorageManagerDelegateInterface. + + This also fixes the situation where "ghost" execution-in-progress items + where coming back each time the application was relaunched, no matter how + many time you delete them (these items are processes in the RUNNING state + whose processes is not executed anymore but which remain in the server + database after a crash e.g.). + +- Enhanced logging: + + - the number of connected users is now logged when a user successfully + connects (it was only logged when a user disconnects). + + - the list of programs'ids in every executed workflow + v1.8.1 ------ diff --git a/LICENSE/LICENCE.fr b/LICENSE/LICENCE.fr index 383a7724284d4c7f1eac86cccc31e2f103b13ae7..f8be916eb350a0cda0d983fce2d6bbadf3c3cc49 100644 --- a/LICENSE/LICENCE.fr +++ b/LICENSE/LICENCE.fr @@ -1,8 +1,7 @@ -Copyright Institut Télécom-Télécom Bretagne, 2002-2012 +Copyright Institut Télécom-Télécom Bretagne, 2002-2013 -Contributeurs: +Project leader, maintenance: Sébastien Bigaret <sebastien.bigaret@telecom-bretagne.eu> - Philippe Picouet <philippe.picouet@telecom-bretagne.eu> Ce logiciel, Praxis, est un environnement logiciel de fédération et de combinaison de services distribués au sein de workflows. diff --git a/LICENSE/LICENSE.en b/LICENSE/LICENSE.en index f1b703216d8f1dc92b8d231884f2a42a93e2b160..a20f17c73fd4e677fdb39714a9355811b00643ec 100644 --- a/LICENSE/LICENSE.en +++ b/LICENSE/LICENSE.en @@ -1,8 +1,7 @@ -Copyright Institut Télécom-Télécom Bretagne, 2002-2012 +Copyright Institut Télécom-Télécom Bretagne, 2002-2013 -Contributors: +Project leader, maintainer: Sébastien Bigaret <sebastien.bigaret@telecom-bretagne.eu> - Philippe Picouet <philippe.picouet@telecom-bretagne.eu> This software, Praxis, is a software environment for federating and combining distributed services using workflows. diff --git a/data/i18n/praxis_en.properties b/data/i18n/praxis_en.properties index a8d17f1dc86e0a803afad417b7a4d51b774b9d12..775ef70b783edfd367b6d6e843bc46261276c631 100644 --- a/data/i18n/praxis_en.properties +++ b/data/i18n/praxis_en.properties @@ -16,6 +16,10 @@ UI.actions.finished_exec_with_warnings.msg = The execution of {0} is UI.actions.finished_exec.title = End of execution UI.actions.wf_close = Close UI.actions.wf_close_tt = Close current workflow +UI.actions.wf_close_all = Close all +UI.actions.wf_close_all_tt = Close all workflows +UI.actions.wf_close_others = Close others +UI.actions.wf_close_others_tt = Close other workflows UI.actions.wf_export = Export without input files... UI.actions.wf_export_tt = Export a workflow UI.actions.wf_export_with_infiles = Export with input files... @@ -64,7 +68,7 @@ UI.dialog.about_content = <html>\ {3}\ <hr>\ <i>Powered by Praxis - Version {0} - date: {2}<br>\ - Copyright Institut Telecom-Telecom Bretagne, 2002-2012<br>\ + Copyright Institut Telecom-Telecom Bretagne, 2002-2013<br>\ All rights reserved</i>\ </html> UI.dialog.about_msg = @@ -174,7 +178,7 @@ UI.dialog.wf.open_failure = An unexpected error occu UI.dialog.wf.rename_save_before_confirm = The workflow has been modified and needs to be saved before it is renamed.\n\ Do you want to save it now? UI.dialog.wf.open_failure_title = Failed to open a workflow -UI.dialog.wf.open_select_workflows = Select the workflow(s) to be opene: +UI.dialog.wf.open_select_workflows = Select the workflow(s) to be opened: UI.dialog.wf.rename_enter_new_name = Please enter the new name of this workflow: UI.dialog.wf.rename_failure_title = Failed to rename a workflow UI.dialog.wf.rename_impossible = Failed to rename the workflow (unknown reason).\n\ diff --git a/data/i18n/praxis_fr.properties b/data/i18n/praxis_fr.properties index a63f9131242a07cdb7f2212ceeabb3ba7d6c08e8..e60a2b81fd0259d624a52bf344c5e5f776bc0f9c 100644 --- a/data/i18n/praxis_fr.properties +++ b/data/i18n/praxis_fr.properties @@ -16,6 +16,10 @@ UI.actions.finished_exec_with_warnings.msg = L''ex\u00E9cution du wor UI.actions.finished_exec.title = Fin de l''ex\u00E9cution d''un workflow UI.actions.wf_close = Fermer UI.actions.wf_close_tt = Fermer le workflow courant +UI.actions.wf_close_all = Tout fermer +UI.actions.wf_close_all_tt = Fermer tous les workflows +UI.actions.wf_close_others = Fermer les autres +UI.actions.wf_close_others_tt = Fermer les autres workflows UI.actions.wf_export = Exporter sans les fichiers d''entr\u00E9es...... UI.actions.wf_export_tt = Exporter un workflow UI.actions.wf_export_with_infiles = Exporter avec les fichiers d''entr\u00E9es... @@ -64,7 +68,7 @@ UI.dialog.about_content = <html>\ {3}\ <hr>\ <i>Elabor\u00e9 avec Praxis - Version {0} - dat\u00E9 du {2}<br>\ - Copyright Institut T\u00E9l\u00E9com-T\u00E9l\u00E9com Bretagne, 2002-2012<br>\ + Copyright Institut T\u00E9l\u00E9com-T\u00E9l\u00E9com Bretagne, 2002-2013<br>\ Tous droits r\u00E9serv\u00E9s</i>\ </html> UI.dialog.about_msg = diff --git a/src/eu/telecom_bretagne/praxis/client/StorageManager.java b/src/eu/telecom_bretagne/praxis/client/StorageManager.java index 19626e9a66a57bc67a64e17921c3db3c9f4c2012..be7213a3ef37e50b401f6747edfb8e1319d3a488 100644 --- a/src/eu/telecom_bretagne/praxis/client/StorageManager.java +++ b/src/eu/telecom_bretagne/praxis/client/StorageManager.java @@ -131,6 +131,40 @@ public class StorageManager } } + /** + * Interface for the {@link StorageManager}'s delegate. + * @author Sébastien Bigaret + */ + static public interface StorageManagerDelegateInterface + { + /** + * Called by the storage manager when it is receives update requests concerning unknown results, i.e. results + * whose worklow ID and execution ID do not {@link StorageManager#getResult(WorkflowID, ExecutionID) correspond} + * to any result it holds. + * + * @param result + * @return {@code true} if the result should be accepted by the storage manager. + */ + public boolean acceptUnknownResult(Result result); + } + + /** + * The default delegate when none is supplied, or when the delegate is reset (set to {@code null}). + * @author Sébastien Bigaret + */ + static public class DefaultStorageManagerDelegate implements StorageManagerDelegateInterface + { + /** + * Rejects all requests + * + * @return {@code false} + */ + public boolean acceptUnknownResult(Result result) + { + return false; + } + } + static public final String SERIALIZED_RESULT_FILENAME = "praxis_serialized_result.xml"; /** Paths of workflows */ @@ -158,6 +192,7 @@ public class StorageManager * */ static public boolean storeReceivedExecutionResults = true; + static private StorageManagerDelegateInterface delegate = new DefaultStorageManagerDelegate(); static synchronized private void init() { @@ -238,6 +273,13 @@ public class StorageManager return results_DO_NOT_USE_DIRECTLY_SEE_IMPLEMENTATION_COMMENT_ON_TOP; } + public static void setDelegate(StorageManagerDelegateInterface delegate) + { + if (delegate==null) + delegate=new DefaultStorageManagerDelegate(); + StorageManager.delegate = delegate; + } + /** * @param dir * @return @@ -318,7 +360,7 @@ public class StorageManager catch (InvalidXMLException e) // incl. FileNotFoundExc { Log.log.warning("Invalid workflow in directory: " + dir.getName() + ": " - + (e.getMessage() != null ? e.getMessage() : "<unknown reason>\n")); + + (e.getMessage() != null ? e.getMessage() : "<unknown reason>")); } return null; } @@ -636,6 +678,22 @@ public class StorageManager return result; } + /** + * Asks the delegate whether an unknown result should be accepted. A result is considered as unknown if + * {@link #getResult(WorkflowID, ExecutionID)} returns {@code null} for its execution- and workflow-ids. + * When the result is already known by the storage manager, this methods return {@code true}. + * + * @param result + * the result to test + * @return {@code true} if the result is accepted. + */ + protected static boolean acceptResult(Result result) + { + if (getResult(result.workflowID(), result.executionID()) == null) + return delegate.acceptUnknownResult(result); + return true; + } + /** * Stores the supplied result in the persistent store. * @param result the stored result @@ -855,6 +913,11 @@ public class StorageManager Log.log.severe("Received a null result!?!"); return; } + if (!acceptResult(result)) + { + Log.log.info("Rejecting unknown result "+result); + return; + } try { updateResult(result, event.file_conveyor); @@ -879,6 +942,11 @@ public class StorageManager for (Result result: results) { + if (!acceptResult(result)) + { + Log.log.info("Rejecting unknown result "+result); + continue; + } switch (result.getStatus()) { case CANCELLED: @@ -918,6 +986,11 @@ public class StorageManager Log.log.severe("Received a null result!?!"); return; } + if (!acceptResult(result)) + { + Log.log.info("Rejecting unknown result "+result); + return; + } try { updateResult(result, event.file_conveyor); diff --git a/src/eu/telecom_bretagne/praxis/common/ReleaseInfo.java b/src/eu/telecom_bretagne/praxis/common/ReleaseInfo.java index 052c0a9a384a5f3fadb63a7a1001858473225ebf..c70977117f31efc679832f89c2b73617bebf3875 100644 --- a/src/eu/telecom_bretagne/praxis/common/ReleaseInfo.java +++ b/src/eu/telecom_bretagne/praxis/common/ReleaseInfo.java @@ -7,15 +7,15 @@ package eu.telecom_bretagne.praxis.common; */ public abstract class ReleaseInfo { - public static final String release = "1.8.1"; + public static final String release = "1.9"; public static final int revision = 0; public static final int application_revision = Configuration.getInt("revision"); - public static final String release_date = "2012-11-23"; + public static final String release_date = "2013-02-19"; - public static final String package_date = "2012-11-23"; + public static final String package_date = "2013-02-19"; /** * Returns a array of three strings: the release, the application_revision number, and the release date diff --git a/src/eu/telecom_bretagne/praxis/common/RemoteComponents.java b/src/eu/telecom_bretagne/praxis/common/RemoteComponents.java index c54721a1b9f0a48189c0d7582daffcca4da25eea..f5ccd97f0ae15e0e9a653bd8ab114a9c5a0600d8 100644 --- a/src/eu/telecom_bretagne/praxis/common/RemoteComponents.java +++ b/src/eu/telecom_bretagne/praxis/common/RemoteComponents.java @@ -59,7 +59,7 @@ public class RemoteComponents * should be set to false as soon as possible, ideally at application startup.<br> * Setting this parameter does not reset the existing cache, if it exists; as a consequence, setting it to false * then reverting its value to true makes the component re-use any value previously cached.<br> - * Callers should be careful when disabling the cached. It should not be disabled in a normal, standard praxis + * Callers should be careful when disabling the cache. It should not be disabled in a normal, standard praxis * project, because many elements expects that the description returned by * {@link eu.telecom_bretagne.praxis.core.workflow.Program#getDescription() Program.getDescription()} is always the same objects. However, * there exists special contexts when this is needed, especially for applications working on the descriptions diff --git a/src/eu/telecom_bretagne/praxis/common/Utile.java b/src/eu/telecom_bretagne/praxis/common/Utile.java index 118255253314eb7fe28e6863008e03ad1180aa0d..e66d78adc278021a54d186b1fdbbda6d8e3925c9 100644 --- a/src/eu/telecom_bretagne/praxis/common/Utile.java +++ b/src/eu/telecom_bretagne/praxis/common/Utile.java @@ -1,6 +1,7 @@ /* License: please refer to the file README.license located at the root directory of the project */ package eu.telecom_bretagne.praxis.common; +import java.awt.Desktop; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; @@ -17,6 +18,7 @@ import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringWriter; +import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -934,6 +936,79 @@ public class Utile return null; } + /** + * Tests whether the method {@link #browse(URI)} is supported on the current platform. + * @return {@code true} if the method {@link #browse(URI)} is supported + */ + public static boolean isBrowseSupported() + { + if (!org.apache.commons.lang.SystemUtils.IS_OS_LINUX) + return Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE); + File url_open = searchSystemPath("xdg-open"); + if (url_open==null) + url_open = searchSystemPath("gnome-open"); + return ( (url_open != null && url_open.canExecute()) + || Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE) ); + } + + /** + * Opens an URI in the default browser. This method basically calls {@link Desktop#browse(URI)}, except on Linux + * where it tries to open it with {@code xdg-open} and {@code gnome-open}. Reason is that on Linux, Desktop.browse() + * uses a different browser when the URI points to a local file (URI starting with {@code file://}), while + * {@code xdg-open} and {@code gnome-open} opens such URI with the default web browser (the same as the one + * used when the URI starts with {@code http://} or {@code https://}). + * + * @param uri + * the URI to open in the default browser + * @return {@code true} if the URI could be opened + * @see #isBrowseSupported() + */ + public static boolean browse(URI uri) + { + if (!org.apache.commons.lang.SystemUtils.IS_OS_LINUX) + { + try + { + java.awt.Desktop.getDesktop().browse(uri); + } + catch (IOException e) + { + return false; + } + return true; + } + // Linux + /* + * Desktop.browse() calls gnome_open_url() which behaves differently for http:// or file://, while + * xdg-open does not (and neither does gnome-open). + */ + File url_open = searchSystemPath("xdg-open"); + if (url_open==null) + url_open = searchSystemPath("gnome-open"); + if (url_open==null) + { + try + { + java.awt.Desktop.getDesktop().browse(uri); + } + catch (IOException e) + { + return false; + } + return true; + } + try + { + Runtime.getRuntime().exec(new String[]{url_open.toString(),uri.toString()}); + } + catch (IOException e) + { + e.printStackTrace(); + return false; + } + return true; + } + /** * Determines whether a program is registered within the Windows registry, and returns a array suitable for * executing it by creating a {@link ProcessBuilder}. The method searches if the program is declared in the diff --git a/src/eu/telecom_bretagne/praxis/core/execution/WorkflowPlanner.java b/src/eu/telecom_bretagne/praxis/core/execution/WorkflowPlanner.java index c8e5802ea7f3cea861523287a26e9781945b7c54..b29639df81fd8cb287e74f503e101ab017e57343 100644 --- a/src/eu/telecom_bretagne/praxis/core/execution/WorkflowPlanner.java +++ b/src/eu/telecom_bretagne/praxis/core/execution/WorkflowPlanner.java @@ -5,7 +5,7 @@ import java.io.ByteArrayInputStream; import java.util.ArrayList; import eu.telecom_bretagne.praxis.common.InvalidXMLException; -import eu.telecom_bretagne.praxis.core.execution.ActivityContext; +import eu.telecom_bretagne.praxis.common.Log; import eu.telecom_bretagne.praxis.core.workflow.Program; import eu.telecom_bretagne.praxis.core.workflow.Workflow; import eu.telecom_bretagne.praxis.server.Serveur; @@ -63,6 +63,21 @@ public class WorkflowPlanner activity.getExecutionSet().add(program); } + // log details on the workflow being executed + try + { + StringBuffer log = new StringBuffer("Executing WF "); + log.append(workflow.id().dumpID()).append(":"); + for (Program program: workflow.getPrograms()) + { + log.append(" ").append(program.getDescription().id()); + } + Log.log.info(log.toString()); + } + catch (Throwable t) + { + Log.log.log(java.util.logging.Level.WARNING, "Unable to log info on the execution", t); + } } /** diff --git a/src/eu/telecom_bretagne/praxis/server/Serveur.java b/src/eu/telecom_bretagne/praxis/server/Serveur.java index 4982bf92227bb648ba9d8f44a404b67e778ff339..656bb11fdd3a7c21bcbbe5b97b45a01770875acc 100644 --- a/src/eu/telecom_bretagne/praxis/server/Serveur.java +++ b/src/eu/telecom_bretagne/praxis/server/Serveur.java @@ -9,6 +9,8 @@ import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -25,11 +27,11 @@ import eu.telecom_bretagne.praxis.common.Configuration; import eu.telecom_bretagne.praxis.common.Console; import eu.telecom_bretagne.praxis.common.Facade_xml; import eu.telecom_bretagne.praxis.common.FileResources; +import eu.telecom_bretagne.praxis.common.FileResources.ResourceNotFoundException; import eu.telecom_bretagne.praxis.common.InvalidXMLException; import eu.telecom_bretagne.praxis.common.Log; import eu.telecom_bretagne.praxis.common.PraxisPreferences; import eu.telecom_bretagne.praxis.common.RemoteComponents; -import eu.telecom_bretagne.praxis.common.FileResources.ResourceNotFoundException; import eu.telecom_bretagne.praxis.common.events.RMICommunicationFacade; import eu.telecom_bretagne.praxis.common.events.SSLSocketCommunicationFacade; import eu.telecom_bretagne.praxis.common.events.SocketCommunicationFacade; @@ -47,6 +49,13 @@ import eu.telecom_bretagne.praxis.server.execution.platform.PlatformDescription; */ public class Serveur extends UnicastRemoteObject implements RemoteServerInterface, ApplicationListener { + /** + * This is used to log the number of clients connected, when a client {@link #registerClient(Client) connects} or + * {@link #removeClient(Client) disconnects}. This may be used by an external tool to monitor the number of + * connected users by examining the logs. + */ + public static final String NUMBER_OF_CLIENTS_CURRENTLY_CONNECTED = "Number of clients currently connected: "; + private static final long serialVersionUID = 6870908801969839494L; /** @@ -97,6 +106,17 @@ public class Serveur extends UnicastRemoteObject implements RemoteServerInterfac { c.createStatement().execute(ResultStore.RESULT_db_schema); } + try + { + final Statement statement = c.createStatement(); + statement.execute("PRAGMA synchronous = NORMAL;"); + statement.execute("PRAGMA journal_mode=WAL;"); + statement.close(); + } + catch(SQLException e) + { + Log.log.log(Level.WARNING, "Unable to set journal_mode to WAL w/ synchronous=normal", e); + } resultStore = new ResultStore(c); } catch (Exception e) @@ -226,6 +246,7 @@ public class Serveur extends UnicastRemoteObject implements RemoteServerInterfac Log.log.fine("Registered client: " + client.toString()); clients.add(client); + Log.log.info(NUMBER_OF_CLIENTS_CURRENTLY_CONNECTED+clients.size()); } /* now that the client is registered, messages can be sent safely */ clientConnected(client); @@ -266,7 +287,7 @@ public class Serveur extends UnicastRemoteObject implements RemoteServerInterfac Log.log.fine("Unregistering client: null"); boolean ret_status = clients.remove(client); - Log.log.info("Number of clients currently connected: "+clients.size()); + Log.log.info(NUMBER_OF_CLIENTS_CURRENTLY_CONNECTED+clients.size()); if (Configuration.getBoolean("server.exit_on_last_client_exit") && clients.size()==0) { for (Server platform: platforms)