Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/flexstor/flexdbserver/services/external/ExecuteExternalService.java


1   /*
2    * ExecuteExternalService.java
3    *
4    * Copyright $Date: 2003/08/11 02:22:49 $ FLEXSTOR.net Inc.
5    *
6    * This work is licensed for use and distribution under license terms found at
7    * http://www.flexstor.org/license.html
8    *
9    */
10  
11  package com.flexstor.flexdbserver.services.external;
12  
13  import java.io.IOException;
14  import java.util.ArrayList;
15  import java.util.HashMap;
16  import java.util.Iterator;
17  import java.util.List;
18  import java.util.Map;
19  import java.util.Vector;
20  
21  import com.flexstor.common.constants.ActionPropertiesI;
22  import com.flexstor.common.constants.AssetRolesI;
23  import com.flexstor.common.constants.ServicesI;
24  import com.flexstor.common.data.ActionData;
25  import com.flexstor.common.data.ActionResult;
26  import com.flexstor.common.data.AssetRecordData;
27  import com.flexstor.common.data.ejb.disguiserecord.DisguiseAssetRecordData;
28  import com.flexstor.common.data.ejb.disguiserecord.DisguiseRecordData;
29  import com.flexstor.common.data.ejb.disguiserecord.GenericBucketRecordData;
30  import com.flexstor.common.errorlogger.FlexError;
31  import com.flexstor.common.importprocessor.ImportData;
32  import com.flexstor.common.resources.Resources;
33  import com.flexstor.common.services.ServiceArgumentsI;
34  import com.flexstor.common.services.SrvcNotAvailException;
35  import com.flexstor.common.util.Diagnostic;
36  import com.flexstor.common.util.DirectDotHot;
37  import com.flexstor.flexdbserver.disguise.BucketHelper;
38  import com.flexstor.flexdbserver.disguise.DisguiseLoaderException;
39  import com.flexstor.flexdbserver.services.Service;
40  import com.flexstor.flexdbserver.services.ServiceContext;
41  import com.flexstor.flexdbserver.services.io.IIntervalTimer;
42  import com.flexstor.flexdbserver.services.io.IntervalTimer;
43  
44  
45  /**
46   * <P>
47   * ExecuteExternalService <BR>
48   * <BLOCKQUOTE>
49   *    The Execute External Service will execute a external program as part of a workflow either 
50   *    during imports or during a customized process. <BR>
51   *    The service can be set so it accepts the full path of the program to be called and its 
52   *    arguments. Arguments can be meta data information stored in the data object 
53   *    passed to the service or literal arguments (defined for the service and passed
54   *    straight to the program. All arguments should be specified in one of the following files: <BR>
55   * <P>
56   *       - services.config <BR>
57   *       - roletype_services.config <BR>
58   *       - custom_process.xml <BR>
59   *       - xml file defined for FLEXsi <BR>
60   * </P>
61   *    The following rules apply when defining arguments: <BR>
62   * <P>
63   *       - Properties defined in roletype_services.config override properties already defined in 
64   *         services.config. <BR>
65   *       - Properties defined in either custom_process.xml or FLEXsi xml overrides properties 
66   *         already defined in services.config. <BR>
67   *       - The program should be defined to include it full path and name as follows: <BR>
68   * <P>
69   *            exceutable = /full/path/to/program/including/name
70   * </P>
71   *       - Meta data arguments should be defined as: <BR>
72   * <P>
73   *            argN.bucket   = <bucket label>, <field label> <BR>
74   *            argN+1.bucket = <bucket label>, <field label> <BR>
75   * </P>
76   *       - Literal arguments should be defined as: <BR>
77   * <P>
78   *            argN+2.literal = <value> <BR>
79   * </P>
80   *         where N is the position of this argument as expected in the program. <BR>
81   * </P>
82   *    <BR>
83   *    When calling this service from a customized process it is a good idea to call the MetaData Service
84   *    first, as it will load all bucket, element, asset and asset role information available from the
85   *    database. Calling the MetaData Service as part of an import process (before the DBUpdate Service)
86   *    will have no effects since information for the assets and any new bucket will not be available yet. <BR>
87   *    Due to some limitations on this service at the moment; the ExecuteExternal Service can only be run 
88   *    against primary assets during an import process. If run as part of a customized process, this limiation
89   *    does not apply. <BR>
90   * 
91   * </BLOCKQUOTE>
92   * </P>
93   *
94   * <P>
95   * Input Data Object <BR>
96   * <BLOCKQUOTE>
97   *    com.flexstor.common.data.ActionData or
98   *    com.flexstor.common.importprocessor.ImportData
99   * </BLOCKQUOTE>
100  * </P>
101  *
102  * <P>
103  * Output Data Object <BR>
104  * <BLOCKQUOTE>
105  *    com.flexstor.common.data.ActionResult containing a 
106  *    com.flexstor.common.importprocessor.ActionData object, if called during an import process, or
107  *    com.flexstor.common.data.ActionData, if called during a customized process
108  * </BLOCKQUOTE>
109  * </P>
110  *
111  * <P>
112  * Configurable Properties (in services.config or roletype_services.config) and <BR>
113  * Programmable Properties (passed inside data object ) <BR>
114  * <BLOCKQUOTE>
115  *    Global Properties (apply to all assets) <BR>
116  * <BLOCKQUOTE>
117  * <P>
118  *       inrole: The role of the assets for which to execute this program. Defaults to 
119  *               ALL if not defined. <BR>
120  *       Data type: String <BR>
121  *       Legal values: One of the following: HIGHRES, LOWRES, THUMBNAIL, LAYOUT, AUDIO, 
122  *                                           VIDEO or ALL <BR>
123  * </P>
124  * <P>
125  *       intype: The type of the assets for which to execute this program. Defaults to 
126  *               ALL if not defined. <BR>
127  *       Data type: String <BR>
128  *       Legal values: A type as defined in typerdat.txt <BR>
129  * </P>
130  * <P>
131  *       inflag: The flag of the assets for which to execute this program. Defaults to 
132  *               PARENT if not defined. <BR>
133  *       Data type: String <BR>
134  *       Legal values: One of the following: PARENT, TEMP_PARENT, CHILDREN, TEMP_CHILDREN, 
135  *                                           ALL, TEMP_ALL <BR>
136  * </P>
137  * <P>
138  *       executable: full path of program to be run by the service. <BR>
139  *       Data type: String <BR>
140  *       Legal values: /full/path/to/program/including/name <BR>
141  * </P>
142  * <P>
143  *       argN.bucket: Bucket and field label for which to use its meta data as argument N
144  *       of the program. <BR>
145  *       Data type: String <BR>
146  *       Legal values: A String of the following form <bucket label>, <field label> <BR>
147  * </P>
148  * <P>
149  *       argN.literal: Literal value to be passed directly to the program as argument N. <BR>
150  *       Data type: String <BR>
151  *       Legal values: A String with an argument for the program <BR>
152  * </P>
153  * <P>
154  *       intervaltimer_timeout: A user-specified property in services.config or roletype_services.config
155  *       that defines the timeout period for the watch-dog timer that interrupts the external process if 
156  *       it hangs.
157  *       Data type: int <BR>
158  *       Legal values: time (seconds) <BR>
159  * </P>
160  * </BLOCKQUOTE>
161  * </BLOCKQUOTE>
162  * </P>
163  */
164 public class ExecuteExternalService
165    implements Service, IIntervalTimer
166 {
167    // To get the version number from MKS
168    public final static String IDENTIFIER="$Id: ExecuteExternalService.java,v 1.3 2003/08/11 02:22:49 aleric Exp $";
169 
170    private ActionData data;
171    private String sThisService;
172    private Process pr;
173    private String sCommand;
174    protected String fileSeparator;
175    protected ServiceContext context;
176 
177    /**
178     * Holds all arguments passed to this service, which will be passed to the program.
179     * The first item on the list is the program name itself.
180     */
181    private List lExecArgs = new ArrayList();
182    
183    /** 
184     * Holds the property names; in other words, while the instance variable lExecArgs will 
185     * hold only the values, this List will hold the name of the properties.
186     */
187    private List lExecProps = new ArrayList();
188       
189    private String sInRole;
190    private String sInType;
191    private String sInFlag;
192 
193    private BucketHelper bucketHelper;
194    
195    /**
196     * Calls before the service is initialized (before initData is called) to 
197     * pass information about the environment in which the service is running.
198     * This environment consists of information about the properties set for the
199     * service in one of these files (services.config, roletype_services.config,
200     * or *.ctl), plus methods to access other information such as an instance
201     * of the service broker to invoke other services, the transaction id for
202     * the service, file separator character and local path for the installation
203     * directory and configuration directory.
204     * 
205     * @param context Holds information about the environment in which the service
206     *                is running.
207     */
208    public void setServiceContext( ServiceContext context )
209    {
210       this.context = context;
211       fileSeparator = context.getFileSeparator();
212    }
213    
214    public void initData( ActionData actionData )
215    {
216       this.data = actionData;
217 
218       //5475=Execute External Service
219       sThisService = Resources.get( 5475 );
220    }
221 
222    public ActionResult go()
223    {
224       ActionResult result = new ActionResult(false);
225       ImportData importData;
226       // If the ActionData is not an instance of ImportData, convert it to one
227       if ( data instanceof ImportData )
228       {
229          importData = (ImportData) data;
230          result.setData( importData );
231       }
232       else
233       {
234          importData = createImportData( data );
235          result.setData( data );
236       }
237             
238       if ( importData != null )
239       {
240          // Set the instance variable lExecArgs with the name and a list of the arguments to be 
241          // passed to the program. It first read the properties set for the service (in services.config
242          // or roletype_services.config) and then the ones set in the data object through an xml file 
243          // (either custom_process.xml or FLEXsi xml). Any argument in the data object will override an 
244          // argument already defined in the service.
245          // These methods also reads the inrole, intype and inflag properties to find the assets in 
246          // ImportData for which to execute the program.
247          retrieveServiceArgs();
248          retrieveDataObjectArgs( importData );
249          
250          DisguiseRecordData disguiseRef = importData.getDisguiseRecordRef();
251          Vector vAssets = disguiseRef.getAssets( sInRole, sInType, sInFlag );
252          if ( vAssets != null && vAssets.size() > 0 )
253          {
254             try
255             {
256                // load the BucketHelper, most likely we will need it
257                bucketHelper = new BucketHelper( disguiseRef.getDisguiseId() );
258 
259                DisguiseAssetRecordData asset;
260                for ( Iterator i = vAssets.iterator(); i.hasNext(); )
261                {
262                   asset = (DisguiseAssetRecordData) i.next();
263                   try 
264                   { 
265                      // Before executing the program, we have to go through the list of arguments and replace any
266                      // 'bucket' argument with its real value from the meta data
267                      List lThisAssetArgs = getMetaDataArguments( asset );
268                      // If the executeProgram goes fine, return successfully!!!!
269                      if ( executeProgram(lThisAssetArgs) )
270                         result.setSuccess(true);
271                   }
272                   catch ( Exception e ) { importData.addErrorRecord( new FlexError( FlexError.CRITICAL, sThisService, IDENTIFIER, e.getMessage() ) ); }
273                }
274             }
275             catch ( DisguiseLoaderException dle ) { importData.addErrorRecord( new FlexError( FlexError.CRITICAL, sThisService, IDENTIFIER, dle.getMessage() ) ); }
276          }
277          else
278             //6290=Unable to find assets of the following role, type and flag: %%1 %%2 %%3.
279             importData.addErrorRecord( new FlexError( FlexError.CRITICAL, sThisService, IDENTIFIER, 6290, new String[] { sInRole, sInType, sInFlag } ) );
280       }
281       else
282          // 6291=Cannot operate on an empty data object.
283          new FlexError( FlexError.CRITICAL, sThisService, IDENTIFIER, 6291 );
284 
285       return result;
286    }
287 
288    private ImportData createImportData( ActionData data )
289    {
290       // Call the ImportDataCreateService in order to convert the ActionData object into
291       // a ImportData object, this way it will be much easier to parse through it.
292       // Before calling the ImportDataCreateService, we first need to set the property that will specify
293       // where the hot directory is. Use the first AssetRecordData in ActionData
294       DirectDotHot refDirectDotHot = new DirectDotHot();
295       
296       AssetRecordData record = (AssetRecordData) data.getRecords().elementAt(0);
297       String sRecordCtlFilePath = refDirectDotHot.getCtlFileName( fileSeparator + record.getLocation(), true );
298       data.setString( ActionPropertiesI.HOT_DIRECTORY, sRecordCtlFilePath );
299       data.setServiceName( ServicesI.IMPORTDATA_CREATE_SERVICE );
300       ActionResult srvResult = null;
301       try { srvResult = (ActionResult) context.getServiceBroker().invokeImmediately(data); }
302       catch ( SrvcNotAvailException snae ) {}
303 
304       if ( srvResult != null && srvResult.isSuccess() )
305          return (ImportData) srvResult.getData();
306       else
307          return null;
308    }
309    
310    /**
311     * Retrieves arguments set in the service instance itself.
312     */
313    private void retrieveServiceArgs()
314    {
315       sInRole = context.getProperty( ServiceArgumentsI.ROLE_DATA_SOURCE );
316       sInType = context.getProperty( ServiceArgumentsI.TYPE_DATA_SOURCE );
317       sInFlag = context.getProperty( ServiceArgumentsI.FLAG_DATA_SOURCE );
318       
319       retrieveArgs( context.getProperties().entrySet().iterator() );
320    }
321    
322    /**
323     * Retrieves arguments set in the data object
324     */
325    private void retrieveDataObjectArgs( ImportData data )
326    {
327       String sTemp;
328       if ( (sTemp = data.getString(ServiceArgumentsI.ROLE_DATA_SOURCE)) != null )
329          sInRole = sTemp;
330          
331       if ( (sTemp = data.getString(ServiceArgumentsI.TYPE_DATA_SOURCE)) != null )
332          sInType = sTemp;
333          
334       if ( (sTemp = data.getString(ServiceArgumentsI.FLAG_DATA_SOURCE)) != null )
335          sInFlag = sTemp;
336          
337       retrieveArgs( data.getGlobalProperties().entrySet().iterator() );
338    }
339    
340    private void retrieveArgs( Iterator props )
341    {
342       // Temporary Map to store items before adding them in lExecProps and lExecArgs
343       HashMap htItems = new HashMap();
344       String sKey;
345       Map.Entry entry;
346       for ( ; props.hasNext(); )
347       {
348          entry = (Map.Entry) props.next();
349          sKey = (String) entry.getKey();
350          if ( sKey.equals("executable") )
351             htItems.put( new Integer(0), new String[] { sKey, (String)entry.getValue() } );
352          else if ( sKey.startsWith("arg") )
353          {
354             // Get whatever is between arg and the next '.' to check if it is a number; if it is
355             // then we should insert the property (either "bucket" or "literal" after the '.' in
356             // lExecProps and the value in lExecArgs
357             int nDotIndex = sKey.indexOf('.');
358             if ( nDotIndex > 3 ) // three is the length of arg
359             {
360                try 
361                { 
362                   int nArgIndex = Integer.parseInt(sKey.substring( 3, nDotIndex ));
363                   htItems.put( new Integer(nArgIndex), new String[] { sKey.substring(nDotIndex + 1), (String)entry.getValue() } );
364                }
365                catch ( NumberFormatException nfe ) { continue; } // This is not the argument we are looking for
366             }
367          }
368       }
369       
370       // Now populate lExecProps and lExecArgs
371       String[] saArguments;
372       for ( int i = 0; i < htItems.size(); i++ )
373       {
374          saArguments = (String[]) htItems.get( new Integer(i) );
375          // The indexes in the Hashtable represents the index at which the items must be placed
376          // in their respective list; if the item index is bigger than the list, then attach to it,
377          // otherwise, substitute the existing value in the list with this new one.
378          if ( i < lExecProps.size() )
379          {
380             lExecProps.set( i, saArguments[0]);
381             lExecArgs.set( i, saArguments[1] );
382          }
383          else
384          {
385             lExecProps.add( i, saArguments[0]);
386             lExecArgs.add( i, saArguments[1] );
387          }
388       }
389    }
390 
391    private List getMetaDataArguments( DisguiseAssetRecordData asset )
392       throws IllegalArgumentException
393    {
394       String sLabels = "";
395       String sProp, sBucketLabel, sFieldLabel;
396       int nArgIndex = 0;
397       try
398       {
399          // Clone lExecArgs so it can be used by all assets in this service.
400          // We will return a list of program arguments specific for this asset
401          List lThisAssetArgs = (ArrayList) ((ArrayList)lExecArgs).clone();
402          for ( Iterator i = lExecProps.iterator(); i.hasNext(); nArgIndex++ )
403          {
404             sProp = (String) i.next();
405             if ( sProp.equals("bucket") )
406             {
407                sLabels = (String) lThisAssetArgs.get(nArgIndex);
408                // Extract the bucket and field labels
409                int nCommaIndex = sLabels.indexOf(',');
410                if ( nCommaIndex != -1 )
411                {
412                   sBucketLabel = sLabels.substring( 0, nCommaIndex ).trim();
413                   sFieldLabel = sLabels.substring( nCommaIndex + 1 ).trim();
414                   int nFieldIndex = bucketHelper.getFieldIndex( sBucketLabel, BucketHelper.BY_LABEL, sFieldLabel, BucketHelper.BY_LABEL );
415 
416                   if ( nFieldIndex == -1 )
417                      // 7095=Illegal argument specified: %%1 is not a valid (bucket label, field label) combination
418                      throw new IllegalArgumentException( Resources.get(7095, sLabels) );
419                   
420                   if ( sBucketLabel.equals("Asset Bucket") )
421                      lThisAssetArgs.set( nArgIndex, asset.getValues()[nFieldIndex].getValue() );
422                   else if ( isAssetRoleBucket( sBucketLabel ) )
423                      lThisAssetArgs.set( nArgIndex, asset.getAssetRole().getValues()[nFieldIndex].getValue() );
424                   else
425                   {
426                      int nBucketLevel = bucketHelper.getBucketLevel( sBucketLabel, BucketHelper.BY_LABEL );
427                      Vector vBuckets = asset.getTraversalPath();
428                      GenericBucketRecordData bucket = (GenericBucketRecordData) vBuckets.elementAt(nBucketLevel);
429                      lThisAssetArgs.set( nArgIndex, bucket.getValues()[nFieldIndex].getValue() );
430                   }
431                }
432             }
433          }
434          return lThisAssetArgs;
435       }
436       catch ( NullPointerException npe )
437       {
438          // A NullPointerException means that there is not value for the argument specified; 
439          // treat this as a IllegalArgumentException indicating that an illegal argument was
440          // specified for the program
441          // 5469=Illegal argument specified: No value found for (%%1) for asset %%2, %%3. 
442          //      Check that value exists in the database and is available to the service.
443          throw new IllegalArgumentException( Resources.get( 5469, sLabels, 
444                                                                     asset.getLocation(), 
445                                                                     asset.getFileName() ) );
446       }
447    }
448    
449    private boolean isAssetRoleBucket( String sBucketLabel )
450    {
451       for ( int i = 0; i < AssetRolesI.roles.length; i++ )
452          if ( sBucketLabel.equalsIgnoreCase( AssetRolesI.roles[i] ) )
453             return true;
454             
455       return false;
456    }
457    
458    private boolean executeProgram( List lThisAssetArgs )
459       throws IOException, InterruptedException
460    {
461       // Convert lThisAssetArgs to a String[] to be passed to the Runtime.exec() method
462       String[] saCommand = (String[]) lThisAssetArgs.toArray( new String[] {} );
463       // Construct a String representation of the command executed
464       StringBuffer sbCommand = new StringBuffer();
465       for ( int i = 0; i < saCommand.length; i++ )
466       {
467          sbCommand.append( saCommand[i] );
468          sbCommand.append( ' ' );
469       }
470       sCommand = sbCommand.toString(); 
471          
472       // Create the watchdog timer in a separate thread
473       // Get the timer interval
474       // Default value is 360 seconds
475       int nInterval;
476       try { nInterval = Integer.parseInt( context.getProperty("intervaltimer_timeout") ); }
477       catch ( NumberFormatException nfe ) { nInterval = 360; }
478          
479       IntervalTimer watchdogTimer = new IntervalTimer(this, nInterval);
480       watchdogTimer.start();
481       
482       Diagnostic.trace( Diagnostic.APPSERVER_SERVICES, "Executing: " + sCommand );
483       pr = Runtime.getRuntime().exec( saCommand );
484       int nReturnedValue = pr.waitFor();
485       
486       // Once the process has completed, stop the timer
487       watchdogTimer.stopTimer();
488       
489       return nReturnedValue == 0 ? true : false; // zero is a successful return value
490    }
491 
492    /**
493     *  Called from IntervalTimer if it times out
494     */
495    public void timedOut()
496    {
497       if (pr != null)
498       {
499          // Terminate the external process since it is taking too long
500          pr.destroy();
501          pr = null;
502 
503          // 7093=Could not complete process in the time specified
504          Diagnostic.trace( Diagnostic.APPSERVER_SERVICES, Resources.get(7093) );
505          
506          // 7094=External process timed out on: %%1
507          new FlexError(FlexError.CRITICAL, IDENTIFIER, sThisService, 7094, new String[] { sCommand } );
508       }
509    }
510 }