Spring-Quartz : Planifier une tâche batch java en 30 minutes

L'objet de ce billet est de montrer comment planifier une tâche java avec Quartz et Spring et ce en n'ajoutant que quelques lignes de configuration xml.
Ce "QuickStart" introduisant Spring2.5+ et Quartz 1.6.5+ vous permet d'être opérationnel en 30 minutes.
Quartz est un ordonnanceur (scheduler) qui permet de planifier des tâches batch java (Job) à des intervalles spécifiés. Par exemple, réaliser des traitements lourds (génération de rapports, de statistiques) à des heures où les ressources du serveur sont suffisamment disponibles.
Quartz s’intègre assez facilement dans Spring (sauf peut être en mode Cluster, qui ne sera pas traité ici, pour lequel l'apprentissage est important!).
Afin de faciliter la mise en place de notre exemple, nous avons privilégié le mode RAMJobStore (les Jobs Quartz sont chargés en mémoire sans les persister).
Notre exemple est un projet web mais sachez qu'il est encore plus simple de prendre un projet java. Néanmoins le type de projet web est très instructif.
Nous terminerons ce billet en testant le Job exemple avec une classe JUnit4 avec Spring.
Tous les détails de configuration des dépendances Maven, de Spring et celles de Quartz dans Spring seront donnés.
Nous mentionnons, surtout à l'intérieur des codes, les explications utiles pour comprendre l'api Quartz et toutes les indications nécessaires afin d'éviter les quelques pièges de la configuration Spring-Quartz.
Un seul pré-requis nécessaire: connaître le framework Spring. La connaissance de Quartz peut s'acquérir de manière incrémentale.

SOMMAIRE:

Nous développons dans ce billet les trois grandes étapes suivantes:

ETAPE1: MISE EN PLACE DE L'ENVIRONNEMENT:

- Création et configuration du projet Eclipse et Maven,
- Configuration des dépendances maven pour Spring et Quartz: pom.xml,
- Configuration du fichier web.xml,
- Configuration Quartz : fichier quartz.xml,
- Configuration xml du fichier Spring.

ETAPE2: CRÉATION, PAS A PAS, DE L'EXEMPLE JOB QUARTZ:

- Écrire les beans Spring,
- Configuration dans Spring du Job Quartz.

ETAPE3: CRÉATION D'UNE CLASSE JUNIT4 DE TEST

- Création d'une classe JUnit4 et exécution du Job Quartz,

CONCLUSION

Nous vous proposons de détailler ensemble ci-après chacune de ces étapes:

ETAPE1: MISE EN PLACE DE L'ENVIRONNEMENT:

1- Création du projet Maven:

Nous allons créer ensemble, étape par étape, un projet sous Eclipse et Maven2 (sous windows). Avec la console Dos, se positionner dans le répertoire workspace d'Eclipse puis taper la commande:

mvn archetype:create -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DgroupId=com.netapsys.springquartz  -DartifactId=springquartz

Cela génère la structure du projet maven dans le répertoire springquartz.
Enfin, il faut s'assurer que le contenu du fichier généré pom.xml pointe sur la version java1.5 ou plus : pour cela, vérifier dans le pom.xml les lignes suivantes:

<artifactid>maven-compiler-plugin</artifactid>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>

Il est important d'ajouter (si ce n'est pas fait) les lignes ci-après, entre les tags <build>, afin de configurer les plugins utiles (par exemple celui de jetty).

<finalName>springquartz</finalName>
		<defaultGoal>jetty:run</defaultGoal>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.5</source>
					<target>1.5</target>
					<verbose>true</verbose>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.mortbay.jetty</groupId>
				<artifactId>maven-jetty-plugin</artifactId>
				<version>${jetty-version}</version>
				<configuration>
					<scanIntervalSeconds>30</scanIntervalSeconds>
				</configuration>
			</plugin>

Il reste encore à faire de ce projet maven un projet Eclipse à l'aide de la commande:

mvn eclipse:eclipse

Pour finir, importer ce projet dans Eclipse.

2-Ajout des dépendances pour Quartz:

Nous ajoutons au fichier pom.xml les dépendances nécessaires à Sping-Quartz en insérant entre les deux tags <dependencies>les lignes suivantes:

<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring</artifactid>
<version>2.5.6</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-web</artifactid>
<version>2.5.6</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-webmvc</artifactid>
<version>2.5.6</version>
</dependency>
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-test</artifactid>
<version>2.5.6</version>
</dependency>
<!-- quartz dependencies -->
		<dependency>
			<groupId>opensymphony</groupId>
			<artifactId>quartz-all</artifactId>
			<version>1.6.5</version>
		</dependency>

