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

Quick Search    Search Deep

Source code: com/flexstor/flexdbserver/services/checkincheckout/CheckOutService.java


1   /*
2    * CheckOutService.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.checkincheckout;
12  
13  import java.util.Enumeration;
14  import java.util.Hashtable;
15  import java.util.Iterator;
16  import java.util.Vector;
17  
18  import com.flexstor.common.constants.ActionPropertiesI;
19  import com.flexstor.common.constants.ServicesI;
20  import com.flexstor.common.data.ActionData;
21  import com.flexstor.common.data.ActionResult;
22  import com.flexstor.common.data.AssetRecordData;
23  import com.flexstor.common.errorlogger.FlexError;
24  import com.flexstor.common.gateway.CheckOutCheckInGateway;
25  import com.flexstor.common.gateway.exceptions.TransactionFailedException;
26  import com.flexstor.common.resources.Resources;
27  import com.flexstor.common.services.ServiceBrokerI;
28  import com.flexstor.common.services.SrvcNotAvailException;
29  import com.flexstor.common.settings.Settings;
30  import com.flexstor.common.threadmgr.ThreadCallbackI;
31  import com.flexstor.common.threadmgr.ThreadConsumerI;
32  import com.flexstor.flexdbserver.services.Service;
33  import com.flexstor.flexdbserver.services.ServiceContext;
34  import com.flexstor.flexdbserver.transactionmanager.TransMngCheckOut;
35  
36  /**
37   * <P>
38   * CheckOutService <BR>
39   * <BLOCKQUOTE>
40   *    Checks out and locks a file for modifications. <BR>
41   *    The CheckOut Service is performed in two steps, depending on whether the check-out location is in the
42   *    server or in the client's local machine. <BR>
43   *    - The first step is to change the database status to in-progress and copy the files to the place specified
44   *    by the client. <BR>
45   *    -  The second step is to update the status in the database to either checked-out (upon success)
46   *    or checked-in (upon failure); delete the temporary files if the checked-out location is not local to the
47   *    server; and finally send an email confirmation if requested by the client. <BR>
48   *    In the case where the checked-out location is local to the client's machine, both steps will be carried on
49   *    separately; first the client requests to perform the first step of the service; after completion,
50   *    the client copies the files from the temporary location to the its machine, and finally it calls the service
51   *    once more to perform the second step. <BR>
52   *    If the checked-out location is local to the server, the service will complete both steps without the client
53   *    intervention. <BR>
54   *    It is very important to call the second step when the checked-out location is local to the client's machine;
55   *    otherwise the assets' status will remain in-progress permanently. <BR>
56   * </BLOCKQUOTE>
57   * </P>
58   * <P>
59   *    The CheckOut Service supports the following cases: <BR>
60   *    1.- Destination is in client machine, preserve resource fork (assume MacBinary will be created) <BR>
61   *    <BLOCKQUOTE>
62   *          Create MacBinary in temp location specified by client <BR>
63   *          Client then copies file to local machine <BR>
64   *    </BLOCKQUOTE>
65   *    2.- Destination is in client machine, do not preserve resource fork <BR>
66   *    <BLOCKQUOTE>
67   *          Copy data fork in temp location specified by client <BR>
68   *          Client then copies file to local machine <BR>
69   *    </BLOCKQUOTE>
70   *    3.- Destination is in FlexDBServer, prserve resource fork by creating MacBinary <BR>
71   *    <BLOCKQUOTE>
72   *          Create MacBinary in destination <BR>
73   *    </BLOCKQUOTE>
74   *    4.- Destination is in FlexDBServer, preserve resource fork by copying data+rsrc fork <BR>
75   *    <BLOCKQUOTE>
76   *          Copy data+rsrc fork in destination <BR>
77   *    </BLOCKQUOTE>
78   *    5.- Destination is in FlexDBServer, do not preserve resource fork <BR>
79   *    <BLOCKQUOTE>
80   *          Copy data fork in destination <BR>
81   *    </BLOCKQUOTE>
82   *    6.- Destination is in another server, preserve resource fork (assume MacBinary will be created) <BR>
83   *    <BLOCKQUOTE>
84   *          Create MacBinary in FlexDBServer's temp location <BR>
85   *          NFS copy file to server <BR>
86   *    </BLOCKQUOTE>
87   *    7.- Destination is in another server, do not preserve resource fork <BR>
88   *    <BLOCKQUOTE>
89   *          NFS copy data fork to server <BR>
90   *    </BLOCKQUOTE>
91   * </P>
92   *
93   * <P>
94   * Input Data Object <BR>
95   * <BLOCKQUOTE>
96   *    com.flexstor.common.data.ActionData
97   * </BLOCKQUOTE>
98   * </P>
99   *
100  * <P>
101  * Output Data Object <BR>
102  * <BLOCKQUOTE>
103  *    com.flexstor.common.data.ActionResult
104  * </BLOCKQUOTE>
105  * </P>
106  *
107  * Programmable Properties (passed inside data object) <BR>
108  * <BLOCKQUOTE>
109  *    Global Properties (apply to all assets) <BR>
110  * <BLOCKQUOTE>
111  * <P>
112  *       SourceLocation: Full path where files will be checked out; if files are to be checked out
113  *       to a local machine or another server, this property needs to be set to a directory called after
114  *       the transaction ID inside the public temp directory. <BR>
115  *       Data type: String <BR>
116  *       Legal values: Full path to destination location for checked out files <BR>
117  * </P>
118  * <P>
119  *       IsLocationInServer: Used by the service to determine if the checked out location is in
120  *       another machine or in this server. If checked out location is in this server and
121  *       KeepResourceFork is true, the service will copy the file to the specified location,
122  *       preserving the resource fork (defaults to true). <BR>
123  *       Data type: Boolean <BR>
124  *       Legal values: true or false <BR>
125  * </P>
126  * <P>
127  *       KeepResourceFork: If set to true, the service will preserve the resource
128  *       fork according to the following rules (defaults to true): <BR>
129  *       - If IsLocationInServer is true, the file is copied along with its resource fork <BR>
130  *       - If IsLocationInServer is set to false, a MacBinary file is created to be moved to the remote machine <BR>
131  *       - Otherwise, the resource fork is ignored <BR>
132  *       Data type: Boolean <BR>
133  *       Legal values: true or false <BR>
134  * </P>
135  * <P>
136  *       StepNumber: Defines what step of the check-out service is to be performed. It default to 1. <BR>
137  *       Data type: Integer <BR>
138  *       Legal values: 1 (check-out, first step) or 2 (check-out, second step) <BR>
139  * </P>
140  * <P>
141  *       BadRecordsVector: A Vector containing the list of bad records; this property must be set by the
142  *       client before calling the second step of the service. This list is the original list sent by the service
143  *       to the client, plus any update made by the client itself in case a file failed to be copied. <BR>
144  *       Data type: java.util.Vector <BR>
145  *       Legal values: A Vector containing CheckOutRecordData objects <BR>
146  * </P>
147  * <P>
148  *       ConfirmData: An optional ActionData object to be used for email confirmation. <BR>
149  *       Data type: com.flexstor.common.data.ActionData <BR>
150  *       Legal values: A ActionData object with the email address of the sender, subject, cc, and body text
151  *       included. The CheckOut Service will attach the path to the successful assets at the end of the body.  <BR>
152  * </P>
153  *
154  * <BLOCKQUOTE>
155  *   Specific Properties (apply to each individual asset) <BR>
156  * <BLOCKQUOTE>
157  * <P>
158  *       UrlString: An optional html/url path to be added at the end of the body of the MimeData. <BR>
159  *       Data type: java.lang.String <BR>
160  *       Legal values: The path of the checked-out location for the record in url form <BR>
161  * </P>
162  * </BLOCKQUOTE>
163  * </BLOCKQUOTE>
164  */
165 public class CheckOutService
166    implements Service, ThreadCallbackI
167 {
168    // To get the version number from MKS
169    public final static String IDENTIFIER="$Id: CheckOutService.java,v 1.5 2003/08/11 02:22:49 aleric Exp $";
170 
171    protected ActionData    data                 = null;
172    protected ActionResult  result               = null;
173    protected Vector          vRecords             = null;
174    protected Vector          vBadRecords          = null;
175    protected int             nManagerRunning      = 0;
176    protected String          sThisService         = "";
177    protected boolean         bIsLocationInServer  = true;
178    protected boolean         bFirstStepSuccessful = true;
179 
180    protected ServiceContext context;
181    protected int id = -1;
182    protected String fileSeparator;
183    protected ServiceBrokerI serviceBroker;
184    
185    /**
186     * Calls before the service is initialized (before initData is called) to 
187     * pass information about the environment in which the service is running.
188     * This environment consists of information about the properties set for the
189     * service in one of these files (services.config, roletype_services.config,
190     * or *.ctl), plus methods to access other information such as an instance
191     * of the service broker to invoke other services, the transaction id for
192     * the service, file separator character and local path for the installation
193     * directory and configuration directory.
194     * 
195     * @param context Holds information about the environment in which the service
196     *                is running.
197     */
198    public void setServiceContext( ServiceContext context )
199    {
200       this.context = context;
201       id = context.getTransactionId();
202       fileSeparator = context.getFileSeparator();
203       serviceBroker = context.getServiceBroker();
204    }
205    
206    /**
207    * A data initialization method called at the beginning of the service.
208    * The input argument, ActionData must be cast into its subclass in order
209    * to extract the CheckOutService specific data from it.
210    */
211    public void initData(ActionData actionData)
212    {
213       this.data = actionData;
214       vRecords = data.getRecords();
215       // If vBadRecords is null, initialize to an empty Vector to avoid a NullPointerException
216       vBadRecords = (Vector)data.getObject( ActionPropertiesI.BADRECORDS_VECTOR );
217       if ( vBadRecords == null )
218          vBadRecords = new Vector();
219 
220       //6233=Check-Out Service
221       sThisService = Resources.get(6233) + " (" + id + ")";
222    }
223 
224    /**
225    * The start of the CheckOut Service.
226    */
227    public ActionResult go()
228    {
229       try { bIsLocationInServer = data.getBoolean( ActionPropertiesI.IS_LOCATION_IN_SERVER ).booleanValue(); }
230       catch ( NullPointerException npe ) {}
231 
232       int nStepNumber = 1;
233       try { nStepNumber = data.getInteger( ActionPropertiesI.STEP_NUMBER ).intValue(); }
234       catch ( NullPointerException npe ) {}
235       catch ( NumberFormatException nfe ) {}
236 
237       switch ( nStepNumber )
238       {
239          case 1:
240             bFirstStepSuccessful = doCheckOutFirstStep();
241             // Don't perform the second step of check-out (updating status to checked out in db) if the
242             // location is in the client machine (client will ask for the second step manually)
243             if ( !bIsLocationInServer )
244                break;
245          case 2:
246             doCheckOutSecondStep();
247       }
248 
249       // Remove this persisted transaction from TransList.per
250       (new TransMngCheckOut( data.getTransId() )).setTransactionCompletedState();
251 
252       if ( vBadRecords.size() > 0 )
253       {
254          result = new ActionResult( false );
255          result.setBadRecords( vBadRecords );
256          // Update the list of record with the good ones only, if any
257          data.setRecords( vRecords );
258       }
259       else
260          result = new ActionResult( true );
261 
262       result.setId( data.getTransId() );
263       result.setData( data );
264       return result;
265    }
266 
267    /**
268     * Performs the following check-out steps:
269     * - Change the database status to in-progress
270     * - Copy the file to the place specified by the client
271     * This service will return false only if the status could not be changed to in-progress.
272     */
273    private boolean doCheckOutFirstStep()
274    {
275       // Set the status to check-out in progress
276       if ( setStatusToInProgress() )
277       {
278          // Create subsets of data object per server and start a new ServiceManager
279          //for each.
280          Hashtable htSubSets = createSubSets();
281 
282          if ( htSubSets != null && htSubSets.size() > 0 )
283          {
284             // Create one thread per data object SubSet and start a Service Manager
285             // synchronized to make sure we start all the threads before getting any response back
286             // from observables.
287             synchronized (this)
288             {
289                for (Enumeration e = htSubSets.keys(); e.hasMoreElements(); )
290                {
291                   String sServerName = (String) e.nextElement();
292                   ActionData dataSubSet = (ActionData)htSubSets.get(sServerName);
293                   (new CheckOutServiceManager( sServerName, dataSubSet, this )).startManager();
294                   nManagerRunning++;
295                }
296             }
297          }
298          else
299          {
300             // Set all records to bad and remove the Vector of good records
301             vBadRecords = data.getRecords();
302             vRecords = null;
303          }
304       }
305       else
306       {
307          // Set all records to bad and remove the Vector of good records
308          vBadRecords = data.getRecords();
309          vRecords = null;
310          return false;
311       }
312 
313       // while there are services still running, just wait until be notified of an update;
314       // if all services are done, exit loop and exit Check-Out Service
315       synchronized (this)
316       {
317          while ( nManagerRunning > 0 )
318          {
319             try
320             {
321                this.wait();
322             }
323             catch (InterruptedException ie)
324             {
325                // Set all records to bad and remove the Vector of good records
326                vBadRecords = data.getRecords();
327                vRecords = null;
328             }
329          }
330       }
331       return true;
332    }
333 
334    /**
335     * Performs the following check-out steps:
336     * - Update the status in the database to either checked-out (upon success) or checked-in
337     *   (upon failure)
338     * - If the checked out location is not in server, deletes the temporary files created
339     * - If confirmation email is requested, send it
340     */
341    protected void doCheckOutSecondStep()
342    {
343       // If the first step was successful, then we attempt to set the database status to either
344       // checked-out or checked-in. A failure in the first step means that the database status
345       // couldn't be changed to in-progress, in which case, it doesn't make sense to change the
346       // status at this point.
347       if ( bFirstStepSuccessful )
348          setStatusFromInProgress();
349 
350       // If the checked-out location is not in the server, we should remove the files from its
351       // temporary location
352       if ( !bIsLocationInServer )
353          deleteTemporaryDir();
354 
355       // Finally check if an email confirmation had been requested
356       ActionData confirmData = (ActionData) data.getObject( ActionPropertiesI.CONFIRMATION_DATA );
357       if ( confirmData != null )
358       {
359          if ( vRecords.size() > 0 )
360          {
361             StringBuffer sbBody = new StringBuffer( confirmData.getString( ActionPropertiesI.EMAIL_MESSAGE ) + "\r\n" );
362             AssetRecordData record;
363             String sRecordLocation;
364             for ( int i = 0; i < vRecords.size(); i++ )
365             {
366                record = (AssetRecordData) vRecords.elementAt(i);
367                sRecordLocation = record.getString( ActionPropertiesI.URL_STRING );
368                if ( sRecordLocation == null )
369                   sRecordLocation = record.getDestinationFilePath();
370                sbBody.append( "   " + sRecordLocation );
371             }
372 
373             confirmData.setString( ActionPropertiesI.EMAIL_MESSAGE, sbBody.toString() );
374          }
375          else
376          {
377             // 7047=%%1 failed. See daily log file for details.
378             confirmData.setString( ActionPropertiesI.EMAIL_MESSAGE, Resources.get( 7047, sThisService ) );
379          }
380 
381          try{ serviceBroker.invokeImmediately( confirmData ); }
382          catch ( SrvcNotAvailException snae ) {}
383       }
384    }
385 
386    /**
387     * Set the status in the database to in-progress
388     */
389    private boolean setStatusToInProgress()
390    {
391       CheckOutCheckInGateway gateway = new CheckOutCheckInGateway();
392       try
393       {
394          gateway.connect();
395          long[] laBadAssets = gateway.updateToInProcessCheckOut( data );
396          if ( laBadAssets != null && laBadAssets.length > 0 )
397          {
398             // if all records failed, return false
399             if ( laBadAssets.length == vRecords.size() )
400             {
401                vBadRecords = vRecords;
402                vRecords = null;
403                return false;
404             }
405             else
406             {
407                // remove the records that could not be updated
408                AssetRecordData record;
409                for ( int i = 0; i < laBadAssets.length; i++ )
410                {
411                   for ( Iterator it = vRecords.iterator(); it.hasNext(); )
412                   {
413                      record = (AssetRecordData) it.next();
414                      if ( record.getRecordId() == laBadAssets[i] )
415                      {
416                         vBadRecords.addElement( record );
417                         it.remove();
418                      }
419                   }
420                }
421             }
422          }
423          return true;
424       }
425       catch ( TransactionFailedException tfe )
426       {
427          // 7046=Unable to change database status for this transaction.
428          new FlexError( FlexError.CRITICAL, sThisService, IDENTIFIER, 7046 );
429          return false;
430       }
431       finally
432       {
433          gateway.dispose();
434       }
435    }
436 
437    /**
438     * Change the database status from in-progress to:
439     * - checked-out for successful assets
440     * - checked-in for failed assets
441     */
442    private boolean setStatusFromInProgress()
443    {
444       CheckOutCheckInGateway gateway = new CheckOutCheckInGateway();
445       try
446       {
447          gateway.connect();
448          // Change status to checked-in for bad records
449          if ( vBadRecords.size() > 0 )
450          {
451             ActionData cloneData = (ActionData) data.clone();
452             cloneData.setRecords( vBadRecords );
453             long[] laBadAssets = gateway.rollbackInProcessCheckOut( cloneData );
454             if ( laBadAssets != null && laBadAssets.length > 0 )
455             {
456                // 7045=Failed to rollback status to checked-in for the following assets:
457                StringBuffer sbMessage = new StringBuffer( Resources.get( 7045 ) + "\r\n   ");
458                for ( int i = 0; i < laBadAssets.length; i++ )
459                   sbMessage.append( laBadAssets[i] + " " );
460                new FlexError( FlexError.CRITICAL, sThisService, IDENTIFIER, sbMessage.toString() );
461             }
462          }
463 
464          // Change status to checked-out for good records
465          if ( vRecords.size() > 0 )
466             gateway.checkOutAssets( data );
467 
468          return true;
469       }
470       catch ( TransactionFailedException tfe )
471       {
472          // 7046=Unable to change database status for this transaction.
473          new FlexError( FlexError.CRITICAL, sThisService, IDENTIFIER, 7046 );
474          return false;
475       }
476       finally
477       {
478          gateway.dispose();
479       }
480    }
481 
482    /**
483     * Deletes the temporary directory used to store the files
484     */
485    private void deleteTemporaryDir()
486    {
487       // Set the temporary directory to {FlexDBServer temp dir}/{trans_id}/
488       String sTempDir = Settings.getString( Settings.APP_SERVER_TMP_DIR );
489       if ( !sTempDir.endsWith( fileSeparator ) )
490          sTempDir += fileSeparator;
491       sTempDir += data.getTransId();
492 
493       ActionData deleteData = new ActionData();
494       deleteData.setServiceName( ServicesI.DELETE_SERVICE );
495       deleteData.setString( ActionPropertiesI.LOCATION, sTempDir );
496 
497       // Execute Delete Service
498       try { serviceBroker.invokeImmediately( deleteData ); }
499       catch ( SrvcNotAvailException snae ) {}
500    }
501 
502    /**
503    * create a Hashtable containing a list of objects to be sent to the service manager.
504    * key = server name; value = ServiceManagerData
505    */
506    private Hashtable createSubSets()
507    {
508       // Retrieve the vector of AssetRecordData and parse thru it; for each server name found
509       // create a ActionData for each new server and add the respective elements to it.
510 
511       Hashtable htSubSets = new Hashtable();
512       ActionData dataSubSet;
513       AssetRecordData currRecord;
514       String sServerName;
515 
516       if ( vRecords != null)
517       {
518          for ( int i = 0; i < vRecords.size(); i++ )
519          {
520             currRecord = (AssetRecordData)vRecords.elementAt(i);
521             sServerName = currRecord.getServer();
522 
523             if ( sServerName != null )
524             {
525                dataSubSet = (ActionData) htSubSets.get( sServerName );
526                if ( dataSubSet == null )
527                {
528                   dataSubSet = (ActionData) data.clone();
529                   dataSubSet.addRecord( currRecord );
530                   htSubSets.put( sServerName, dataSubSet );
531                }
532                else
533                   dataSubSet.addRecord( currRecord );
534             }
535          }
536       }
537       return htSubSets;
538    }
539 
540    /**
541    * update the ImportData after the set of services are executed for
542    * a ImportData sub set.
543    **/
544    private synchronized void notify( ActionResult result )
545    {
546       Vector vBadRecs = null;
547       if ( result == null )
548          vBadRecs = data.getRecords();
549       else if ( result.isSuccess() == false )
550          vBadRecs = result.getBadRecords();
551 
552       if ( vBadRecs != null && vBadRecs.size() > 0 )
553       {
554          AssetRecordData record;
555          for (int i = 0; i < vBadRecs.size(); i++ )
556          {
557             // Set records to bad and remove the Vector of good records
558             record = (AssetRecordData)vBadRecs.elementAt(i);
559             vBadRecords.addElement( record );
560             vRecords.removeElement( record );
561          }
562       }
563 
564       nManagerRunning--;
565       this.notifyAll();
566    }
567 
568    /**
569     * Called when a thread task is about to begin.
570     * @param consumer the instance that this task will run against.
571     * @param obj      the user defined parameter for this task.
572     */
573    public void threadTaskStart ( ThreadConsumerI consumer, Object obj ) {}
574 
575    /**
576     * Called when a thread task has just finished.
577     * @param consumer the instance that this task will run against.
578     * @param obj      the user defined parameter for this task.
579     */
580    public void threadTaskEnd ( ThreadConsumerI consumer, Object obj )
581    {
582       notify( ((CheckOutServiceManager)consumer).getResult() );
583    }
584 } // CheckOutService