Eclipse RCP : exécuter une LaunchConfiguration

Dans une application RCP, il peut être utile de créer une action pour exécuter une launchConfiguration présente dans le workspace.

Pour ce faire, il va falloir utiliser des classes présentes dans le plugin org.eclipse.debug et re-créer manuellement la configuration telle que l'IDE l'exécute lorsque l'on lance l'action "Run...".

Commençons par exporter la configuration dans un fichier afin de voir de quoi elle est constituée. Dans le menu "Run configurations", ouvrir l'onglet Common pour définir l'endroit où sauvegarder le fichier (option "Shared file") :

A la fermeture de cette fenêtre, un fichier *.launch a été créé. Voici un exemple de contenu :


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<launchConfiguration type="org.eclipse.ant.AntLaunchConfigurationType">
<booleanAttribute key="org.eclipse.ant.ui.DEFAULT_VM_INSTALL" value="false"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/my.project/buildProduct.xml"/>
</listAttribute>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
<listEntry value="1"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="my.project"/>
<stringAttribute key="org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER" value="org.eclipse.ant.ui.AntClasspathProvider"/>
<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="${workspace_loc:/my.project/buildProduct.xml}"/>
<stringAttribute key="process_factory_id" value="org.eclipse.ant.ui.remoteAntProcessFactory"/>
</launchConfiguration>
 

Le fichier est finalement assez simple, on trouve le type de configuration (ici une tâche Ant) et tous les paramètres requis.

Et voici en quelques lignes comment créer les objets nécessaires pour le lancement manuel :


import java.util.Arrays;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.debug.ui.DebugUITools;

public class RunAntBuild {

 public static void main(String... args) {
  ILaunchManager manager = DebugPlugin.getDefault().getLaunchManager();
  ILaunchConfigurationType type =
        manager.getLaunchConfigurationType("org.eclipse.ant.AntLaunchConfigurationType");
  try {
   ILaunchConfigurationWorkingCopy workingCopy =
           type.newInstance(null, "Run ant task");
   workingCopy.setAttribute("org.eclipse.ant.ui.DEFAULT_VM_INSTALL","false");
   workingCopy.setAttribute("org.eclipse.debug.core.MAPPED_RESOURCE_PATHS",Arrays.asList("/my.project/buildProduct.xml"));
   workingCopy.setAttribute("org.eclipse.debug.core.MAPPED_RESOURCE_TYPES",Arrays.asList("1"));
   workingCopy.setAttribute("org.eclipse.jdt.launching.CLASSPATH_PROVIDER","org.eclipse.ant.ui.AntClasspathProvider");
   workingCopy.setAttribute("org.eclipse.jdt.launching.PROJECT_ATTR","my.project");
   workingCopy.setAttribute("org.eclipse.jdt.launching.SOURCE_PATH_PROVIDER","org.eclipse.ant.ui.AntClasspathProvider");
   workingCopy.setAttribute("org.eclipse.ui.externaltools.ATTR_LOCATION",antXmlPath);
   workingCopy.setAttribute("process_factory_id","org.eclipse.ant.ui.remoteAntProcessFactory");
   DebugUITools.launch(workingCopy, ILaunchManager.RUN_MODE);
  } catch (CoreException e) {
   e.printStackTrace();
  }
 }

}

Fichier(s) joint(s) :

Eclipse RCP : fenêtre de sélection de ressource

Petit mémo pour ne pas ré-inventer la roue : voici un lien très utile qui recense les fenêtres de dialogue facilement implémentables disponibles dans la plateforme RCP : EclipseTips - Selection Dialogs in Eclipse

Et un petit exemple, avec un bonus. Ici on crée une fenêtre permettant de sélectionner uniquement un fichier XML présent dans le workspace :


final ElementTreeSelectionDialog selectDialog = new ElementTreeSelectionDialog(Display.getDefault().getActiveShell(), 
  new WorkbenchLabelProvider(), new BaseWorkbenchContentProvider());