Nous ajoutons aussi les dépendances pour JUnit et log4j:

<dependency>
<groupid>junit</groupid>
<artifactid>junit</artifactid>
<version>4.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupid>commons-logging</groupid>
<artifactid>commons-logging</artifactid>
<version>1.1</version>
</dependency>
<dependency>
<groupid>log4j</groupid>
<artifactid>log4j</artifactid>
<version>1.2.14</version>
</dependency>

Notez que nous mettons à jour le pom.xml lorsque certaines options dans Quartz doivent être activées.

3- Configuration du web.xml:

Notre projet exemple étant conforme JEE, nous commençons donc par configurer le fichier web.xml. Celui-ci contient :
La déclaration de la servlet frontale de Spring nommée DispatcherServlet,
Et enfin, il indique que toutes les requêtes .html seront interceptées par ce DispatcherServlet.
A noter que le nom <servlet-name> est sprinsquartz et le fichier de configuration de Spring se nomme springquartz-servlet.xml.

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
  http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
   <display-name>SpringQuartz</display-name>
   <context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			/WEB-INF/springquartz-servlet.xml,classpath*:/spring.xml,classpath*:/quartz.xml
		</param-value>
   </context-param>
    <listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>     
   <servlet>
		<servlet-name>springquartz</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
   </servlet>
  <servlet-mapping>
		<servlet-name>springquartz</servlet-name>
		<url-pattern>*.html</url-pattern>
  </servlet-mapping>
</web-app>

Vous constatez que le contextConfigLocation dans le fichier web.xml pointe vers trois fichiers de configuration des contextes Web, Spring et Quartz:
springquartz-servlet.xml, spring.xml, quartz.xml.
Le contenu de quartz.xml et le contenu de spring.xml sont explicités aux sections suivantes.
Pour springquartz-servlet.xml, son contenu est standard et comporte ces quelques lignes:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p" 
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
          http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
  	<context:component-scan base-package="com.netapsys"/>
 
	<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
	<bean id="internalViewResolver"
	 class="org.springframework.web.servlet.view.InternalResourceViewResolver" 
	 p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>
</beans>

4- Configuration de Quartz:

