1    package com.instantbank.component.lettersjobmdb.ejb;
2    
3    import java.io.FileOutputStream;
4    import java.io.ByteArrayOutputStream;
5    import java.io.File;
6    
7    import java.util.Locale;
8    import java.util.Date;
9    import java.util.ArrayList;
10   import java.util.zip.ZipOutputStream;
11   import java.util.zip.ZipEntry;
12   import java.util.Set;
13   import java.text.SimpleDateFormat;
14   import java.text.NumberFormat;
15   import java.text.DecimalFormat;
16   
17   import javax.ejb.MessageDrivenBean;
18   import javax.ejb.EJBException;
19   import javax.ejb.MessageDrivenContext;
20   import javax.jms.Message;
21   import javax.jms.MessageListener;
22   import javax.jms.MapMessage;
23   import javax.jms.JMSException;
24   
25   import com.instantbank.common.utilcomponents.Debug;
26   import com.instantbank.common.utilcomponents.CommonUtil;
27   import com.instantbank.common.utilcomponents.JNDINames;
28   import com.instantbank.common.utilcomponents.LetterTemplateGlobals;
29   import com.instantbank.common.utilcomponents.UtilOnJdk;
30   
31   import com.instantbank.component.lettertemplate.ejb.LetterTemplate;
32   import com.instantbank.component.lettertemplate.ejb.LetterTemplateHome;
33   import com.instantbank.component.lettertemplate.util.Template;
34   import com.instantbank.component.lettertemplate.util.LettersJobLogBean;
35   
36   import com.instantbank.component.job.ejb.JobHome;
37   import com.instantbank.component.job.ejb.Job;
38   import com.instantbank.component.job.model.JobModel;
39   
40   import com.instantbank.component.lettersjobmdb.util.MessageSender;
41   
42   import com.instantbank.lettertemplate.editor.web.TemplateTransformer;
43   
44   import com.instantbank.common.utilcomponents.DAOUtil;
45   import com.instantbank.common.utilcomponents.SqlUtils;
46   
47   import javax.naming.InitialContext;
48   import javax.naming.NamingException;
49   
50   // for accessing DB:
51   import java.sql.Connection;
52   import javax.sql.DataSource;
53   
54   /**
55    * Message Driven bean that process letter jobs. A message is a "reference"
56    * to a letter job.
57    *
58    * @author InstantBank (Rodrigo Lopez)
59    * @created October 2002
60    */
61   public class LettersJobMessageEJB
62       implements MessageDrivenBean, MessageListener {
63   
64     /**
65      * Code of the template bound to the letter job.
66      */
67     private long templateCode;
68   
69     /**
70      * Name of the job
71      */
72     private String jobName;
73   
74     /**
75      * The ubiquitous debugger object.
76      */
77     private Debug debug;
78   
79   
80     /**
81      * This method has void body for Mesage Driven Beans.
82      *
83      * @throws EJBException Description of the Exception
84      */
85     public void ejbRemove() throws EJBException { }
86   
87   
88     /**
89      * Nothing interesting here.
90      *
91      * @param mdc The new messageDrivenContext value
92      * @throws EJBException Description of the Exception
93      */
94     public void setMessageDrivenContext(MessageDrivenContext mdc)
95        throws EJBException { }
96   
97   
98     /**
99      * Initializes the debugger.
100     */
101    public void ejbCreate() {
102      debug = new Debug();
103      debug.setDebugginOn(true);
104      debug.setPreMessage("** LetterJobMessageEJB: ");
105  
106    }
107  
108  
109    /**
110     * Process one letter job. Only MapMessages are processed and are
111     * expected to contain:
112     * <ul>
113     *   <li>CompanyId: The company Id (as a String).
114     *   <li>UserId: The user Id (as Long).
115     *   <li>JobKey: Job Id as a Long object.
116     *   <li>WorkDir: The full path of a working directory.
117     *   <li>Rtf2foConfDir : Full path of the configuration directory for rtf2fo.
118     *   <li>ContextProviderUrl: Context provider url as expected by the
119     *       WebLogic Server.
120     *   <li>InitContextFactory: Name of the Initial Context Factory class.
121     * </ul>
122     * Log information is recorded in the LETT_JOB_LOG table.
123     *
124     * @param jobMsg Description of the Parameter
125     */
126  
127    public void onMessage(Message jobMsg) {
128      MapMessage msg = null;
129  
130      LetterTemplateHome lethome;
131      LetterTemplate letejb;
132  
133      String companyId;
134      Long userId;
135      Long jobKey;
136      long jobId;
137      long locationId1;
138      long locationId2;
139  
140      Template template;
141  
142      long zipSizeLimit;  // = LetterTemplateGlobals.ZIP_LETTERS_FILE_MAX_SIZE;
143      String workDir;
144      //String rtf2foConfDir;
145      String sql;
146      String ctxProviderUrl;
147      String initContextFactory;
148  
149      try {
150  
151        //Gets the message information
152        if(jobMsg instanceof MapMessage) {
153          msg = (MapMessage)jobMsg;
154          companyId = msg.getString("CompanyId");
155          userId = (Long)msg.getObject("UserId");
156          workDir = msg.getString("WorkDir");
157          //rtf2foConfDir = msg.getString("Rtf2foConfDir");
158          jobKey = (Long)msg.getObject("JobKey");
159          debug.println("jobKey=" + jobKey);
160          ctxProviderUrl = msg.getString("ContextProviderUrl");
161          initContextFactory = msg.getString("InitContextFactory");
162          try {
163            long limitInKbytes = Long.parseLong(msg.getString("MaxZipSize"));
164            zipSizeLimit = limitInKbytes * 1024;
165          }
166          catch(NumberFormatException ex) {
167            zipSizeLimit = LetterTemplateGlobals.ZIP_LETTERS_FILE_MAX_SIZE;
168          }
169  
170          debug.println("CompanyId = " + companyId);
171          debug.println("UserId = " + userId);
172          debug.println("workDir = " + workDir);
173          //debug.println("rtfDir = "+       rtf2foConfDir);
174          debug.println("ContextProviderUrl = " + ctxProviderUrl);
175          debug.println("MaxZipSize =" + zipSizeLimit);
176        }
177        else {
178          throw new Exception("Only MapMessages are processed");
179        }
180  
181        //Get the jobModel information through the Job entity bean
182        InitialContext ctx = new InitialContext();
183        JobHome jobHome = (JobHome)ctx.lookup(JNDINames.JOB_EJBHOME);
184  
185        Job job = jobHome.findByPrimaryKey(jobKey);
186        JobModel jobModel = job.getState();
187        jobId = jobModel.getJobId().longValue();
188        locationId1 = jobModel.getFtpPrimaryId();
189        locationId2 = jobModel.getFtpAlternateId();
190  
191        //Zip files will not be ftped if there is no valid location.
192        boolean sendFtp = true;
193        if(locationId1 == LetterTemplateGlobals.UNDEF &&
194          locationId2 == LetterTemplateGlobals.UNDEF) {
195          sendFtp = false;
196        }
197        else if(locationId1 != LetterTemplateGlobals.UNDEF &&
198          locationId2 == LetterTemplateGlobals.UNDEF) {
199          locationId2 = locationId1;
200        }
201        else if(locationId1 == LetterTemplateGlobals.UNDEF &&
202          locationId2 != LetterTemplateGlobals.UNDEF) {
203          locationId1 = locationId2;
204        }
205  
206        String jobName = UtilOnJdk.squeeze(jobModel.getName(), '_');
207        templateCode = jobModel.getTemplateCode();
208  
209        // Contact the LetterTemplateHome through JNDI.
210  
211        lethome = (LetterTemplateHome)ctx.lookup(JNDINames.LETTERTEMPLATE_EJBHOME);
212        letejb = lethome.create(companyId, userId);
213  
214        template = letejb.loadTemplate(templateCode);
215  
216        java.sql.Date sysDate = letejb.loadSysDate();
217        Date templDate = letejb.loadTemplateDate(templateCode);
218  
219        //if job outdated => job failure.
220        if(jobModel.getVersion().before(templDate)) {
221          letejb.storeLetterJobLog(jobId, sysDate,
222            LetterTemplateGlobals.LETTER_JOB_OUTDATED, 0, "", "");
223          debug.println("Job outdated");
224          return;
225        }
226  
227        String foTemplate = template.toFoTemplate();
228        if(foTemplate == null) {
229          throw new Exception("fo-template null");
230        }
231  
232        //Builds the transformer for producing actual letters.
233        ArrayList[] variables = template.setOfVariables();
234        ArrayList[] images = template.getAllImages();
235  
236        String foFileName = jobName + sysDate.getTime();
237  
238        TemplateTransformer transf =
239          new TemplateTransformer(workDir, foTemplate, images,
240          variables, foFileName, letejb);
241  
242        Set inconsistentVars = transf.offsetConsistency();
243        if(!inconsistentVars.isEmpty()) {
244          letejb.storeLetterJobLog(jobId, sysDate,
245            LetterTemplateGlobals.LETTER_JOB_OFFSET_INCONSISTENCY, 0, "", "");
246          debug.println("Job with date offset inconsistencies");
247          return;
248        }
249  
250        debug.println("Transformer initiated");
251        String ext;
252        if(template.getPrintType() == Template.TYPEWRITTER) {
253          transf.setRenderMode(TemplateTransformer.RENDER_TXT);
254          ext = ".txt";
255        }
256        else {
257          ext = ".pdf";
258        }
259  
260        //Executes the sql statement
261        sql = jobModel.getSqlText();
262  
263        ArrayList[] sqlExec =
264          letejb.executeSQL
265          (sql, TemplateTransformer.varCodesFromSet(variables),
266          jobModel.getJobORDERtable());
267  
268        //Logs basic job information
269        String loggeableResult = serializeSqlExec(sqlExec);
270        //sysDate = letejb.loadSysDate(); //Redundant now. Will be important for
271        //job outdate test.
272        LettersJobLogBean jobLog =
273          letejb.storeLetterJobLog(
274          jobId, sysDate,
275          LetterTemplateGlobals.LETTER_JOB_OK,
276          sqlExec[0].size(), loggeableResult, foTemplate);
277  
278        letejb.updateJobExecDate(jobId, sysDate);
279  
280        ArrayList varList = sqlExec[1];
281        debug.println("varlist extracted");
282  
283        if(varList.size() == 0) {
284          debug.println("There were no letters to be generated");
285  
286          //Log ftp information.
287          letejb.storeLetterFtpJobLog(
288            jobLog.getLogId(), LetterTemplateGlobals.STR_NO_INFO, true, 0,
289            LetterTemplateGlobals.STR_NO_INFO,
290            LetterTemplateGlobals.STR_NO_INFO,
291            "No Zip file to be ftp'ed");
292  
293          return;
294        }
295  
296        String companyPath = letejb.loadCompanyWorkDir();
297        File companyDir =
298          new File(companyPath, LetterTemplateGlobals.LETTER_JOBS_DIR);
299  
300        ZipOutputStream zout = null;
301        ByteArrayOutputStream zipbytes = null;
302        String zipname = null;
303        ByteArrayOutputStream letter = null;
304  
305        long zipOutputSize = 0;
306        int numLetter = 0;  // Do not change to 1. The next do loop will fail!!
307        boolean generateLetter = true;
308        SequencedFileName sequencer = new SequencedFileName(jobName, sysDate, 1);
309  
310        //generate the zip files
311        //ArrayList zipFiles = new ArrayList();
312        String generatedZip = null;
313  
314        MessageSender ftpSender =
315          new MessageSender(
316          ctxProviderUrl,
317          initContextFactory, LetterTemplateGlobals.LETTERS_FTP_JMS_QUEUE);
318  
319        long jobLogId = jobLog.getLogId();
320  
321        //////////////////////////////////////////////////////////////
322        // Get the field index for AGRM_CODE and find the actual value
323        //////////////////////////////////////////////////////////////
324  
325        String sAgrmCode = "";
326  
327        DataSource datasource = DAOUtil.getDataSource(JNDINames.INSTANTBANKDB);
328  
329        Connection dbConnection = DAOUtil.getDBConnection(datasource);
330  
331        long lAgrmCodeIdx = SqlUtils.getAgreementCodeFieldIndex(dbConnection);
332        Long lVarValue;
333        Long lAgrmCode;
334        Long lAgrmID;
335        long lActionCode = 0;
336        long lResultCode = 0;
337        long lTemplateCode = 0;
338        Long lCompanyID;
339        boolean bAgrmCodeExists = false;
340  
341        for(int i = 0; i < variables.length; i++) {
342          lVarValue = (Long)variables[i].get(0);
343          if(lVarValue.longValue() == lAgrmCodeIdx) {
344            bAgrmCodeExists = true;
345            break;
346          }
347        }
348        /////////////////////////////////////////////////////////////////
349  
350        //Zip files are created respecting some size limits
351        do {
352  
353          if(zipOutputSize == 0) {  //A new zip must be created
354            zipbytes = new ByteArrayOutputStream();
355            zipname = sequencer.nextName();
356            zout = new ZipOutputStream(zipbytes);
357          }
358  
359          if(generateLetter) {
360            letter = transf.transform((String[])varList.get(numLetter));
361            //------------------- added by acs---------------------------//
362  
363            if(bAgrmCodeExists) {
364  
365              String[] sAgrmCodes = (String[])varList.get(numLetter);
366              lAgrmCode = Long.valueOf(sAgrmCodes[0]);
367              lCompanyID = Long.valueOf(jobModel.getCompanyId());
368  
369              lAgrmID = SqlUtils.getAgreementId(dbConnection, lCompanyID.longValue(), lAgrmCode.longValue());
370  
371              java.sql.Date procDate = (java.sql.Date)com.instantbank.collections.util.DateUtils.processDate(lCompanyID.longValue());
372  
373              lActionCode = SqlUtils.getActionCodeFieldIndex(dbConnection, "SY", lCompanyID.longValue());
374              lResultCode = SqlUtils.getResultCodeFieldIndex(dbConnection, "SL", lCompanyID.longValue());
375              lTemplateCode = SqlUtils.getLetterTemplateIndex(dbConnection, String.valueOf(templateCode), lCompanyID.longValue());
376  
377              letejb.insertLetterARHistory(dbConnection, lCompanyID.longValue(), lAgrmID.longValue(), procDate, lTemplateCode, templateCode, lActionCode, lResultCode);
378            }
379            //---------------- end of added by acs -----------------------------
380            if(letter == null) {
381              debug.println("null letter");
382              throw new Exception("Null letter");
383            }
384          }
385  
386          if(zipOutputSize + letter.size() > zipSizeLimit) {
387            //current zip must be stored in a file
388            if(zipOutputSize == 0) {  //letter is bigger than ziplimit
389              addLetter(zout, letter, numLetter++, ext);
390              generatedZip = writeZipFile(zout, companyDir, zipname, zipbytes);
391              generateLetter = true;
392            }
393            else {
394              generatedZip = writeZipFile(zout, companyDir, zipname, zipbytes);
395              zipOutputSize = 0;
396              generateLetter = false;
397            }
398          }
399          else {  //letter is added but zip is not always stored
400            addLetter(zout, letter, numLetter++, ext);
401            if(numLetter == varList.size()) {  //it was the last letter
402              generatedZip = writeZipFile(zout, companyDir, zipname, zipbytes);
403            }
404            zipOutputSize = zipbytes.size();
405            generateLetter = true;
406          }
407  
408          //Sends a ftp message if a zip file was generated and if
409          //zip files must be ftped.
410          if(generatedZip != null) {
411  
412            if(sendFtp) {
413              Message ftpMsg =
414                ftpSender.buildFtpMessage(companyId, userId,
415                generatedZip, jobLogId, locationId1, locationId2);
416  
417              ftpSender.send(ftpMsg);
418              debug.println("Message sent to ftp queue");
419            }
420            else {
421              debug.println("Message NOT sent to ftp queue");
422              letejb.storeLetterFtpJobLog
423                (jobLogId, generatedZip, true, 0, null, null, null);
424            }
425  
426            generatedZip = null;
427          }
428  
429        }while (numLetter < varList.size());
430  
431        //clean temporary files used by the transformer
432        ftpSender.release();
433        transf.cleanfiles();
434  
435      }
436      catch(NamingException nex) {
437        debug.println("Naming exception");
438        debug.println(CommonUtil.stackTraceToString(nex));
439      }
440      catch(JMSException jex) {
441        debug.println("JMS exception");
442        debug.println(CommonUtil.stackTraceToString(jex));
443      }
444      catch(Exception ex) {
445        debug.println("Unexpected exception");
446        debug.println(CommonUtil.stackTraceToString(ex));
447      }
448      catch(Throwable tex) {
449        debug.println(CommonUtil.stackTraceToString(tex));
450      }
451    }
452  
453  
454    /**
455     * Adds a letter as a new entry in a zip structure.
456     *
457     * @param zout The zip structure (in main memory).
458     * @param letter The letter to be added (in main memory)
459     * @param i A sequence number bound to the letter.
460     * @param ext The extension appended to the zip entry name.
461     * @throws Exception Description of the Exception
462     */
463    private void addLetter(ZipOutputStream zout, ByteArrayOutputStream letter,
464                           int i, String ext)
465       throws Exception {
466      zout.putNextEntry(new ZipEntry("Letter-" + i + ext));
467      letter.writeTo(zout);
468      zout.closeEntry();
469    }
470  
471  
472    /**
473     * Writes a zip structure to a file.
474     *
475     * @param zout OutputStream through wich the zip structure has been created.
476     * @param workDir Directory where the file will be created.
477     * @param zipname Name of the file to be created.
478     * @param zipbytes The zip structure to be written.
479     * @return The full path of the written file.
480     * @throws Exception Description of the Exception
481     */
482    private String writeZipFile(ZipOutputStream zout, File workDir, String zipname,
483                                ByteArrayOutputStream zipbytes)
484       throws Exception {
485      zout.close();
486      File f = new File(workDir, zipname);
487      String fullPath = f.getAbsolutePath();
488  
489      FileOutputStream fout = new FileOutputStream(f);
490      zipbytes.writeTo(fout);
491      fout.close();
492  
493      return fullPath;
494    }
495  
496  
497    /**
498     * Transforms the result of execution of the sql statement bound
499     * to a job into a String of row values separated by a non printable
500     * ROW_SEPARATOR character and values in a row separated by a
501     * VALUE_SEPARATOR non printable character.
502     *
503     * @param execSet The result as calculated by LetterTemplate.executeSQL().
504     * @return The custom "serialization" of execSet.
505     */
506    private String serializeSqlExec(ArrayList[] execSet) {
507      if(execSet[0].size() == 0) {
508        return "";
509      }
510  
511      ArrayList loans = execSet[0];
512      ArrayList vars = execSet[1];
513  
514      StringBuffer sb = new StringBuffer();
515  
516      for(int i = 0; i < loans.size(); i++) {
517        sb.append((String)loans.get(i));
518        String[] row = (String[])vars.get(i);
519        if(row.length > 0) {
520          sb.append(UtilOnJdk.VALUE_SEPARATOR);
521        }
522        for(int k = 0; k < row.length; k++) {
523          sb.append(row[k]);
524          if(k + 1 < row.length) {
525            sb.append(UtilOnJdk.VALUE_SEPARATOR);
526          }
527        }
528  
529        if(i + 1 < loans.size()) {
530          sb.append(UtilOnJdk.ROW_SEPARATOR);
531        }
532      }
533  
534      return sb.toString();
535    }
536  
537  
538    /**
539     * Auxiliary class that generates names of files with a sequencing
540     * criterium.
541     *
542     * @author InstantBank (Rodrigo Lopez)
543     */
544    class SequencedFileName {
545      /**
546       * Sequence number
547       */
548      private int seqnum = 0;
549  
550      /**
551       * Temporay buffer for efficient name building.
552       */
553      private char[] buffer;
554  
555      /**
556       * Index inside the {@link #buffer} where the sequencing figure is placed.
557       */
558      private int seqIndex;
559  
560      /**
561       * Formatter for the sequence number.
562       */
563      private DecimalFormat df;
564  
565  
566      /**
567       * SequencedFileName constructor.
568       *
569       * @param jobName String seed for names.
570       * @param date Date seed for names.
571       * @param seqNum Starting value for the sequence number.
572       */
573      public SequencedFileName(String jobName, Date date, int seqNum) {
574        StringBuffer sb = new StringBuffer();
575        SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd");
576        seqnum = seqNum - 1;
577  
578        sb.append(jobName);
579        sb.append('-');
580        sb.append(date.getTime() % 100000000);
581        sb.append('-');
582        seqIndex = sb.length();
583        sb.append("  -");
584        sb.append(sdf.format(date));
585        sb.append(".zip");
586        buffer = new char[sb.length()];
587        sb.getChars(0, sb.length(), buffer, 0);
588        df = (DecimalFormat)NumberFormat.getInstance(Locale.US);
589        df.applyPattern("00");
590      }
591  
592  
593      /**
594       * Produces the next name. The sequencing number is incremented each
595       * time this service is invoked.
596       *
597       * @return Description of the Return Value
598       */
599      public String nextName() {
600        seqnum++;
601        String seq = df.format(seqnum);
602        buffer[seqIndex + 1] = seq.charAt(seq.length() - 1);
603        buffer[seqIndex] = seq.charAt(seq.length() - 2);
604        return new String(buffer);
605      }
606    }
607  }
608  
609