View Javadoc

1   /**
2    * Copyright 2005-2008 the original author or authors.
3    *
4    * Licensed under the Gnu General Pubic License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    *
8    *      http://www.opensource.org/licenses/gpl-license.php
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13   * See the Gnu General Public License for more details.
14   */
15  package org.figure8.join.control.action;
16  
17  import org.figure8.join.control.JoinAction;
18  import org.figure8.join.control.form.QuartzCronForm;
19  import org.figure8.join.core.Configurable;
20  import org.figure8.join.core.ContainerContextHandler;
21  import org.figure8.join.core.DuplicateEntityException;
22  import org.figure8.join.core.setup.BootstrapUtil;
23  import org.figure8.join.core.setup.BootstrapManager;
24  import org.figure8.join.services.scheduling.QuartzCronInfo;
25  import org.figure8.join.services.scheduling.QuartzCronManager;
26  import org.figure8.join.services.scheduling.QuartzCronParameterInfo;
27  import org.figure8.join.services.security.InvalidLoginException;
28  import org.figure8.join.services.remoting.CronService;
29  import org.figure8.join.services.remoting.services.BeansHelper;
30  import org.figure8.join.services.remoting.beans.RemoteQuartzCronInfo;
31  import org.figure8.join.businessobjects.security.User;
32  import org.figure8.join.util.ScriptLogAccessorProxy;
33  import org.figure8.join.util.LogUtil;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.beanutils.PropertyUtils;
37  import org.apache.struts.action.ActionForm;
38  import org.apache.struts.action.ActionForward;
39  import org.apache.struts.action.ActionMapping;
40  
41  import com.caucho.hessian.client.HessianProxyFactory;
42  
43  import javax.servlet.http.HttpSession;
44  import javax.servlet.http.HttpServletRequest;
45  import javax.servlet.http.HttpServletResponse;
46  
47  import java.io.IOException;
48  import java.io.InputStream;
49  import java.io.ByteArrayInputStream;
50  import java.util.List;
51  import java.util.Iterator;
52  import java.util.Properties;
53  import java.util.Enumeration;
54  import java.rmi.RemoteException;
55  /**
56   * Struts action used for managing crons within Join application. 
57   * @author <a href="mailto:jerome.evrard@gmail.com">Jerome Evrard</a>
58   * @author <a href="mailto:laurent.broudoux@free.fr">Laurent Broudoux</a>
59   * @version $Revision: 1.6 $
60   * 
61   * @struts.action path="/quartzCron" name="quartzCronForm"
62   *                scope="request" parameter="op" validate="true"
63   *                input="/pages/mainpage.jsp?body=/jsp/quartzcrons.jsp"
64   * @struts.action-forward name="Crons" path="/jsp/quartzcrons.jsp"
65   * @struts.action-forward name="Details" path="/jsp/quartzcrondetails.jsp"
66   * @struts.action-forward name="CronHelp" path="/jsp/cronexpressionhelp.jsp"
67   * @struts.action-forward name="Parameters" path="/jsp/configurableparameters.jsp"
68   */
69  public class QuartzCronActions extends JoinAction{
70  
71     // Static -------------------------------------------------------------------
72  
73     /** Get a commons logger. */
74     private static final Log log = LogUtil.getLog(QuartzCronActions.class);
75  
76     /** <code>SAVE_OP</code>: Action name to load a cron */
77     public static final String LOAD_OP = "load";
78     /** <code>SAVE_OP</code>: Action name to save a new cron */
79     public static final String SAVE_OP = "save";
80     /** Operation code for saving the configurable parameters of Quartz job */
81     public static final String SAVE_PARAM_OP = "saveParameters";
82     /** Operation code for retrieving the details and script log infos of a Quartz cron */
83     public static final String DETAILS_OP = "details";
84     /** Operation code for getting help about cron expression syntax */
85     public static final String CRON_HELP_OP = "cronHelp";
86  
87     /**
88      * The session scope attribute under which the cron object
89      * currently selected by our logged-in User is stored.
90      */
91     public static final String QUARTZ_CRON_KEY = "quartzCron";
92     /**
93      * The session scope attribute under which the retrieved crons
94      * list is stored.
95      */
96     public static final String QUARTZ_CRONS_KEY = "quartzCrons";
97     /**
98      * The request scope attribute under which is stored the cron that
99      * has raised a DuplicateEntityException during save of another one.
100     */
101    public static final String DUPLICATE_QUARTZ_CRON_KEY = "duplicate";
102 
103    
104    // Attributes ---------------------------------------------------------------
105    
106    /** <code>quartzcronManager</code>: The cron manager */
107    private QuartzCronManager cronManager;
108    /** <code>service</code>: Service to get a remote acces */
109    private CronService service;
110 
111 
112    // Constructors -------------------------------------------------------------
113 
114    /** Creates a new instance of QuartzCronActions. */
115    public QuartzCronActions(){
116    }
117 
118 
119    // Public -------------------------------------------------------------------
120 
121    /** @param quartzCronManager The cronManager to set.*/
122    public void setQuartzCronManager(QuartzCronManager quartzCronManager){
123       this.cronManager = quartzCronManager;
124    }
125 
126 
127    // Implementation of JoinAction ---------------------------------------------
128 
129    /**
130     * @param operation String representing the operation to invoke on Action
131     * @param mapping Mapping between forwards name and path for this action
132     * @param form The form object containing request parameters
133     * @param request The servlet container request wrapper
134     * @param response The servlet container response wrapper
135     * @return A forward to the next view to render and display
136     * @throws Exception such as InfraStructureExceptions ...
137     */
138    public ActionForward doExecute(String operation, ActionMapping mapping, ActionForm form,
139                                   HttpServletRequest request, HttpServletResponse response) throws Exception{
140       // Trace operation to execute.
141       log.debug("doExecute() called for '" + operation + "' operation");
142 
143       // References cannot be injected so retrieve them.
144       initializeManagerOrService();
145 
146       if (form instanceof QuartzCronForm){
147          // We always need the list of managed quartz cron, so put it
148          // directly into the appropriate request attribute.
149          updateQuartzCronsInRequest(request);
150 
151          if (LOAD_OP.equals(operation)){
152             return loadQuartzCron(mapping, (QuartzCronForm)form, request, response);
153          }
154          else if (SAVE_OP.equals(operation)){
155             return saveQuartzCron(mapping, (QuartzCronForm)form, request, response);
156          }
157          else if (SAVE_PARAM_OP.equals(operation)){
158             return saveQuartzCronParameters(mapping, (QuartzCronForm)form, request, response);
159          }
160          else if (DETAILS_OP.equals(operation)){
161             return getQuartzCronDetails(mapping, (QuartzCronForm)form, request, response);
162          }
163          else if (CRON_HELP_OP.equalsIgnoreCase(operation)){
164             return mapping.findForward("CronHelp");
165          }
166          else{
167             // Default : empty crons management page.
168             request.getSession().removeAttribute(QUARTZ_CRON_KEY);
169             removeObsoleteForm(mapping, request);
170             return mapping.findForward("Crons");
171          }
172       }
173       // This should not happen...
174       return null;
175    }
176 
177 
178    // Protected ----------------------------------------------------------------
179 
180    /**
181     * Load a specified cron info from datastore and fill form with it.
182     * @return A forward to the next view to render and display
183     */
184    protected ActionForward loadQuartzCron(ActionMapping mapping, QuartzCronForm form,
185                                           HttpServletRequest request, HttpServletResponse response) throws Exception{
186       HttpSession session = request.getSession();
187       // Retrieve QuartzCronInfo corresponding to name.
188       if (cronManager != null){
189          // Use local manager.
190          QuartzCronInfo cron = cronManager.getQuartzCron(form.getName());
191          session.setAttribute(QUARTZ_CRON_KEY, cron);
192          // Copy consumer into the form properties.
193          PropertyUtils.copyProperties(form, cron);
194          // Copy the user parameters
195          Iterator itUser = cron.getJobParameterInfos().iterator();
196          String strParams = "";
197          while (itUser.hasNext()){
198             QuartzCronParameterInfo param = (QuartzCronParameterInfo)itUser.next();
199             if (!param.isJobParameter())
200                strParams = strParams + param.getName() + "=" + param.getValue() +"\n";
201          }
202          form.setUserProperties(strParams);
203       }
204       else if (service != null){
205          // Use remote service, require login, logout.
206          String token = loginToRemoteService(request);
207          RemoteQuartzCronInfo remoteCron = service.getQuartzCronInfo(token, form.getName());
208          logoutFromRemoteService(token);
209          // Create a local QuartzCron for storing in session.
210          QuartzCronInfo cron = BeansHelper.getLocalObject(remoteCron);
211          session.setAttribute(QUARTZ_CRON_KEY, cron);
212          // Copy consumer into the form properties.
213          PropertyUtils.copyProperties(form, remoteCron);
214       }
215       // Forward.
216       return mapping.findForward("Crons");
217    }
218 
219    /**
220     * Load a specified cron info from datastore and fill form with it.
221     * @return A forward to the next view to render and display
222     */
223    protected ActionForward saveQuartzCron(ActionMapping mapping, QuartzCronForm form,
224                                           HttpServletRequest request, HttpServletResponse response) throws Exception{
225       // Is there a cron to update in session ?
226       HttpSession session = request.getSession();
227       QuartzCronInfo cron = (QuartzCronInfo)session.getAttribute(QUARTZ_CRON_KEY);
228 
229       // Create a new cron info or populate existing one with form.
230       if (cron == null){
231          log.info("Creation of a new QuartzCronInfo with name: " + form.getName());
232          cron = new QuartzCronInfo(form.getName(), form.getCronExpression(), "", form.getJobClass());
233       }
234       else{
235          log.info("Update of existing QuartzCronInfo having name: " + form.getName());
236          PropertyUtils.copyProperties(cron, form);
237       }
238 
239       // Remove all the existing user parameters infos ...
240       Iterator cronParams = cron.getJobParameterInfos().iterator();
241       while (cronParams != null && cronParams.hasNext()){
242          QuartzCronParameterInfo paramInfo = (QuartzCronParameterInfo)cronParams.next();
243          if (!paramInfo.isJobParameter())
244             cronParams.remove();
245       }
246       // Before adding the new ones.
247       createUserQuartzCronParameterInfos(cron, form);
248 
249       // Save the cron info using manager.
250       try{
251          if (cronManager != null){
252             log.debug("Saving QuartzCronInfo '" + form.getName() + "' using local manager");
253             // Use local manager.
254             cronManager.saveQuartzCronInfo(cron);
255          }
256          else if (service != null){
257             log.debug("Saving QuartzCronInfo '" + form.getName() + "' using remote service");
258             // Use remote service, require login, logout.
259             String token = loginToRemoteService(request);
260             service.saveCron(token, BeansHelper.getRemoteObject(cron));
261             logoutFromRemoteService(token);
262          }
263       }
264       catch (DuplicateEntityException dee){
265          // Store exception within request.
266          request.setAttribute(DUPLICATE_QUARTZ_CRON_KEY, dee.getOriginalEntity());
267          return mapping.findForward("Crons");
268       }
269 
270       // Forward depending of configurable nature of job class.
271       if (Configurable.class.isAssignableFrom(cron.checkJobClass())){
272          session.setAttribute(QUARTZ_CRON_KEY, cron);
273          // Give the configurable form what it need.
274          request.setAttribute(OP_PARAMETER, SAVE_PARAM_OP);
275          request.setAttribute("configurableId", cron.getName());
276          session.setAttribute("configurable", cron.checkJobClass().newInstance());
277          request.setAttribute("action", "quartzCron");
278 
279          // Retrieve consumer's parameters and fill form.
280          List parameters = cron.getJobParameterInfos();
281          for (int i=0; i < parameters.size(); i++){
282             QuartzCronParameterInfo param = (QuartzCronParameterInfo)parameters.get(i);
283             if (param.isJobParameter())
284                form.addParameter(param);
285          }
286 
287          return mapping.findForward("Parameters");
288       }
289 
290       // Remove form and session attribute if present.
291       session.removeAttribute(QUARTZ_CRON_KEY);
292       removeObsoleteForm(mapping, request);
293       return mapping.findForward("Crons");
294    }
295 
296    /**
297     * Save a cron parameters into datastore. The cron is an already existing one.
298     * @return A forward to the next view to render and display
299     */
300    protected ActionForward saveQuartzCronParameters(ActionMapping mapping, QuartzCronForm form,
301                                                     HttpServletRequest request, HttpServletResponse response)
302            throws Exception{
303       // Is there a cron to update in session ?
304       HttpSession session = request.getSession();
305       QuartzCronInfo cron = (QuartzCronInfo)session.getAttribute(QUARTZ_CRON_KEY);
306 
307       if (cron != null){
308          log.info("Adding QuartzCronParameterInfo to quartz cron: " + cron.getName());
309          // Remove all the existing job parameters infos.
310          Iterator cronParams = cron.getJobParameterInfos().iterator();
311          while (cronParams != null && cronParams.hasNext()){
312             QuartzCronParameterInfo paramInfo = (QuartzCronParameterInfo)cronParams.next();
313             if (paramInfo.isJobParameter())
314                cronParams.remove();
315          }
316 
317          // Browse parameters present in form.
318          Iterator parameters = form.getParameters().keySet().iterator();
319          while (parameters != null && parameters.hasNext()){
320             // Retrieve name, value pairs.
321             String name = (String)parameters.next();
322             String value = (String)form.getMappedParameter(name);
323             if (log.isDebugEnabled())
324                log.debug("Adding a cron parameter: " + name + "=" + value);
325             // Add a new parameter info to consume.
326             QuartzCronParameterInfo paramInfo = new QuartzCronParameterInfo(name, value, true);
327             cron.addJobParameterInfo(paramInfo);
328          }
329 
330          if (cronManager != null){
331             log.debug("Saving quartz cron '" + form.getName() + "' parameters using local manager");
332             // Use local manager.
333             cronManager.saveQuartzCronInfo(cron);
334          }
335          else if (service != null){
336             log.debug("Saving quartz cron '" + form.getName() + "' parameters using remote service");
337             // Use remote service.
338             String token = loginToRemoteService(request);
339             service.saveCron(token, BeansHelper.getRemoteObject(cron));
340             logoutFromRemoteService(token);
341          }
342       }
343       // Remove form and session attribute if present.
344       session.removeAttribute(QUARTZ_CRON_KEY);
345       removeObsoleteForm(mapping, request);
346       return mapping.findForward("Crons");
347    }
348 
349    /**
350     * Get all the details of a registered quartz cron from datastore. This includes getting its execution logs infos.
351     * @return A forward to the next view to render and display
352     */
353    protected ActionForward getQuartzCronDetails(ActionMapping mapping, QuartzCronForm form,
354                                                 HttpServletRequest request, HttpServletResponse response)
355            throws Exception{
356       if (cronManager != null){
357          // Use local manager.
358          QuartzCronInfo info = cronManager.getQuartzCron(form.getName());
359          request.setAttribute(QUARTZ_CRON_KEY, info);
360       }
361       else if (service != null){
362          // Use remote service.
363          String token = loginToRemoteService(request);
364          RemoteQuartzCronInfo info = service.getQuartzCronInfo(token, form.getName());
365          logoutFromRemoteService(token);
366          request.setAttribute(QUARTZ_CRON_KEY, info);
367       }
368       // Get information on execution logs.
369       ScriptLogAccessorProxy proxy = ScriptLogAccessorProxy.getInstance();
370       List logInfos = proxy.getScriptLogInfos(form.getName(), "QuartzAdapter");
371       request.setAttribute("scriptLogInfos", logInfos);
372       
373       // Forward to details.
374       return mapping.findForward("Details");
375    }
376 
377    /**
378     * Update the crons definitions collection within request.
379     * @param request The servlet container request wrapper
380     * @throws Exception if consumers cannot be refreshed
381     */
382    protected void updateQuartzCronsInRequest(HttpServletRequest request) throws Exception{
383       if (cronManager != null){
384          // Use local manager.
385          request.setAttribute(QUARTZ_CRONS_KEY, cronManager.getQuartzCronInfos());
386       }
387       else if (service != null){
388          // Use remote service (require login, logout).
389          String token = loginToRemoteService(request);
390          request.setAttribute(QUARTZ_CRONS_KEY, service.getQuartzCronInfo(token));
391          logoutFromRemoteService(token);
392       }
393    }
394 
395    /**
396     * Initialize the local consumer manager or the remote service depending
397     * on the application setup and the application side we are on.
398     * @throws Exception if manager cannot be found or remote service is not available
399     */
400    protected void initializeManagerOrService() throws Exception{
401       if (cronManager == null && service == null){
402          BootstrapManager bootstrapManager = BootstrapUtil.getBootstrapManager();
403          // Use remote service only if we are on synchronous side of a dissociate setup.
404          if (bootstrapManager.isDissociatedSetup() && bootstrapManager.isSynchronousSide()){
405             String url = bootstrapManager.getOtherSideUrl() + "/remoting/hessian/CronService";
406             log.info("Connecting to remote CronService using url: " + url);
407             HessianProxyFactory proxyFactory = new HessianProxyFactory();
408             service = (CronService)proxyFactory.create(CronService.class, url);
409          }
410          else{
411             // Use local manager provided by container facility.
412             log.info("Looking up local QuartzCronManager using 'quartzCronManager' component");
413             ContainerContextHandler handler = ContainerContextHandler.getInstance();
414             cronManager = (QuartzCronManager)handler.getComponent("quartzCronManager");
415          }
416       }
417    }
418 
419 
420    // Private ------------------------------------------------------------------
421 
422    /**
423     * Create the user parameters of the current edited cron from formular.
424     * @param cron The cron to create parameters for
425     * @param form The current edited formular.
426     */
427    private void createUserQuartzCronParameterInfos(QuartzCronInfo cron, QuartzCronForm form){
428       // Parse the string rpresenting user properties.
429       Properties properties = new Properties();
430       try{
431          InputStream is = new ByteArrayInputStream(form.getUserProperties().getBytes());
432          properties.load(is);
433       }
434       catch (IOException ioe){
435          ioe.printStackTrace();
436       }
437       Enumeration enumUserProps = properties.keys();
438       while (enumUserProps.hasMoreElements()){
439          // Create the new Quartz cron parameter
440          String name = (String)enumUserProps.nextElement();
441          String value = properties.getProperty(name);
442          QuartzCronParameterInfo parameter = new QuartzCronParameterInfo(name, value, false);
443          cron.addJobParameterInfo(parameter);
444       }
445    }
446 
447    /** Login for using the remote service. */
448    private String loginToRemoteService(HttpServletRequest request) throws InvalidLoginException, RemoteException{
449       // Extract user and its clear password from request.
450       User user = getUserContainer(request).getView().getUser();
451       String pwd = getUserContainer(request).getView().getClearPassword();
452       // Logon to remote service, get a token.
453       String token = service.login(user.getLogin(), pwd);
454       return token;
455    }
456 
457    /** Logout from the remote service. */
458    private void logoutFromRemoteService(String token) throws RemoteException{
459       service.logout(token);
460    }
461 }