La configuration de Quartz dans Spring est isolée dans le fichier quartz.xml.
Il contient ces quelques lignes (avec beaucoup d'explications et commentaires):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
		xmlns:tx="http://www.springframework.org/schema/tx"
		xmlns:aop="http://www.springframework.org/schema/aop"
		xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
				http://www.springframework.org/schema/aop http://www.springframework.org/schema/tx/spring-aop-2.5.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<!-- 
      Si default-autowire=byName dans le tag beans alors if faudrait preciser autowire=no dans le bean "schedulerFactoryBean"
	  En effet, lorsq'une dataSource est défini dans le projet, il est utile de mentionner l'option autowire="no" pr conserver l option par defaut RAMJobStore. 
	  NOTA: RAMJobStore est l option par defaut à moins qu'une datasource soit définie dans le projet.. 
-->
<!-- Configuration de Quartz  -->    
<!-- property name="concurrent" value="false" utile lorsque deux triggers sont specifiés pour ce job car QuartzJob is stateless  -->
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
  <property name="targetObject" ref="myService"/>
  <property name="targetMethod" value="getClient"/>
  <property  name="arguments">
    <list>
     <value>nom007</value>
     <value>prenom007</value>
    </list>
  </property>
  <property name="concurrent" value="false"/>   
</bean><bean id="cronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean" >
	<property name="jobDetail" ref="jobDetail" />
	<property name="cronExpression" value="0 0/5 * * * ? * *" />
</bean>
	<!-- 
	Cron-Expressions are used to configure instances of CronTrigger. Cron-Expressions are strings that are actually made up of seven sub-expressions, that describe individual details of the schedule. These sub-expression are separated with white-space, and represent:
   1. Seconds    2. Minutes     3. Hours   4. Day-of-Month  
   5. Month  6. Day-of-Week   7. Year (optional field)
 An example of a complete cron-expression is the string "0 0 12 ? * WED" - which means "every Wednesday at 12:00 pm".  Example Cron Expressions
 CronTrigger Ex1 - an expression to create a trigger that simply fires every 5 minutes from Saturday to Sunday "0 0/5 * * SAT-SUN ?"
 CronTrigger Ex2 - an expression to create a trigger that fires every 5 minutes, at 10 sec after the minute (i.e. 10:00:10 am, 10:05:10 am, etc.). "10 0/5 * * * ?"
 CronTrigger Ex3 - an expression to create a trigger that fires at 10:30, 11:30, 12:30, and 13:30, on every Wednesday and Friday."0 30 10-13 ? * WED,FRI"
 CronTrigger Ex4 - an expression to create a trigger that fires every half hour between the hours of 8 am and 10 am on the 5th and 20th of every month.
Note that the trigger will NOT fire at 10:00 am, just at 8:00, 8:30, 9:00 and 9:30 "0 0/30 8-9 5,20 * ?"
-->
  <bean id="jobFactory" class="org.springframework.scheduling.quartz.SpringBeanJobFactory"/>
 
  <bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" autowire="no">
	<property name="triggers">
	<list><ref bean="cronTrigger" /></list>
	</property>
	 <property name="jobFactory" ref="jobFactory" />
  </bean>
</beans>

Ces lignes configurent les trois notions de base de Quartz à savoir: Job, Calendar et Trigger. Nous n’expliquons pas ici ces notions de Quartz.
Nous utilisons ici le CronTrigger pour configurer des Jobs avec des répétitions calendaires infinies. Mais sachez que SimpleTrigger est plus facile à mettre en place.
Pour planifier une tâche qui exécute une méthode dans un bean, la classe org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean est suffisante.
Elle nécessite de lui indiquer la classe cible (via la property targetObject) et la méthode à exécuter (via targetMethod). C'est tout.
Observez que targetObject pointe vers une référence myService qui est une classe annotée via @Service dans le code de la couche service comme nous le verrons par la suite.
Pour le reste, c'est du standard, il faudrait définir jobFactory et schedulerFactoryBean pour configurer Quartz dans Spring.

IMPORTANT:
Le fait que notre exemple définit une datasource, nous oblige à ajouter autowire="no" dans la déclaration du bean "schedulerFactoryBean" afin de conserver le mode RAMJobStore sinon nous devons configurer les tables de Quartz dans la base mySql.
Les explications et commentaires insérés dans quartz.xml sont, me semble-t-il, suffisants pour comprendre l'essentiel.

5- Configuration de Spring :

Le fichier spring.xml configure les beans de la couche service et il déclare une datasource qui pointe sur une base MySql et indique les packages où Spring recherche les beans à auto-injecter:

[xml]<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
		xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
		xsi:schemaLocation="http://www.springframework.org/schema/beans 
                http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/tx/spring-aop-2.5.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
		default-autowire="byName">
	
	<context:component-scan base-package="com.netapsys.springquartz.dao"/>
        <context:component-scan base-package="com.netapsys.springquartz.service"/>
	<bean id="dataSource" 
		class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName"
			value="com.mysql.jdbc.Driver">
		</property>
		<property name="url"
			value="jdbc:mysql://localhost:3306/test">
		</property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>
</beans>

NOTE:
Les classes de tests JUnit sont exécutables à la fois sous maven et sous eclipse. N'hésitez pas à updater le classpath d'eclipse via la commande:

mvn eclipse:clean eclipse:eclipse

Nous vous conseillons, chaque fois que les dépendances du pom.xml sont modifiées, de relancer cette commande et de faire un "refresh" voire un clean du projet eclipse.

ETAPE2: CRÉATION, PAS A PAS, DE L'EXEMPLE JOB QUARTZ:

1- Ecrire les classes et beans du projet

1.1- Beans couche DAO:

L'interface de cette couche est simple car elle contient ces deux déclarations:

public interface IDao extends Serializable {
  Client getClient(final String nom, final String prenom);
  List<Client> getClients(); 
 }

La classe DaoImpl implémente ces deux méthodes ainsi:

@Repository 
public class DaoImpl implements IDao{
	private static final long serialVersionUID = 1L;
	final private Logger logger = Logger.getLogger(getClass().getName());
	/** * spring template for jdbc (jdk 5ou+)*/
	private SimpleJdbcTemplate jt=null;
	private DataSource dataSource;
	@Autowired
	public void setDataSource(DataSource dataSource) {
		jt=new SimpleJdbcTemplate(dataSource);
	}
	public Client getClient(final String nom, final String prenom) {
		if(nom==null || "".equals(nom.trim()) ) return null;
		Client client=null;
		String sql=Constants.SQL_REQUETE_CLIENT + "  WHERE lower(CLINOM)='"+nom.toLowerCase()+"'";
		if(prenom!=null &&  "".equals(prenom.trim()) ) {
			sql+=" AND lower(CLIPRENOM)='"+prenom.toLowerCase()+"'";
		}
		List<Client> clients=jt.query(sql, new Mapper(), new Object[]{});
		if(clients.size()>0) client=clients.get(0);
		logger.info("get Client = "+client);
		return client;
      }
      public List<Client> getClients(){		
		String sql=Constants.SQL_REQUETE_CLIENT ;
		List<Client> clients=jt.query(sql, new Mapper(), new Object[]{});		
		return clients;
      }
 
      private  final class Mapper implements ParameterizedRowMapper  {
	    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
	        return populateClient(rs);
	    }
	}
	private Client populateClient(final ResultSet rs)	throws SQLException {
		if(rs==null) return null;
		Client client=new Client();
		client.setCliId    (  rs.getLong("cliId")      );
		client.setNom   (  rs.getString("cliNom")   );
		client.setPrenom(  rs.getString("cliPrenom"));
		client.setEmail (  rs.getString("cliEmail") );
		return client;
	}
}