selectDialog.setTitle("Sélection de ressource");
selectDialog.setMessage("Sélectionner le fichier XML :");
selectDialog.setInput(ResourcesPlugin.getWorkspace().getRoot());
selectDialog.setAllowMultiple(false);
selectDialog.setHelpAvailable(false);
selectDialog.setValidator(new ISelectionStatusValidator() {
 @Override
 public IStatus validate(final Object[] arg0) {
  boolean isFileOK = arg0.length>0 && (arg0[0] instanceof IFile) && ((IFile)arg0[0]).getName().endsWith(".xml");
  selectDialog.getOkButton().setVisible(isFileOK);
  return isFileOK ? Status.OK_STATUS : Status.CANCEL_STATUS;
 }
});
selectDialog.open();

String xmlPath = ((IFile)selectDialog.getFirstResult()).getRawLocationURI().getPath().substring(1);

On notera l'utilisation du ISelectionStatusValidator pour valider l'objet sélectionné.

Hope this helps!


Fichier(s) joint(s) :

Eclipse RCP : intéragir avec une tache ANT

Dans cet article je vais décrire comment lancer, dans une application RCP, une tâche Ant et surtout comment intéragir avec : input, console...

Tout d'abord, voici le script Ant à manipuler :


<project default="main">
    <target name="deploy.checks">
        <input message="Etes vous sûrs de vouloir lancer ce script?"
         validargs="o,n" addproperty="do.answer.runtime" />
        <condition property="do.abort.runtime">
            <equals arg1="n" arg2="${do.answer.runtime}"/>
        </condition>
        <fail if="do.abort.runtime">Abandon.</fail>
 </target>
    <target name="main" depends="deploy.checks">
     <echo message="Run from Eclipse!"/>
    </target>
</project>

Comme on le voit, celui-ci est assez simple, avec une tâche de demande de saisie et une tâche principale affichant des informations. Pour continuer, notre outil va avoir besoin des librairies suivantes :

  • ant.jar
  • ant-launcher.jar

Et voici le code :


import java.io.File;
import java.io.PrintStream;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.input.DefaultInputHandler;
import org.apache.tools.ant.input.InputRequest;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.MessageConsole;

public class RunAntBuild {

 public static void build() {
  // Fichier ant
  File buildFile = new File("...");
  
  // projet ant
  Project p = new Project();
  
  // gestion des inputs ant
  p.setInputHandler(new PluginInputHandler());
  
  // definition du logger...
  DefaultLogger consoleLogger = new DefaultLogger();
  
  // ... qui sera une console
  MessageConsole capiBuildConsole = new MessageConsole("AntConsole", null);
  
  // on ajoute notre console ant aux consoles de l'IDE Eclipse
  ConsolePlugin.getDefault().getConsoleManager().addConsoles(new IConsole[] {
   capiBuildConsole
  });
  
  // le stream de sortie pour ant
  PrintStream printStream = new PrintStream(capiBuildConsole.newOutputStream());
  consoleLogger.setErrorPrintStream(printStream);
  consoleLogger.setOutputPrintStream(printStream);
  consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
  p.addBuildListener(consoleLogger);
  
  // definition des propriétés
  p.setUserProperty("ant.file", buildFile.getAbsolutePath());
  
  p.init();
  ProjectHelper helper = ProjectHelper.getProjectHelper();
  p.addReference("ant.projectHelper", helper);
  helper.parse(p, buildFile);
  p.executeTarget(p.getDefaultTarget());

 }

 static class PluginInputHandler extends DefaultInputHandler {
  @Override
  public void handleInput(InputRequest arg0) throws BuildException {
   InputDialog dlg = new InputDialog(Display.getCurrent().getActiveShell(), "Vérification", arg0.getPrompt() + " (o/n)", "",
     null);
   if (dlg.open() == Window.OK) {
    arg0.setInput(dlg.getValue());
   }
  }
 }

}

Le code et les commentaires parlent d'eux-mêmes, mais il est intéressant de noter l'utilisation de la console via le ConsolePlugin et de l'InputDialog comme utilitaire pour permettre la saisie de paramètres d'entrée.

Voici donc le résultat en images :

Ouverture de la fenêtre d'input au début du script