1.2- Beans couche Service:

L'implémentation de la couche service:

@Service()
public class MyService implements IDao{
	private final Logger logger=Logger.getLogger(this.getClass().getName());
	private static final long serialVersionUID = 1L;
	private DaoImpl daoImpl;
	@Autowired
	public void setDaoImpl(DaoImpl daoImpl) {
		this.daoImpl = daoImpl;
	}
	public Client getClient(final String nom, final String prenom) {		
		Client client= daoImpl.getClient(nom,prenom);
		logger.info("Client  '"+prenom+"','"+nom+"': "+client);
		return client;
	}
	public List<Client> getClients() {		
		List<Client> listeClients= daoImpl.getClients();
		for(Client client:listeClients) {logger.info(client);}
		return listeClients;
	}

ETAPE3: CRÉATION D'UNE CLASSE JUNIT4 DE TEST DU JOB QUARTZ:

La classe de test MyJobTest utilise les deux fichiers spring-test.xml et quartz-test.xml placés dans le dossier src/test/java/config.
Leurs contenus sont à l'identique de ceux des fichiers spring.xml et quartz.xml.

//certains imports omis...
import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.impl.calendar.CronCalendar;
import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.CronTriggerBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ContextConfiguration(locations={"classpath*:/config/spring-test.xml","classpath*:/config/quartz-test.xml"})
@RunWith( SpringJUnit4ClassRunner.class) 
public class MyJobTest {
private final Logger logger=Logger.getLogger(this.getClass().getName());
  @Autowired
  private SpringBeanJobFactory jobFactory;
  @Autowired
  private SchedulerFactoryBean schedulerFactory;
  @Autowired
  private CronTriggerBean trigger;
  @Test
  public void testJob() throws Exception {
	logger.info("starting testJob....");
	final TriggerFiredBundle bundle = new TriggerFiredBundle(
    	trigger.getJobDetail(),
    	trigger,
        new CronCalendar( trigger.getCronExpression() ),
        false, new Date(), new Date(), 
        trigger.getPreviousFireTime(),
        trigger.getNextFireTime()
    );
    final Job job = jobFactory.newJob(bundle);
    job.execute(new JobExecutionContext(schedulerFactory.getScheduler(), bundle, job));
  }
}

La capture (extrait) résultat du lancement de la commande mvn test donné:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.netapsys.tests.quartz.MyJobTest
09:05:34,111 INFO  (XmlBeanDefinitionReader.java:323) Loading XML bean definitions from URL [file:/C:/workspace/springswf/target/test-classes/config/spring-test.xml]
09:05:34,798 INFO  (XmlBeanDefinitionReader.java:323) Loading XML bean definitions from URL [file:/C:/workspace/springswf/target/test-classes/config/quartz-test.xml]
 
09:05:35,390 INFO  (SchedulerSignalerImpl.java:63) Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
09:05:35,390 INFO  (QuartzScheduler.java:215) Quartz Scheduler v.1.6.5 created.
09:05:35,390 INFO  (RAMJobStore.java:141) RAMJobStore initialized.
09:05:35,390 INFO  (StdSchedulerFactory.java:1229) Quartz scheduler 'schedulerFactoryBean' initialized from an externally provided properties instance.
09:05:35,406 INFO  (StdSchedulerFactory.java:1233) Quartz scheduler version: 1.6.5
09:05:35,406 INFO  (QuartzScheduler.java:2094) JobFactory set to: org.springframework.scheduling.quartz.SpringBeanJobFactory@148f8c8
09:05:35,406 INFO  (SchedulerFactoryBean.java:626) Starting Quartz Scheduler now
09:05:35,406 INFO  (QuartzScheduler.java:461) Scheduler schedulerFactoryBean_$_NON_CLUSTERED started.
09:05:35,453 INFO  (MyJobTest.java:44) starting testJob....
 
09:05:36,186 INFO  (MyService.java:39) Client  'prenom007','nom007': Id=1 nom=nom007 prenom=prenom007 email=007@007.com
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
09:05:36,264 INFO  (SchedulerFactoryBean.java:728) Shutting down Quartz Scheduler
09:05:36,264 INFO  (QuartzScheduler.java:570) Scheduler schedulerFactoryBean_$_NON_CLUSTERED shutting down.
09:05:36,264 INFO  (QuartzScheduler.java:496) Scheduler schedulerFactoryBean_$_NON_CLUSTERED paused.
09:05:36,280 INFO  (QuartzScheduler.java:621) Scheduler schedulerFactoryBean_$_NON_CLUSTERED shutdown complete.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

Enfin, terminons par le déploiement de l'application web via la commande suivante dans la console dos

mvn:jetty:run

Observez sur la console, toutes les cinq minutes la tâche est exécutée.

CONCLUSION

Vous avez pu donc voir la magie de Spring opèrer. Vous ajoutez quelques lignes de configuration xml et vous avez planifié une tâche qui se répète à intervalles spécifiés configurable à volonté.
Essentiellement, vous devez passer à la classe Spring MethodInvokingJobDetailFactoryBean les arguments attendus (en particulier targetMethod).
Le reste concerne les deux autres notions de base de Quartz: CronTrigger et le SchedulerFactoryBean.

8 commentaires

  1. Article intéressant et surtout très pratique.
    Mais j’ai une question : c’est quoi la différence entre les Job de Spring (ce que vous détaillez dans cet article) et une tâche planifiée qui lance un jar (ou un batch)?

  2. @Najjar
    Très bonne question

    On parle plutôt de Job Quartz et non de Job Spring -:)

    L’API quartz facilite la création de tâches planifiées compatibles avec JEE/JSE, avec :

    Indépendants des systèmes d’exploitation,

    Tolérance à la panne ( les jobs seront lancés au démarrage des serveurs),

    Possibilité de « persister » ces jobs dans la base de données,

    Et bien plus d’autres fonctionnalités à l’aide d’un code naturel optimisé qui s’intègre aisément dans les applications web/standalone. 

    Spring (magic Spring) offre la grande facilité de réaliser un job Quartz avec quelques lignes de XML sans apprendre le framework (API) Quartz.

    Pour répondre à la question:

    Le faire en créant directement une tâche planifiée passe par cibler le système d’exploitation. Et pour rendre cette tâche tolérante à la panne demande un effort (et beaucoup de peine) sans parler d’autre fonctionnalités.

    J’espère avoir répondu à votre question

  3. Nous sommes ici plutôt dans un cas de génération de courrier ou d’un rapport préformaté qui, comme le précise le post, doit être justement modifié par un utilisateur non informaticien.

    Pour l’impression en masse, nous opterons plutôt pour 2 solutions :
    1. Passer par un prestataire spécialisé en éditique
    2. S’appuyer sur un format PDF et non RTF comme ici

  4. Bonjour,

    Dommage que la plupart des fichiers doivent être retouchés pour que ca marche.
    D’autre part il manque des classes et la table MYSQL sans lesquels il est impossible de mettre en oeuvre tout ca.

    Sinon ca donne une idée du truc sans toutefois permettre la mise en oeuvre en 30 minutes !!

  5. bonjour,
    merci beaucoup pour l’exemple.
    Je travaille sous RAD7.5, et j’ai un petit souci :
    le test unitaire marche bien, par contre quand je deploie le war sur mon was et que je lance l’application, après il ne se passe rien. Auriez vous une idée?

  6. Bonjour,
    Je vous remercie pour ces informations cruciales. bon! je viens d’installer pour la 1ère fois Weblogic et je cherche comment déployer une application développée avec Oracle Forms/Reports 10g ? est ce que c’est la même chose que IAS 10g?

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Captcha *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.