Affichage dans la console des sorties texte du script

Et voilà! En espérant vous avoir fait gagner du temps...


Fichier(s) joint(s) :



Continuous Performance

Les test unitaires c'est bien, mais peut mieux faire!

J'ai déjà abordé le sujet de l'intégration continue avec un article sur Jenkins. Il est indéniable que ce genre d'outil apporte une sécurité et une assurance de stabilité dans la conception d'une application, même si cela ne fait pas tout!

Il est pourtant possible d'ajouter un nouvel élément dédié au respect des performances du code : les deux librairies JUnitPerf et ContiPerf sont des extensions JUnit qui permettent de mesurer notamment les temps d'exécution des différents tests.

JUnitPerf utilise essentiellement le design pattern Decorator pour ajouter des fonctionnalités aux tests existants. Assez simple d'utilisation, elle permet même de simuler des tests de monter en charge (Load test).

ContiPerf se base plutôt sur des annotations pour configurer les métriques désirées. Un peu plus souple et plus précis (par le calcul de moyennes sur des déciles), elle permet de générer des rapports graphiques sur les exécutions :

A vous de jouer!

Sources

  • http://blog.javabenchmark.org/2013/03/continuous-performance-and-junit-with.html

Fichier(s) joint(s) :



Camel Cheatsheet

Dans cet article j'ai décidé de regrouper un petit ensemble de trucs et astuces à connaître lors du développement d'applications Camel.

Et pour une fois, je vais rédiger ce contenu en anglais afin de le rendre accessible au plus grand nombre.

Edit : This article is viewable at (thanks to them) :

Java Code Geeks

Polling an empty directory (and send an empty message with null body) :
from("file://temp?sendEmptyMessageWhenIdle=true")
Stop a route :
.process(new Processor() {
 public void process(Exchange exchange) throws Exception {
  getContext().stopRoute("ROUTE_ID");
 }
})
Access a property of the object in the body :
admitting the object has a method named "getMydata()" :
new ValueBuilder(simple("${body.mydata}")).isEqualTo(...)
Define an aggregator :
.aggregate(simple("${header.id}.substring(0,15)"), 
       genericAggregationStrategy)
.completionPredicate(header(Exchange.BATCH_COMPLETE) .isEqualTo(Boolean.TRUE))
  • "${header.id}.substring(0,15)" : flag to differenciate messages (here, the returned string is common to all messages, we aggregate them all)
  • Exchange.BATCH_COMPLETE : predicate indicating the end of polling (all files parsed for example)
  • genericAggregationStrategy : above, an example of an aggregator grouping all messages' contents in a list :
public class GenericAggregationStrategy implements AggregationStrategy {
  @SuppressWarnings("unchecked")
  public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
    if (oldExchange == null) {
      ArrayList<Object> list = new ArrayList<Object>();
      list.add(newExchange.getIn().getBody());
      newExchange.getIn().setBody(list);
      return newExchange;
    } else {
      Object oldIn = oldExchange.getIn().getBody();
      ArrayList<Object> list = null;
      if(oldIn instanceof ArrayList) {
        list = (ArrayList<Object>) oldIn;
      } else {
        list = new ArrayList<Object>();
        list.add(oldIn);
      }
      list.add(newExchange.getIn().getBody());
      newExchange.getIn().setBody(list);
      return newExchange;
    }
  }
}
Manually trigger an aggregation's completion (whatever it is) :

Send a message with the header Exchange.AGGREGATION_COMPLETE_ALL_GROUPS = true
It is possible to do from("bean:..."), knowing that the bean will be polled permanently (like with "file") and re-instanciated each time.
Modify the message's body on a route, using :
.transform(myExpression)
with myExpression :
public class MyExpression implements Expression {
  public <T> T evaluate(Exchange exchange, Class<T> type) {
    MyBean newData = ...;
    return exchange.getContext().getTypeConverter()
      .convertTo(type, newData);
  }
}
Using JaxB :
  • on a route :
    .[un]marshal().jaxb("my.business_classes.package")
  • with a configurable DataFormat :
    .[un]marshal(jaxbDataFormat)
    
    with :
// indicate to Jaxb to not write XML prolog :
JaxbDataFormat jaxbDataFormat = 
   new JaxbDataFormat("my.business_classes.package");
jaxb.setFragment(true);
General concepts for threads management :
  • a from(...) = a thread
  • except for from("direct:...") wich creates a "named route" with a unique identifier only callable by another route (in the same thread than the caller).
  • The component .resequence().batch() creates a new thread to rethrow the messages.

Define a shutdown strategy :

getContext().setShutdownStrategy(new MyShutdownStrategy(getContext()));
With :
public class MyShutdownStrategy extends DefaultShutdownStrategy {
   protected CamelContext camelContext;
   private long timeout = 1;
   private TimeUnit timeUnit = TimeUnit.SECONDS;
   public SpiralShutdownStrategy(CamelContext camelContext) {
      this.camelContext = camelContext;
   }

   @Override
   public long getTimeout() {
      return this.timeout;
   }

   @Override
   public TimeUnit getTimeUnit() {
      return this.timeUnit;
   }

   @Override
   public CamelContext getCamelContext() {
      return this.camelContext;
   }

   /**
   * To ensure shutdown
   *
   */
   @Override
   public void suspend(CamelContext context, 
        List<RouteStartupOrder> routes) throws Exception {
      doShutdown(context, routes, getTimeout(), 
          getTimeUnit(), false, false, false);
   }

   /**
   * To ensure shutdown
   *
   */
   @Override
   public void shutdown(CamelContext context, 
        List<RouteStartupOrder> routes, long timeout, 
        TimeUnit timeUnit) throws Exception {
      doShutdown(context, routes, this.timeout, 
           this.timeUnit, false, false, false);
   }

   /**
   * To ensure shutdown
   *
   */
   @Override
   public boolean shutdown(CamelContext context, RouteStartupOrder route, 
      long timeout, TimeUnit timeUnit, boolean abortAfterTimeout)
         throws Exception {
      super.shutdown(context, route, this.timeout, 
          this.timeUnit, false);
      return true;
   }
}

Stop a batch :

.process(new Processor() {
   public void process(Exchange exchange) throws Exception {
      context.stop();
   }
});
Calling a method of a bean from a route :
  1. method's return is always affected to message's body. For example :
    • public void myMethod(Exchange e) :
      Will not modify the body
    • public boolean myMethod(Exchange e) :
      the boolean (or whatever primitive type) will be set in the body
    • public Object myMethod(Exchange e) :
      the Object will be placed in the body (even if null)
    • public Message myMethod(Exchange e) :
      the Message will be placed in the body (better avoid this)
    • public List<Object> myMethod(Exchange e) :
      the list will be set in the body : useful to use with .split(), each object will be sent in a new message
    • public List<Message> myMethod(Exchange e) :
      the list will be set in the body : a .split() will create a new message for each element (better avoid, see upper)
  2. configurable method's parameters :
    • public void myMethod(Exchange e) :
      the complete Exchange will be passed
    • public void myMethod(Object o) :
      Camel will try to convert the body in the required parameter's class
    • public void myMethod(@Body File o, @Header("myHeader") String myParamHeader) :
      Camel will inject each parameter as specified
Exceptions management on routes :
  • in a global way (to be declared before all routes) :
    onException(MyException.class, RuntimeCamelException.class).to(...)...
  • to truly handle Exception and not bubble it in routes (and logs) :
    onException(...).handled(true).to(...)...
  • to continue process in a route after an Exception :
    onException(...).continued(true).to(...)...
  • An exception is "handled" or "continued"
  • local way (in a route) :
    from(...)
    .onException(...).to("manage_error").log("FAIL !!").end()
    .to("continue_route")... 
    
For writing file, only the header Exchange.FILE_NAME is necessary.
Reorder messages with component .resequence :
  • uses an expression to compute the new order of messages, from a unique Comparable "key" (number, String or custom Comparator)
  • two ways :
    • .batch() : batch mode. Waits the reception of ALL the messages befor reorder them. ATTENTION : a new thread is created to rethrow messages.
    • .stream() : streaming mode. Uses a gap detection between the messages' keys to re-send them. It is possible to configure a maximal capacity and a timeout.
Split the body with a token :
.split(body().tokenize("TOKEN"))
Knowing that the TOKEN will be deleted from content. For example, if receiving a message containing : "data1TOKENdata2TOKENdata3", messages created will be : "data1", "data2, "data3".

So avoid this when treating XML data, prefer "tokenizeXML()".

Dynamic access to body's data :
Sending mails :
from("direct:mail")
 .setHeader("To", constant(mailTo))
 .setHeader("From", constant(mailFrom)) 
 .setHeader("Subject", constant(mailSubject)) 
 .to("smtp://${user}@${server}:${port}?password=${password}");
With attachment :
.beanRef(MAIL_ATTACHER, "attachLog");
//with
public class MailAttacher {
   public void attachLog(Exchange exc) throws Exception {
      File toAttach = ...;   
      exc.getIn().addAttachment(toAttach.getName(), 
               new DataHandler(new FileDataSource(toAttach)));
      // if needed
      exc.setProperty(Exchange.CHARSET_NAME, "UTF-8");
   }
}

Useful Exchange's properties :

  • Exchange.AGGREGATED_* : aggregations management
  • Exchange.BATCH_* : treated messages management
  • Exchange.FILE_* : File messages management
  • Exchange.HTTP_* : web requests management
  • Exchange.LOOP_* : loops management
  • Exchange.REDELIVERY_* : exceptions management
  • Exchange.SPLIT_* : splitted contents management
  • Exchange.EXCEPTION_CAUGHT : thrown exception on route
Loop a route :
from("direct:...")
.loop(countExpression)
.to("direct:insideLoop")
.end()

Where "countExpression" is an Expression used to dynamically compute the loop count (evaluated entering the loop)

It is preferable to move the loop's code in a new route if the process is complex.

Headers management :

Message's headers are defined at its creation. When using a ".split()", all subsequent messages will have the same headers from the original message (so be careful when managing files). In an aggregation, custom headers will have to be managed manually to be preserved in the rest of the route.

Intercept a message and execute a route parallely (to be declared before routes) :
interceptSendToEndpoint("ENDPOINT_TO_INTERSEPT").to(...)...
Send an exchange from a Java class to a route :
  1. Create a ProducerTemplate from Camel context :
    ProducerTemplate template = camelRouteBuilder
           .getContext().createProducerTemplate();
    
  2. Define its target endpoint :
    template.setDefaultEndpointUri("direct:start");
    
  3. Create a message and Exchange from template :
    Message message = new DefaultMessage();
    message.setBody("...");
    message.setHeader(key,value);
    
    Exchange exchange = template.getDefaultEndpoint().createExchange();
    exchange.setIn(message);
    
  4. Send message :
    template.send(exchange);
    
Definie a maximum inflight messages with a route policy :
ThrottlingInflightRoutePolicy policy = new ThrottlingInflightRoutePolicy();
policy.setMaxInflightExchanges(999);
policy.setScope(ThrottlingInflightRoutePolicy.ThrottlingScope.Route);

from("seda:route").routePolicy(policy)
Best pratices to combine Split and aggregator :

It is possible to use :
.split(body())
(...)
.aggregate(simple("${header.id}.substring(0,15)"), simpleAggregationStrategy)
.completionPredicate(header(Exchange.SPLIT_COMPLETE).isEqualTo(Boolean.TRUE))
(... end of route ...)
But with an empty list (null body or list size to 0), split behavior must be confusing and might shortcut the end of the route. Safer use is :
.split(body(), simpleAggregationStrategy)
(...)
.end()
The end() instruction clearly isolates the split instructions block and simpleAggregationStrategy is still triggered at SPLIT_COMPLETE

To be continued, don't hesitate to participate!


 
P.S. : un grand merci à l'équipe de "Camel masters", sans qui rien de tout cela n'aurait été possible! Daminou, Gaël et Gros ;-) kiss, love.

Fichier(s) joint(s) :