View Javadoc

1   /***
2    * Copyright 2007 ATG DUST Project
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * 
7    * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8    * 
9    * Unless required by applicable law or agreed to in writing, software 
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and limitations under the License.
13   */
14  
15  package atg.junit.nucleus;
16  
17  import java.io.BufferedReader;
18  import java.io.ByteArrayOutputStream;
19  import java.io.File;
20  import java.io.FileOutputStream;
21  import java.io.IOException;
22  import java.io.InputStreamReader;
23  import java.io.PrintStream;
24  import java.lang.reflect.Method;
25  import java.net.InetAddress;
26  import java.net.MalformedURLException;
27  import java.net.URL;
28  import java.net.UnknownHostException;
29  import java.util.Iterator;
30  import java.util.LinkedList;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Properties;
34  import java.util.StringTokenizer;
35  import java.util.jar.Manifest;
36  
37  import javax.activation.DataHandler;
38  import javax.activation.FileDataSource;
39  import javax.mail.BodyPart;
40  import javax.mail.Message;
41  import javax.mail.Multipart;
42  import javax.mail.internet.MimeBodyPart;
43  import javax.mail.internet.MimeMultipart;
44  
45  import org.apache.log4j.Logger;
46  import org.w3c.dom.Node;
47  
48  import atg.applauncher.AppLauncher;
49  import atg.applauncher.AppModule;
50  import atg.nucleus.DynamoEnv;
51  import atg.nucleus.Nucleus;
52  import atg.service.dynamo.LicenseImpl;
53  import atg.service.email.ContentPart;
54  import atg.service.email.EmailEvent;
55  import atg.service.email.MimeMessageUtils;
56  import atg.service.email.SMTPEmailSender;
57  import atg.servlet.ServletUtil;
58  
59  /***
60   * This class is used to hold useful utilty methods people may
61   * need when running tests.
62   */
63  public class TestUtils
64    extends atg.nucleus.GenericService
65  {
66    
67    private static Logger log = Logger.getLogger(TestUtils.class);
68    // names of app servers types that may be specified by the
69    // 'atg.dynamo.appserver' system property
70    // Dynamo currently does not distinguish between generic
71    // Tomcat and JBoss, everything is just referred to as 'tomcat' 
72    public static final String APP_SERVER_DAS = "das";
73    public static final String APP_SERVER_BEA = "weblogic";
74    public static final String APP_SERVER_IBM = "websphere";
75    public static final String APP_SERVER_TOMCAT = "tomcat";
76  	
77    // names of various vendors that ATG works with
78    public static final String VENDOR_ATG = "ATG";
79    public static final String VENDOR_BEA = "BEA";
80    public static final String VENDOR_IBM = "IBM";
81    public static final String VENDOR_JBOSS = "JBOSS";
82  	
83    // the variable that points to the installation directory for dynamo
84    private static final String ROOT_VAR = "atg.dynamo.root";
85    private static final String HOME_VAR = "atg.dynamo.home";
86    private static final String ATG_J2EESERVER_ROOT = "atg.j2eeserver.root";
87  
88    // these are used to lookup some system settings from the VM
89    private static final String JAVA_VAR = "java.vm.info";
90    private static final String JAVA_VERSION = "java.vm.version";
91    private static final String JAVA_BUILD_VERSION = "java.version";
92    private static final String COMPILER_VAR = "java.compiler";
93    private static final String OS_VAR = "os.name";
94    private static final String OS_VERSION_VAR = "os.version";
95  
96    // the system variable that returns the name of the app server being used
97    private static final String APP_SERVER = "atg.dynamo.appserver";
98      
99    // the Configuration component used by Dynamo
100   private static final String CONFIGURATION_COMPONENT = "/atg/dynamo/Configuration";
101 
102   // mailhost used to send email
103   private static final String MAILHOST = "mailsvr.atg.com";
104     
105   // value returned by several methods, as noted in javadoc, if a
106   // piece of information can not be definitively determined.  in
107   // particular, used when reporting about product build and version
108   // information
109   public static final String UNKNOWN_INFO = "unknown";
110     
111   /*** property to track the DUST version being used.  utilized by
112    *  ATGXMLFileTestResultReported so we can tag XML result files for
113    *  compatibility validation when passed to the XML file logger  */
114   public static int DUST_VERSION = 1;
115 
116   /*** specifies the DUST version being used.  utilized by
117    *  ATGXMLFileTestResultReporter so XML result files can be tagged
118    *  for compatibility validation when passed to the XML file
119    *  logger */
120   public void setDustVersion( int pVersion ) { DUST_VERSION = pVersion; }
121 
122   /*** returns the DUST version being used.  utilized by
123    *  ATGXMLFileTestResultReporter so XML result files can be tagged
124    *  for compatibility validation when passed to the XML file
125    *  logger */
126   public int getDustVersion() { return DUST_VERSION; }
127 	    
128   /*** property to track the DUST user.  utilized when results are
129    *  logged to the database to correlate the result with a user
130    *  account in the test management system. */
131   public static String DUST_USERNAME = System.getProperty( "user.name" );
132 
133   /*** specifies the DUST user.  utilized when results are logged to
134    *  the database to correlate the result with a user account in the
135    *  test management system. */
136   public void setDustUsername( String pUsername ) { DUST_USERNAME = pUsername; }
137   /*** returns the DUST user name.  utilized when results are logged to
138    *  the database to correlate the result with a user account in the
139    *  test management system. */
140   public String getDustUsername() { 
141     if ( DUST_USERNAME == null || DUST_USERNAME.trim().length() == 0 )
142       return System.getProperty( "user.name" );
143     else return DUST_USERNAME; 
144   }
145 	
146   /*** property to track which testrun a result is part of.  utilized
147    *  by TSM to correlate a specifid result with the testrun used to
148    *  install and configure the test Dynamo. */
149   public static String TSM_TESTRUN = null;
150 
151   /*** Specifies the TSM testrun this result is part of.  utilized by
152    *  TSM to correlate a specifid result with the testrun used to
153    *  install and configure the test Dynamo. */	
154   public void setTsmTestrun( String pId ) { TSM_TESTRUN = pId; }
155 
156   /*** Returns the TSM testrun this result is part of.  utilized by TSM
157    *  to correlate a specifid result with the testrun used to install
158    *  and configure the test Dynamo. */	
159   public String getTsmTestrun() { return TSM_TESTRUN; }
160 		
161   /*** property to track the p4 sync time for tests.  utilized by TSM
162    *  to inform end-users of time at which machine was last synced.
163    *  must be specified by test setup before test is run. */
164   public static String P4SYNCTIME = null;
165 
166   /*** property to track the p4 sync time for tests.  utilized by TSM
167    *  to inform end-users of time at which machine was last synced.
168    *  must be specified by test setup before test is run. */
169   public void setP4Synctime( String pTime ) { P4SYNCTIME = pTime; }
170 
171   /*** property to track the p4 sync time for tests.  utilized by TSM
172    *  to inform end-users of time at which machine was last synced.
173    *  must be specified by test setup before test is run. */
174   public String setP4Synctime() { return P4SYNCTIME; }
175 
176   /*** Returns the directory in which Dynamo was installed.  If the
177    *  installation directory was not specified during the DUST
178    *  installation, returns null.  Should <b>only</b> be used by
179    *  System tests. */
180   public static File DYNAMO_INSTALL_DIR = null;
181 
182   /*** Returns the directory in which Dynamo was installed.  If the
183    * installation directory can not be successfully determined returns
184    * null.
185    * <br><b>NOTE:</b> There is no reliable System property (or
186    * other inherent mechanism) to determine the Dynamo installation
187    * directory when running as BigEar, so this value is set during the
188    * DUST build process.  The SystemTests.base build step writes the
189    * file
190    * SystemTests/base/config/atg/junit/nucleus/TestUtils.properties
191    * with the proper value.  Consequently, this method should only be
192    * used by System tests, not Unit tests.
193    */
194   public File getDynamoInstallDir() { return DYNAMO_INSTALL_DIR; }
195 
196   /*** Specifies the directory in which Dynamo was installed. */
197   public void setDynamoInstallDir( File pDir ) { DYNAMO_INSTALL_DIR = pDir; }
198 	
199   /*** Returns the root directory for this Dynamo.  If the root
200    * directory can not be successfully located returns null.  Operates
201    * according to this logic:
202    * <ul>
203    *   <li>If "home" Dynamo module can be found, return parent directory
204    *       containing that module.  (This should resolve when running 'BigEar')
205    *   <li>Otherwise, return value of System property 'atg.dynamo.root'.
206    *       (this could still be null)
207    * </ul>
208    */
209   public static File getDynamoRootDir() {
210     File root = null;
211     try { root = getDynamoHomeDir().getParentFile(); }
212     catch (Throwable t) {}
213     if ( root == null ) {
214       try { root = new File( System.getProperty( ROOT_VAR ) ); }
215       catch (Throwable t) {}
216     }
217     return root;
218   }
219 	
220   /*** Returns Dynamo's "home" module installation directory.  If the
221    *  directory can not be successfully located returns null.
222    *  <br>Logic works like this:
223    *  <ul>
224    *    <li>If "home" Dynamo module can be found, return directory representing
225    *        that module.  This should resolve when running 'BigEar' and may
226    *        point to a subdirectory of the application server's directory used
227    *        to deploy ear files.  On DAS it should resolve to
228    *        <DYNAMO_INSTALL_DIR>/home.
229    *    <li>Otherwise, return value of System property 'atg.dynamo.home'.
230    *        (this could be null)
231    *  </ul>
232    */
233   public static File getDynamoHomeDir() {
234     File root = null;
235     try { root = getModuleResourceFile("home",".").getCanonicalFile(); }
236     catch (Throwable t) {}
237     if ( root == null ) {
238       try { root = new File( System.getProperty( HOME_VAR ) ); }
239       catch (Throwable t) {}
240     }
241 
242     return root;
243   }
244 
245   /*** returns the root install directory for the ATG J2EE Server, or null if
246    *  the ATG J2EE Server is not installed. */
247   public static File getAtgJ2eeServerInstallDir() {
248     try { return new File(System.getProperty( ATG_J2EESERVER_ROOT )); }
249     catch (Throwable t) { return null; }
250   }
251 
252 
253   /*** Returns the product name of the app server being used. For ATG
254    *  we currently assume it's called 'ATGDAS' if it's a separate
255    *  product, since there is no definitive way to figure out the
256    *  product name from MANIFEST files. For all other app servers it
257    *  returns the value of getAppServerType().
258    */
259   public static String getAppServerProductName() {
260     if ( getAppServerType().equals( APP_SERVER_DAS ) ) return getAtgJ2eeServerProductName();
261     else return getAppServerType();
262   }
263 	
264   /*** Returns the name of the ATG J2EE Server product this is installed, or
265    *  null if a separate ATG J2EE Server build is not installed. */
266   public static String getAtgJ2eeServerProductName() {
267     // TODO: Bug 78552 was opened to add a MANIFEST entry containing
268     // the product name so we don't have to hard code this to
269     // 'ATGDAS'.
270     if ( getAtgJ2eeServerInstallDir() != null ) return "ATGDAS";
271     else return null;
272   }	
273 	
274   /*** Returns the version number of the app server being used.  For
275    *  DAS this version comes from the J2EEServer MANIFEST file, or is
276    *  UNKNOWN_INFO if the MANIFEST can't be found. Such may be the
277    *  case if a person is using devtools to build their product. For
278    *  3PAS the version number is extracted from their configuration
279    *  file (typically some well known XML file).
280    */
281   public static String getAppServerVersion() {
282     String apptype = getAppServerType();
283     if ( apptype.equals( APP_SERVER_DAS ) ) {
284       return getAtgVersion( getAtgJ2eeServerModule() );
285     } else if ( apptype.equals( APP_SERVER_BEA ) ) {
286       return getBeaVersion();
287     } else if ( apptype.equals( APP_SERVER_IBM ) ) {
288       return getWasVersion();
289     } else if ( apptype.equals( APP_SERVER_TOMCAT ) ) {
290       return getJBossVersion();
291     } else {
292       return UNKNOWN_INFO;
293     }
294   }
295 	 
296   /*** Returns the build number of the app server being used.  For DAS
297    *  this version comes from the J2EEServer MANIFEST file, or is
298    *  UNKNOWN_INFO if the MANIFEST can't be found. Such may be the
299    *  case if a person is using devtools to build their product. For
300    *  3PAS the build number is always UNKNOWN_INFO.
301    */
302   public static String getAppServerBuildNumber() {
303     String apptype = getAppServerType();
304     if ( apptype.equals( APP_SERVER_DAS ) ) {
305       return getAtgBuildNumber( getAtgJ2eeServerModule() );
306     } else {
307       return UNKNOWN_INFO;
308     }
309   }	 
310 
311   /*** Returns the patch version of the app server being used.  For DAS
312    *  this version comes from the J2EEServer MANIFEST file, or is
313    *  UNKNOWN_INFO if the MANIFEST can't be found. Such may be the
314    *  case if a person is using devtools to build their product. For
315    *  3PAS the patch version is always UNKNOWN_INFO.
316    */
317   public static String getAppServerPatchVersion() {
318     String apptype = getAppServerType();
319     if ( apptype.equals( APP_SERVER_DAS ) ) {
320       return getAtgPatchVersion( getAtgJ2eeServerModule() );
321     } else {
322       return UNKNOWN_INFO;
323     }
324   }	
325 
326   /*** Returns the patch build number of the app server being used.
327    *  For DAS this version comes from the J2EEServer MANIFEST file, or
328    *  is UNKNOWN_INFO if the MANIFEST can't be found. Such may be the
329    *  case if a person is using devtools to build their product. For
330    *  3PAS the patch build number is always UNKNOWN_INFO.
331    */
332   public static String getAppServerPatchBuildNumber() {
333     String apptype = getAppServerType();
334     if ( apptype.equals( APP_SERVER_DAS ) ) {
335       return getAtgPatchBuildNumber( getAtgJ2eeServerModule() );
336     } else {
337       return UNKNOWN_INFO;
338     }
339   }	
340 	 
341   /*** Returns the vendor name of the App server manufacturer.  if a
342    *  vendor can not be determined it returns UNKNOWN_INFO.
343    */
344   public static String getAppServerVendor() {
345     String apptype = getAppServerType();
346     if ( apptype.equals( APP_SERVER_DAS ) ) {
347       return VENDOR_ATG;
348     } else if ( apptype.equals( APP_SERVER_BEA ) ) {
349       return VENDOR_BEA;
350     } else if ( apptype.equals( APP_SERVER_IBM ) ) {
351       return VENDOR_IBM;
352     } else if ( apptype.equals( APP_SERVER_TOMCAT ) ) {
353       return VENDOR_JBOSS;
354     } else {
355       return UNKNOWN_INFO;
356     }
357   }
358 
359   /*** Returns true if the Dynamo product is being used; false if only the ATG
360    *  J2EE Server product is running. 
361    **/
362   public static boolean isDynamoInstalled() 
363   {
364     try {
365       // if j2ee server is not installed then Dynamo must be...
366       if ( getAtgJ2eeServerInstallDir() == null ) return true;
367       // if the j2ee server root is the same as the dynamo root then
368       // we're running only the j2ee server
369       else return ( ! getAtgJ2eeServerInstallDir().getCanonicalFile().equals(getDynamoRootDir().getCanonicalFile() ) );
370     } catch ( IOException ioe ) {
371       // this should never happen, but if it does return false...
372       return false;
373     }
374   }
375 
376   /*** This method returns the name of the Dynamo product that is
377    *  installed.  Because there is no guaranteed way to determine the
378    *  installed product this method makes a best-guess.  If the
379    *  version is less than 5.5 then the method skips down from DCS, to
380    *  DPS, etc. until it finds a product that exists. If the version
381    *  5.5, 5.6, or 5.6.1 then this method just returns 'AppDAP' since
382    *  that is the only Dynamo product we have for those versions.
383    *  Likewise, if the version is NOT anything between 4 and 5.6, then
384    *  we return 'ATG' since that is the only version we have for
385    *  copper, etc.  If the method can't determine a version it returns
386    *  UNKNOWN_INFO
387    */
388   public static String getDynamoProductName() 
389   {
390     AppModule module = getAtgDynamoModule(); 
391     if ( module == null ) return UNKNOWN_INFO;       
392     String version = getAtgVersion(module); 
393             
394     if ( version == null ) {
395       return UNKNOWN_INFO;
396     } else if ( version.startsWith("5.5") || version.startsWith("5.6") ) {
397       // this is an AppDAP build of 5.5, 5.6, or 5.6.1
398       return "AppDAP";
399     } else if ( ! version.startsWith("4") && ! version.startsWith("5.0") && ! version.startsWith("5.1") ) {
400       // assume this is an ATG build from version 6.x
401       // TODO: Bug 78552 was opened to add a MANIFEST entry containing
402       // the product name so we don't have to guess at it.
403       return "ATG";
404     } else {
405       return UNKNOWN_INFO;
406     }
407   }
408 
409   /*** Returns information about the ATG Dynamo product being used.
410    * does not include information about the app server that may be in
411    * use.  returns null if Dynamo is not running.
412    **/
413   public static String getDynamoProductInfo() 
414   {
415     StringBuffer sb = new StringBuffer();
416     AppModule dynamo = getAtgDynamoModule();
417 		
418     if ( dynamo == null ) return null;
419 		
420     sb.append( getDynamoProductName() + " version " + getAtgVersion(dynamo) );
421     String build = getAtgBuildNumber(dynamo);
422     if ( ! build.equals(UNKNOWN_INFO) ) sb.append( " build " + build);
423     String patch_version = getAtgPatchVersion(dynamo);
424     String patch_build   = getAtgPatchBuildNumber(dynamo);
425     if ( ! (patch_version == null) && ! patch_version.equals(UNKNOWN_INFO) )
426       sb.append( " with patch " + patch_version + " build " + patch_build );
427 		   
428     return sb.toString();		
429   }
430 
431   /*** Returns a summary of information about the App Server product
432    * being used.
433    */
434   public static String getAppServerProductInfo() {
435     StringBuffer sb = new StringBuffer();
436 		
437     sb.append(getAppServerProductName() + " version " + getAppServerVersion());
438     String build = getAppServerBuildNumber();
439     if ( ! build.equals(UNKNOWN_INFO) ) sb.append( " build " + build);
440     String patch_version = getAppServerPatchVersion();
441     String patch_build   = getAppServerPatchBuildNumber();
442     if ( ! (patch_version == null) && ! patch_version.equals(UNKNOWN_INFO) )
443       sb.append( " with patch " + patch_version + " build " + patch_build );
444 		   
445     return sb.toString();
446   }
447 
448   /*** returns the java version that Dynamo is using
449    *
450    * @return String version of java Dynamo is using
451    */
452   public static String getJavaVersion() {
453     return System.getProperty( JAVA_VERSION );
454   }
455 
456   /*** returns the java build version (java.version) that Dynamo is using
457    *
458    *  @return String build version of java Dynamo is using
459    */
460   public static String getJavaBuildVersion() {
461     return System.getProperty( JAVA_BUILD_VERSION );
462   }
463 
464   /*** returns detailed version information about the jdk being used */
465   public static String getJavaVersionDetails() {
466     return TestUtils.getJavaInfo() + " - " + TestUtils.getJavaVersion();
467   }
468 	
469   /*** returns info about the java build that Dynamo is using
470    *
471    * @return String info about java build Dynamo is using
472    */
473   public static String getJavaInfo() {
474     return System.getProperty( JAVA_VAR );
475   }
476 
477   /*** returns the type of compiler that Dynamo is using
478    *
479    * @return String compiler Dynamo is using
480    */
481   public static String getCompilerType() {
482     return System.getProperty( COMPILER_VAR );
483   }
484 
485   /*** returns the type of Operating System that Dynamo is running on
486    */
487   public static String getOperatingSystemType() {
488     return System.getProperty( OS_VAR )
489       + " version " + System.getProperty( OS_VERSION_VAR );
490   }
491 
492   /*** returns the hostname of the machine that Dynamo is running on
493    */
494   public static String getHostname() {
495     try {
496       InetAddress address = InetAddress.getLocalHost();
497       return address.getHostName();
498     } catch (UnknownHostException uhe) {}
499 
500     return "unknown";
501   }
502 
503   /*** returns the name of the app server that dynamo is using */
504   public static String getAppServerType() {
505     if ( ServletUtil.isWebLogic() ) return APP_SERVER_BEA;
506     else if ( ServletUtil.isWebSphere() ) return APP_SERVER_IBM;
507     else if ( ServletUtil.isDynamoJ2EEServer() ) return APP_SERVER_DAS;
508     else if ( isGenericAppServer() ) return APP_SERVER_TOMCAT;
509     else return System.getProperty( APP_SERVER );
510   }
511         
512   /*** Returns true if Dynamo is running on a 'generic' (aka Tomcat)
513    *  j2ee appserver; false otherwise.
514    *  ServletUtil.isGenericJ2EEServer() method call does not exist in
515    *  early Dynamo versions, so use reflection to invoke it.  As of
516    *  ATG 7x, isGenericJ2EEServer() really means "are we running on
517    *  JBOSS" - I'm not sure whether we intend to differentiate between
518    *  JBOSS and other 'generic' Tomcat app servers.
519    */
520   public static boolean isGenericAppServer() {
521     try {
522       ServletUtil.class.newInstance();
523       return ((Boolean) invokeMethod(dynamoEnv(), "isGenericJ2EEServer", null, null, null)).booleanValue();
524     } catch (Throwable t) {}
525     return false;
526   }
527 
528   /*** returns the WAS home directory if running on WAS.  otherwise,
529    * returns null */
530   public static String getWasHomeDir() {
531     return System.getProperty( "was.install.root" );
532   }
533         
534   /*** returns the WAS version number.  if not running against WAS, or
535    *  if the {WAS.install.root}/properties/version/BASE.product file
536    *  can not be found, or if an error occurs parsing the file then
537    *  returns UNKNOWN_INFO.
538    */    	
539   public static String getWasVersion() {
540     String version = null;				
541     try {
542       // WAS 5
543       File f = new File( getWasHomeDir(), "properties/version/BASE.product" );
544       // WAS 6
545       if (!f.exists())
546 	f = new File( getWasHomeDir(),"properties/version/WAS.product" );
547 
548       String[] children1 = { "version" };
549       List<Node> nodes1 = XmlUtils.getNodes(f, false, children1);
550       if (nodes1 != null ) {
551 	Iterator<Node> iter1 = nodes1.iterator();
552 	while (iter1.hasNext()) {
553 	  Node n1 = iter1.next();
554 	  version = XmlUtils.getNodeTextValue(n1);
555 	}
556       }
557     } catch (Throwable e) {}
558 			
559     if ( version != null ) return version;
560     else return UNKNOWN_INFO;
561   }
562 
563         
564   /*** returns the full file path of specified was log if
565    * getWasHomeDir() is not null.  otherwise returns null. */
566   private static File getWasLogFile( String pServerName, String pLogName )
567   {
568     if ( getWasHomeDir() == null ) return null;
569     else return new File( getWasHomeDir(), "logs" + File.separator + pServerName + File.separator + pLogName );
570   }
571 	
572   private static String mWasSystemOutLogFile = null;
573 
574   /*** Specifies the log file to return when asked for the WAS
575    *  'SystemOut.log' file.  if this value is null we attempt to
576    *  calculate a default location */
577   public void setWasSystemOutLogFile( String pFile ) {
578     mWasSystemOutLogFile = pFile;
579   }
580 
581   /*** returns the expected location of the WAS 'SystemOut.log' file if
582    * running on WAS.  otherwise returns null.*/
583   public static String getWasSystemOutLogFile() {
584     if ( getWasHomeDir() == null ) return null;
585     else if (mWasSystemOutLogFile != null
586 	     && mWasSystemOutLogFile.trim().length() > 0)
587       return mWasSystemOutLogFile.trim();
588     else {
589       File f = getWasLogFile(ServletUtil.getWebsphereServerName(),"SystemOut.log");
590       if (f == null || ! f.exists())
591 	f = getWasLogFile( "server1", "SystemOut.log" );
592                
593       if ( f != null ) return f.getAbsolutePath();
594     }
595     return null;
596   }
597 
598   private static String mWasSystemErrLogFile = null;
599 
600   /*** Specifies the log file to return when asked for the WAS
601    *  'SystemErr.log' file.  if this value is null we attempt to
602    *  calculate a default location */
603   public void setWasSystemErrLogFile( String pFile ) {
604     mWasSystemErrLogFile = pFile;
605   }
606 
607   /*** returns the expected location of the WAS 'SystemErr.log' file if
608    * running on WAS.  otherwise returns null.*/
609   public static String getWasSystemErrLogFile() {
610     if ( getWasHomeDir() == null ) return null;
611     else if (mWasSystemErrLogFile != null
612 	     && mWasSystemErrLogFile.trim().length() > 0)
613       return mWasSystemErrLogFile.trim();
614     else {
615       File f = getWasLogFile(ServletUtil.getWebsphereServerName(),"SystemErr.log");
616       if ( f == null || ! f.exists() )
617 	f = getWasLogFile( "server1", "SystemErr.log" );
618                
619       if ( f != null ) return f.getAbsolutePath();
620     }
621     return null;
622   }
623 
624   /*** returns the BEA home directory if running on BEA.  otherwise,
625    * returns null */
626   public static String getBeaHomeDir() { 
627     String homedir = System.getProperty( "bea.home" );
628     if ( homedir != null ) return homedir;
629         	
630     // sometimes (like on bea 8) 'bea.home' is not specified, so try
631     // to determine bea.home base on another property...
632     String startfile = System.getProperty( "java.security.policy" );
633     if ( startfile != null ) {
634       // the policy file is (hopefully) always located at a location like:
635       // /root/to/bea/<weblogic>/server/lib/weblogic.policy
636       // so we basically want to go up four levels from there...
637       homedir = atg.core.io.FileUtils.getParent( startfile );
638       // should now be in /root/to/bea/<weblogic>/server/lib
639       homedir = atg.core.io.FileUtils.getParent( homedir );
640       // should now be in /root/to/bea/<weblogic>/server
641       homedir = atg.core.io.FileUtils.getParent( homedir );
642       // should now be in /root/to/bea/<weblogic>
643       homedir = atg.core.io.FileUtils.getParent( homedir );
644       // should now be in /root/to/bea
645     }
646         	
647     return homedir;
648   }
649 
650   private static String mBeaMyServerLogFile = null;
651 
652   /*** Specifies the log file to return when asked for the BEA
653    *  'myserver.log' file.  if this value is null then a default value
654    *  will be calculated. */
655   public static void setBeaMyServerLogFile( String pFile ) {
656     mBeaMyServerLogFile = pFile;
657   }
658 
659   /*** returns the expected location of the BEA 'myserver.log' file if
660    * running on BEA.  otherwise returns null.*/
661   public static String getBeaMyServerLogFile() {
662     if ( getBeaHomeDir() == null ) return null;
663     else if (mBeaMyServerLogFile != null
664 	     && mBeaMyServerLogFile.trim().length() > 0 )
665       return mBeaMyServerLogFile.trim();
666     else {
667       // try this default location....
668       String name = "user_projects" + File.separator +
669 	"mydomain" + File.separator +
670 	"myserver" + File.separator +
671 	"myserver.log";
672       File log = new File( getBeaHomeDir(), name );
673                
674       if ( log.exists() ) {
675 	return log.getAbsolutePath();
676       } else {
677 	// the default didn't work (as we shouldn't always expect it
678 	// to) so try this location...  'user.dir' typically points to
679 	// the domain dir: /path/to/bea/user_projects/mydomain
680 	// 'weblogic.Name' should be like : myserver
681 	name = System.getProperty( "user.dir" ) + File.separator + 
682 	  System.getProperty( "weblogic.Name" ) + File.separator +
683 	  System.getProperty( "weblogic.Name" ) + ".log";
684 	log = new File( name );
685 	if ( log.exists() ) return log.getAbsolutePath();
686       }
687     }
688     return null;
689   }
690 
691   /*** returns the BEA version number.  if not running against BEA, or if
692    *  the {BEA_HOME}/registry.xml file can not be found, or if an error occurs
693    *  parsing the file then returns UNKNOWN_INFO.
694    */
695   public static String getBeaVersion() 
696   {
697     String version = null;
698     try {
699       File f = new File( new File( getBeaHomeDir() ), "registry.xml" );
700       String[] children = { "host", "product", "release" };
701       List<Node> nodes = XmlUtils.getNodes(f, false, children);
702       if ( nodes != null ) {
703 	Iterator<Node> iter = nodes.iterator();
704 	// I expect there to only be one <host><product><release> node
705 	// so this iteration should really just loop over one node.
706 	while ( iter.hasNext() ) {
707 	  Node n =  iter.next();
708 	  version = XmlUtils.getAttribute(n,"level","0") + "." +
709 	    XmlUtils.getAttribute(n,"ServicePackLevel","0") + "." +
710 	    XmlUtils.getAttribute(n,"PatchLevel","0");
711 	}
712       }
713     } catch (Throwable e) {
714     }
715             
716     if ( version != null ) return version;
717     else return UNKNOWN_INFO;
718   }
719         
720   // ---------------- JBOSS Utility Methods --------------------------
721   // NOTE: Available JBoss System property names can be found in class
722   // org.jboss.system.server.ServerConfig
723         
724   /*** Returns the JBOSS installation home directory, if Dynamo is
725    * running on JBOSS.  Otherwise returns null. */
726   public static File getJBossHomeDir() {
727     String dir = System.getProperty("jboss.home.dir");
728     if ( dir == null ) return null;
729     else return new File(dir);
730   }
731         
732   /*** Returns the JBOSS server home directory, if Dynamo is running on
733    * JBOSS.  Otherwise returns null. */
734   public static File getJBossServerHomeDir() {
735     String dir = System.getProperty("jboss.server.home.dir");
736     if ( dir == null ) return null;
737     else return new File(dir);
738   }
739         
740   /*** Returns the JBOSS server name, if Dynamo is running on JBOSS.
741    * Otherwise returns null. */
742   public static String getJBossServerName() {
743     return System.getProperty("jboss.server.name");
744   }
745         
746   /*** Returns the path to the JBOSS server log file, if it can be
747    * found.  Otherwise returns null */
748   public static String getJBossServerLog() {
749     try {
750       File log = new File( getJBossServerHomeDir(), "log/server.log" );
751       if ( log.exists() ) return log.getAbsolutePath();
752     } catch ( Throwable t ) {}
753     return null;
754   }
755         
756   /*** Returns the version of JBOSS being used, if it can be
757    *  determined.  Otherwise returns UNKNOWN_INFO.  This method
758    *  expects to find a 'jar-versions.xml' file in the JBoss home
759    *  directory. It searches for the &lt;jar&gt; element whose 'name'
760    *  attribute is "jboss.jar", and determines the version based on
761    *  the value of the 'implVersion' attribute.
762    **/
763   public static String getJBossVersion() {
764     try {
765       File versionFile = new File( getJBossHomeDir(), "jar-versions.xml");
766       if (!versionFile.exists()) {
767 	log("jar-versions.xml file does not exist; "
768 	    + "unable to determine version info");
769 	return UNKNOWN_INFO;
770       }
771       String[] children = { "jar" };
772       Iterator<Node> nodes = XmlUtils.getNodes(versionFile,false,children).iterator();
773       while ( nodes.hasNext() ) {
774 	try {
775 	  Node node =  nodes.next();
776 	  String name = node.getAttributes().getNamedItem("name").getNodeValue();
777 	  log("Checking node: " + name);
778 	  if ( name.equals("jboss.jar") ) {
779 	    String ver = node.getAttributes().getNamedItem("implVersion").getNodeValue().trim();
780 	    log("JBOSS version string: " + ver);
781 	    // implVersion is typically something like 
782 	    // "4.0.1sp1 (build: CVSTag=JBoss_4_0_1_SP1 date=200502160314)"
783 	    // so, strip off the build information since we don't care about it
784 	    int idx = ver.indexOf(" (build:");
785 	    if ( idx != -1 ) ver = ver.substring(0,idx).trim();
786 	    return ver;
787 	  }
788 	} catch (Throwable ti) {}
789       }
790     } catch (Throwable t) {}
791     return UNKNOWN_INFO;
792   }
793 
794   private static atg.service.dynamo.Configuration DYN_CONFIG = null;
795 
796   /*** returns the Configuration component being used by Dynamo */
797   public static atg.service.dynamo.Configuration getDynamoConfiguration() {
798     if ( DYN_CONFIG == null ) {
799       try {
800 	DYN_CONFIG = (atg.service.dynamo.Configuration) Nucleus.getGlobalNucleus().resolveName( CONFIGURATION_COMPONENT );
801       } catch (Throwable t) {}
802     }
803     return DYN_CONFIG;
804   }
805 
806   /*** returns the session limit of the specified license component
807    *  @param String the component name of the license in question
808    *  @param boolean true if Nucleus should attempt to create the license
809    *  component if it does not exist
810    *  @return int the session limit for the license.  0 if the license does not
811    *  resolve.
812    **/
813   public static int getSessionLimit( String pLicense, boolean pResolve )
814   {
815     if ( pLicense == null ) return 0;
816     LicenseImpl license = (LicenseImpl) Nucleus.getGlobalNucleus().resolveName( pLicense, pResolve );
817     if ( license == null ) return 0;
818     return license.getMaxSessions();
819   }
820 
821   // ==================== EMAIL ======================
822 
823   /*** This method is used to send an email message and allows the user
824    *  to specify the return address.
825    *  @return boolean true if the mail was sent successfully; otherwise false.
826    */
827   public static boolean sendEmailWithReturn(String pAddress, String pMsg,
828 					    String pSubject,
829 					    String pBodyEncoding,
830 					    String pReturnAddress )
831   {
832     try {
833       // create a Message with the given From and Subject
834       Message msg = MimeMessageUtils.createMessage( pReturnAddress, pSubject);
835       // set the To recipient
836       MimeMessageUtils.setRecipient(msg, Message.RecipientType.TO, pAddress);
837 
838       // set the message content: multipart message + attachment
839       if ( pBodyEncoding == null || pBodyEncoding.trim().length() == 0 )
840 	pBodyEncoding = "text/plain";
841       ContentPart[] content =
842 	{ new ContentPart( pMsg, pBodyEncoding) };
843       MimeMessageUtils.setContent(msg, content);
844 
845       // create the email event
846       EmailEvent em = new EmailEvent(msg);
847 
848       // now send the event
849       SMTPEmailSender sender = new SMTPEmailSender();
850       sender.setEmailHandlerHostName( MAILHOST );
851       sender.sendEmailEvent( em );
852     } catch (Exception e) {
853       log.info("Caught exception sending email: " + e.toString() );
854       return false;
855     }
856     return true;
857   }
858 
859   /*** This method is used to send an email message; returns true if
860    * everything went ok; otherwise, returns false
861    */
862   public static boolean sendEmail(String pAddress, String pMsg,
863 				  String pSubject, String pBodyEncoding)
864   {
865     return sendEmailWithReturn(pAddress,pMsg,pSubject,pBodyEncoding,pAddress);
866   }
867 
868   /*** This method is used to send an email message; returns true if
869    * everything went ok; otherwise, returns false
870    */
871   public static boolean sendEmail(String pAddress, String pMsg,
872 				  String pSubject)
873   {
874     return sendEmail( pAddress, pMsg, pSubject, "text/plain" );
875   }
876 
877   /*** This method is used to send the same email message to a vector
878    * of recipients
879    */
880   public static void sendEmails(List<String> pAddresses, String pMsg,
881 				String pSubject, String pBodyEncoding )
882   {
883     // make sure addresses are valid
884     if ( pAddresses == null ||
885 	 pAddresses.size() <= 0 )
886       return;
887 
888     // send emails
889     Iterator<String> addresses = pAddresses.iterator();
890     String address = null;
891     while ( addresses.hasNext() ) {
892       try {
893 	address =  addresses.next();
894 	if ( address != null && address.trim().length() > 0 )
895 	  sendEmail(address.trim(),pMsg,pSubject,null,null,null,pBodyEncoding);
896       } catch (Exception e) {}
897     }
898   }
899 
900   /*** This method is used to send the same email message to a vector
901    *  of recipients.  It encodes the message body as "text/plain".
902    */
903   public static void sendEmails(List<String> pAddresses, String pMsg,
904 				String pSubject )
905   {
906     sendEmails( pAddresses, pMsg, pSubject, "text/plain" );
907   }
908 
909   /*** This method is used to send an email message that contains
910    *  several attachments.  This method is specifically designed to
911    *  accept a map of java.lang.Strings as content parts instead of
912    *  java.io.Files.  The key in the Map should be a String
913    *  representing the name that you would like to show for the
914    *  attached file.  The value in the Map should be a String
915    *  representing the contents of the attachment.
916    */
917   public static void sendEmail(String pAddress, String pMsg, String pSubject,
918 			       Map<String, Object> pTextAttachments, Map<String, Object> pHTMLAttachments,
919 			       File[] pFiles, String pBodyEncoding )
920   {
921     try {
922       // make sure addresses are valid
923       if ( pAddress == null ||
924 	   pAddress.trim().length() == 0 )
925 	return;
926 
927       // create a Message with the given From and Subject
928       Message msg = MimeMessageUtils.createMessage( pAddress, pSubject);
929 
930       // set the To recipient
931       MimeMessageUtils.setRecipient(msg, Message.RecipientType.TO, pAddress);
932 
933       // create the MultiPart used to hold everything
934       Multipart mp = new MimeMultipart();
935 
936       // set the message content: multipart message + attachment
937       BodyPart guts = new MimeBodyPart();
938       if ( pBodyEncoding == null || pBodyEncoding.trim().length() == 0 )
939 	pBodyEncoding = "text/plain";
940       guts.setContent( pMsg, pBodyEncoding );
941       mp.addBodyPart( guts );
942 
943       // add the text attachments
944       if ( pTextAttachments != null ) {
945 	Iterator<String> textkeys = pTextAttachments.keySet().iterator();
946 	while ( textkeys.hasNext() ) {
947 	  String key = textkeys.next();
948 	  Object val = pTextAttachments.get( key );
949 	  if ( val != null ) {
950 	    MimeBodyPart part = new MimeBodyPart();
951 	    part.setContent( val.toString(), "text/plain" );
952 	    part.setDisposition( MimeBodyPart.ATTACHMENT );
953 	    part.setDescription( key );
954 	    part.setFileName( key );
955 	    mp.addBodyPart( part );
956 	  }
957 	}
958       }
959 
960       // add the html attachments
961       if ( pHTMLAttachments != null ) {
962 	Iterator<String> htmlkeys = pHTMLAttachments.keySet().iterator();
963 	while ( htmlkeys.hasNext() ) {
964 	  String key =  htmlkeys.next();
965 	  Object val = pHTMLAttachments.get( key );
966 	  if ( val != null ) {
967 	    MimeBodyPart part = new MimeBodyPart();
968 	    part.setContent( val.toString(), "text/html" );
969 	    part.setDisposition( MimeBodyPart.ATTACHMENT );
970 	    part.setDescription( key );
971 	    part.setFileName( key );
972 	    mp.addBodyPart( part );
973 	  }
974 	}
975       }
976 
977       // add the File attachments
978       if ( pFiles != null ) {
979 	for ( int i=0; i<pFiles.length; i++ ) {
980 	  MimeBodyPart part = new MimeBodyPart();
981 	  part.setDataHandler(new DataHandler(new FileDataSource(pFiles[i])));
982 	  part.setFileName( pFiles[i].getName() );
983 	  mp.addBodyPart( part );
984 	}
985       }
986 
987       msg.setContent( mp );
988 
989       // create the email event
990       EmailEvent em = new EmailEvent(msg);
991 
992       // now send the event
993       SMTPEmailSender sender = new SMTPEmailSender();
994       sender.setEmailHandlerHostName( MAILHOST );
995       sender.sendEmailEvent( em );
996     } catch (Exception e) {
997       log.info("Caught exception sending email: " + e.toString() );
998       e.printStackTrace();
999     }
1000   }
1001 
1002   /*** This method is used to send an email message that contains
1003    *  several attachments.  This method is specifically designed to
1004    *  accept a map of java.lang.Strings as content parts instead of
1005    *  java.io.Files.  The key in the Map should be a String
1006    *  representing the name that you would like to show for the
1007    *  attached file.  The value in the Map should be a String
1008    *  representing the contents of the attachment.  If you wish to
1009    *  attach java.io.Files, use the static methods found in
1010    *  atg.service.email.MimeMessageUtils.
1011    */
1012   public static void sendEmail(String pAddress, String pMsg, String pSubject,
1013 			       Map<String, Object> pTextAttachments, Map<String, Object> pHTMLAttachments,
1014 			       String pBodyEncoding )
1015   {
1016     sendEmail( pAddress, pMsg, pSubject, pTextAttachments, pHTMLAttachments, null, pBodyEncoding );
1017   }
1018 
1019   /*** This method is used to send an email message that contains
1020    *  several attachments.  This method is specifically designed to
1021    *  accept a map of java.lang.Strings as content parts instead of
1022    *  java.io.Files.  The key in the Map should be a String
1023    *  representing the name that you would like to show for the
1024    *  attached file.  The value in the Map should be a String
1025    *  representing the contents of the attachment.  If you wish to
1026    *  attach java.io.Files, use the static methods found in
1027    *  atg.service.email.MimeMessageUtils.
1028    */
1029   public static void sendEmail( String pAddress, String pMsg, String pSubject,
1030 				Map<String, Object> pTextAttachments, Map<String, Object> pHTMLAttachments )
1031   {
1032     sendEmail( pAddress,pMsg,pSubject,pTextAttachments,pHTMLAttachments,"text/plain");
1033   }
1034 
1035 
1036   /*** This method is used to send an email message that contains
1037    *  several attachments.  This method is specifically designed to
1038    *  accept a map of java.lang.Strings as content parts instead of
1039    *  java.io.Files.  The key in the Map should be a String
1040    *  representing the name that you would like to show for the
1041    *  attached file.  The value in the Map should be a String
1042    *  representing the contents of the attachment.
1043    */
1044   public static void sendEmails(List<String> pAddresses, String pMsg,
1045 				String pSubject, Map<String, Object> pTextAttachments,
1046 				Map<String, Object> pHTMLAttachments, File[] pFiles,
1047 				String pBodyEncoding )
1048   {
1049     // make sure addresses are valid
1050     if ( pAddresses == null ||
1051 	 pAddresses.size() <= 0 )
1052       return;
1053 
1054     // send emails
1055     Iterator<String> addresses = pAddresses.iterator();
1056     String address = null;
1057     while ( addresses.hasNext() ) {
1058       try {
1059 	address =  addresses.next();
1060 	if ( address != null && address.trim().length() > 0 )
1061 	  sendEmail(address.trim(), pMsg, pSubject, pTextAttachments,
1062 		    pHTMLAttachments, pFiles, pBodyEncoding );
1063       } catch (Exception e) {}
1064     }
1065   }
1066 
1067   /*** This method is used to send an email message that contains
1068    *  several attachments to multiple recipients.  This method is
1069    *  specifically designed to accept a map of java.lang.Strings as
1070    *  content parts instead of java.io.Files.  The key in the Map
1071    *  should be a String representing the name that you would like to
1072    *  show for the attached file.  The value in the Map should be a
1073    *  String representing the contents of the attachment.
1074    */
1075   public static void sendEmails(List<String> pAddresses, String pMsg,
1076 				String pSubject, Map<String, Object> pTextAttachments,
1077 				Map<String, Object> pHTMLAttachments, String pBodyEncoding )
1078   {
1079     sendEmails( pAddresses, pMsg, pSubject, pTextAttachments, pHTMLAttachments, null, pBodyEncoding );
1080   }
1081 
1082   /*** This method is used to send an email message that contains
1083    *  several attachments to multiple recipients.  The message will
1084    *  have it's main body part encoded as "text/plain".  <br>This
1085    *  method is specifically designed to accept a map of
1086    *  java.lang.Strings as content parts instead of java.io.Files.
1087    *  The key in the Map should be a String representing the name that
1088    *  you would like to show for the attached file.  The value in the
1089    *  Map should be a String representing the contents of the
1090    *  attachment.  If you wish to attach java.io.Files, use the static
1091    *  methods found in atg.service.email.MimeMessageUtils.
1092    */
1093   public static void sendEmails(List<String> pAddresses, String pMsg,
1094 				String pSubject, Map<String, Object> pTextAttachments,
1095 				Map<String, Object> pHTMLAttachments )
1096   {
1097     sendEmails(pAddresses,pMsg,pSubject,pTextAttachments,pHTMLAttachments,"text/plain");
1098   }
1099 
1100   // ======================== EXCEPTIONS =====================
1101 
1102   /*** this method returns a String representation of an Exception's stacktrace
1103    */
1104   public static String getStackTrace( Throwable pException ) {
1105     ByteArrayOutputStream bos = new ByteArrayOutputStream();
1106     PrintStream ps = new PrintStream( bos );
1107     pException.printStackTrace( ps );
1108     ps.flush();
1109     return bos.toString();
1110   }
1111 
1112   // ===================== URL ACCESS ========================
1113 
1114   /*** this method returns the contents of the page specified as the
1115    *  URL.  the URL should be a fully qualified request string.  for
1116    *  example, http://rygar.atg.com:8880/some/directory/page.jhtml
1117    *
1118    *  If the boolean parameter is set to true then this method will
1119    *  throw an Exception if an error occurs; otherwise it will simply
1120    *  return the contents of the exception.
1121    *
1122    * @exception MalformedURLException if URL is malformed & pThrow is true
1123    * @exception IOException if error happens while reading and pThrow is true
1124    */
1125   public static String accessURL( String pUrl, boolean pThrow )
1126     throws MalformedURLException, IOException
1127   {
1128     URL url = null;
1129     StringBuffer results = new StringBuffer();
1130     BufferedReader in = null;
1131     InputStreamReader isr = null;
1132     try {
1133       url = new URL( pUrl );
1134 
1135       isr = new InputStreamReader( url.openStream() );
1136       in = new BufferedReader( isr );
1137       String line = null;
1138       while ( ( line = in.readLine() ) != null ) {
1139 	results.append( line + "\n" );
1140       }
1141       return results.toString();
1142     } catch ( MalformedURLException e ) {
1143       if ( pThrow )
1144 	throw e;
1145       else
1146 	results.append( "\nEncountered an unexpected error while trying to retrieve the configuration info." +
1147 			"\nWhen the url " + url + " was requested, this error was received: \n" + getStackTrace( e ) + "\n" );
1148     } catch ( IOException ioe ) {
1149       if ( pThrow )
1150 	throw ioe;
1151       else
1152 	results.append( "\nEncountered an unexpected error while trying to retrieve the configuration info." +
1153 			"\nWhen the url " + url + " was requested, this error was received: \n" + getStackTrace( ioe ) + "\n");
1154     } finally {
1155       if ( in != null ) {
1156 	try { in.close(); }
1157 	catch (Exception e) {}
1158       }
1159       if ( isr != null ) {
1160 	try { isr.close(); }
1161 	catch (Exception e) {}
1162       }
1163     }
1164     return results.toString();
1165   }
1166 
1167   /*** this method returns the contents of the page specified as the URL.  the
1168    *  URL should be a fully qualified request string.  for example,
1169    *     http://rygar.atg.com:8880/some/directory/page.jhtml
1170    *
1171    *  Unlike it's sister method with the boolean parameter, this method will
1172    *  not throw an exception.
1173    */
1174   public static String accessURL( String pUrl )
1175   {
1176     try {
1177       return accessURL( pUrl, false );
1178     } catch (Exception e) {
1179       return "\nEncountered an unexpected error while trying to retrieve the configuration info." +
1180 	"\nWhen the url " + pUrl + " was requested, this error was received: \n" + getStackTrace( e ) + "\n";
1181     }
1182   }
1183 
1184   // ==================== File IO ============================
1185 
1186   /***
1187    * Writes the byte array into the specified file.
1188    *
1189    * @param File pFile the file to write to
1190    * @param byte[] the bytes to write
1191    *
1192    * @exception IOException if an error occurred opening or reading the file.
1193    */
1194   public static void writeFileBytes(File pFile, byte[] pBytes)
1195     throws IOException
1196   {
1197     if ( pBytes == null ) pBytes = new byte[0];
1198     FileOutputStream fos = null;
1199     try {
1200       fos = new FileOutputStream( pFile );
1201       fos.write( pBytes );
1202     }
1203     catch (IOException e) {
1204       throw e;
1205     }
1206     finally {
1207       try { if (fos != null) fos.close (); }
1208       catch (IOException exc) {}
1209     }
1210   }
1211 
1212   /*** converts a delimiter separated String of file names into an
1213    *  array and expands all System property variables in the Strings.
1214    *  it does not check whether resolved file paths exist.
1215    *
1216    *  @param String delimited string of files to be converted to array.
1217    *  @param String delimiter string used to separated files
1218    *  @return String[] array of expanded paths
1219    *  @exception Exception if files can't be resolved properly
1220    */
1221   public static String[] convertFileArray( String pFiles, String pDelimiter )
1222     throws Exception
1223   {
1224     return convertFileArray( pFiles, pDelimiter, null );
1225   }
1226 	
1227   /*** converts a delimiter separated String of file names into an array
1228    *  and expands all variables in the Strings.  it does not check whether
1229    *  resolved file paths exist.
1230    *
1231    *  @param String delimited string of files to be converted to array.
1232    *  @param String delimiter string used to separated files
1233    *  @param Properties optional primary mapping of key/value pairs to
1234    *  substitute into file paths whererever the syntax <tt>{...}</tt>
1235    *  is found.  If parameter is null, or mapping not found, then
1236    *  System.getProperties() is checked.
1237    *  @return String[] array of expanded paths
1238    *  @exception Exception if files can't be resolved properly
1239    */
1240   public static String[] convertFileArray(String pFiles, String pDelimiter,
1241 					  Properties pPrimaryMapping )
1242     throws Exception
1243   {
1244     if ( pDelimiter == null ) pDelimiter = "";
1245     StringTokenizer st = new StringTokenizer( pFiles, pDelimiter );
1246     List<String> files = new LinkedList<String>();
1247     while ( st.hasMoreTokens() ) {
1248       files.add( expand(st.nextToken(), pPrimaryMapping) );
1249     }
1250     return (String[]) files.toArray( new String[files.size()] );
1251   }
1252 
1253   /*** expands all System property variables specified in the supplied
1254    *  String using curly braces syntax <tt>{...}</tt> and returns the
1255    *  resulting String.
1256    *
1257    *  @param String the string to expand.  
1258    *  @exception Exception if a System property resolves to null or if
1259    *  the enclosing braces are not properly matched.
1260    */
1261   public static String expand( String pString )
1262     throws Exception
1263   {
1264     return expand( pString, null );
1265   }
1266 
1267   /*** expands all property variables specified in the supplied String
1268    *  using curly braces syntax <tt>{...}</tt> and returns the
1269    *  resulting String.  Property names inside the curly braces can be
1270    *  either a simple String referring to a Java System property, such
1271    *  as "SystemProperty.X", or can be in AppModuleResource format,
1272    *  such as
1273    *  "appModuleResource?moduleID=MyModule&resource=my/resource/file".
1274    *
1275    *  @param String the string to expand.  
1276    *  @param Properties an optional primary key/value mapping to use
1277    *  for System property substitutions.  If param is null, or if
1278    *  mapping not found, then System.getProperties().getProperty(xxx)
1279    *  is used.
1280    *  @return String the expanded string.
1281    *  @exception Exception if a System or AppModuleResource property
1282    *  resolves to null or if the enclosing braces are not properly
1283    *  matched.
1284    */
1285   public static String expand( String pString, Properties pPrimaryMapping )
1286     throws Exception
1287   {
1288     int idx = pString.indexOf("{");
1289     while ( idx != -1 ) {
1290       int end = pString.indexOf("}");
1291       if ( end == -1 )
1292 	throw new Exception("Unclosed braces in String " + pString );
1293       String pname = pString.substring(idx+1,end);
1294       String prop = null;
1295       if ( pPrimaryMapping != null ) prop = pPrimaryMapping.getProperty(pname);
1296       if ( prop == null ) {
1297 	if ( pname.startsWith("appModuleResource?") )
1298 	  prop = resolveAppModuleResourceReference(pname).getPath();
1299 	// atg.dynamo.root and atg.dynamo.home are resolved specially
1300 	// because of BigEar
1301 	else if ( pname.equals(ROOT_VAR) ) prop = getDynamoRootDir().getPath();
1302 	else if ( pname.equals(HOME_VAR) ) prop = getDynamoHomeDir().getPath();
1303 	else prop = System.getProperty(pname);
1304       }
1305       if ( prop == null )
1306 	throw new Exception("System property '"
1307 			    + pString.substring(idx+1,end)
1308 			    + "' is null.  String " + pString
1309 			    + " can not be resolved.");
1310 
1311       pString = pString.substring(0,idx) + prop + pString.substring(end+1);
1312       idx = pString.indexOf("{");
1313     }
1314     return pString;
1315   }
1316     
1317   // ===================== atg dynamo info ===================================
1318 
1319   /*** product module corresponding to ATG Dynamo */
1320   public static String ATGDYNAMO_PRODUCT_MODULE = "DPS";
1321 
1322   /*** specifies the name of the ATG Dynamo product module that will be
1323    *  loaded if Dynamo is being used. */
1324   public static void setAtgDynamoProductModule( String pModule ) {
1325     ATGDYNAMO_PRODUCT_MODULE = pModule;
1326   }
1327 
1328   /*** returns the name of the ATG Dynamo product module that will be
1329    *  loaded if Dynamo is being used. */
1330   public static String getAtgDynamoProductModule() {
1331     return ATGDYNAMO_PRODUCT_MODULE;
1332   }
1333     
1334   /*** returns an AppModule corresponding to the ATG Dynamo if that
1335    *  product is loaded.  If it isn't loaded then returns null.
1336    */
1337   public static AppModule getAtgDynamoModule() 
1338   {        
1339     // get all modules that were started with dynamo
1340     Iterator<?> modules = getAppLauncher().getModules().iterator();
1341 		
1342     while ( modules.hasNext() ) {
1343       AppModule module = (AppModule)modules.next();
1344       if ( module.getName().equals( getAtgDynamoProductModule() ) )
1345 	return module;
1346     }
1347 		
1348     return null;
1349   }
1350     
1351   // ==================== atg j2ee server info ==============================
1352 
1353   /*** product module corresponding to ATG's J2EE Server */
1354   public static String ATGJ2EESERVER_PRODUCT_MODULE = "J2EEServer";
1355 
1356   /*** specifies the name of the ATG J2EE Server product module that
1357    *  will be loaded if ATG's J2EE Server is being used. */
1358   public static void setAtgJ2eeServerProductModule( String pModule ) {
1359     ATGJ2EESERVER_PRODUCT_MODULE = pModule;
1360   }
1361 
1362   /*** returns the name of the ATG J2EE Server product module that will
1363    *  be loaded if ATG's J2EE Server is being used. */
1364   public static String getAtgJ2eeServerProductModule() {
1365     return ATGJ2EESERVER_PRODUCT_MODULE;
1366   }
1367         
1368   /*** returns an AppModule corresponding to the ATG J2EE Server if
1369    *  that product is loaded.  If it isn't loaded then returns null.
1370    */
1371   public static AppModule getAtgJ2eeServerModule() 
1372   {        
1373     // get all modules that were started with dynamo
1374     Iterator<?> modules = getAppLauncher().getModules().iterator();
1375 		
1376     while ( modules.hasNext() ) {
1377       AppModule module = (AppModule)modules.next();
1378       if ( module.getName().equals( getAtgJ2eeServerProductModule() ) )
1379 	return module;
1380     }
1381 		
1382     return null;
1383   }
1384 	
1385   // ==================== application info ============================
1386 	
1387   /*** possible application product modules that may be installed */
1388   public static String[] APPLICATION_PRODUCT_MODULES = {"ACA","ABTest","DCS-SO","CAF"};
1389 
1390   /*** specifies the names of possible application product modules that
1391    *  may be installed in Dyanmo.  used to help report on which
1392    *  application modules are running. */
1393   public void setApplicationProductModules( String[] pModules ) {
1394     APPLICATION_PRODUCT_MODULES = pModules;
1395   }
1396 
1397   /*** returns the names of possible application product modules that
1398    *  may be installed in Dyanmo.  used to help report on which
1399    *  application modules are running.  NOTE: This method should not
1400    *  be called.  It is only provided so we can specify application
1401    *  modules in a .properties file.  Java classes should call method
1402    *  getApplicationModules().*/
1403   public String[] getApplicationProductModules() {
1404     return APPLICATION_PRODUCT_MODULES;
1405   }
1406 		
1407   /*** returns an array of AppModule items corresponding to the
1408    * currently running application products.
1409    */
1410   public static AppModule[] getApplicationModules() 
1411   {
1412     List<AppModule> apps = new LinkedList<AppModule>();
1413 		
1414     // get all modules that were started with dynamo
1415     Iterator<?> modules = getAppLauncher().getModules().iterator();
1416 		
1417     while ( modules.hasNext() ) {
1418       AppModule module = (AppModule)modules.next();
1419       for ( int i=0; i<APPLICATION_PRODUCT_MODULES.length; i++ ) {
1420 	// in order to work around bug 80207, we allow a colon ":" in
1421 	// the specified module names.  if a colon exists, the name
1422 	// before the colon is the name of the module that would be
1423 	// started if the application is running.  the name after the
1424 	// colon is the module containing the MANIFEST.MF file with
1425 	// build info.  if there is no colon, assume the two modules
1426 	// are the same.
1427 	int idx = APPLICATION_PRODUCT_MODULES[i].indexOf(":");
1428 	if ( idx == -1 ) {
1429 	  // no colon...
1430 	  if ((APPLICATION_PRODUCT_MODULES[i]).equals(module.getName()))
1431 	    apps.add( module );
1432 	} else {
1433 	  if (APPLICATION_PRODUCT_MODULES[i].substring(0,idx).equals(module.getName())) {
1434 	    // NOTE: getAppLauncher().getModule(...) will return a
1435 	    // module as long as it exists; the module does not need
1436 	    // to be running.
1437 	    try {
1438 	      AppModule mod = getAppLauncher().getModule(APPLICATION_PRODUCT_MODULES[i].substring(idx+1));
1439 	      log.info("\nMod: " + mod );
1440 	      if ( mod != null ) apps.add( mod );
1441 	      else throw new Exception(APPLICATION_PRODUCT_MODULES[i].substring(idx+1) + " not found.");
1442 	    } catch (Exception ale) {
1443 	      log.info("*** WARNING [atg.junit.nucleus.TestUtils] "
1444 				 + "Can not resolve module '"
1445 				 + APPLICATION_PRODUCT_MODULES[i].substring(idx+1)
1446 				 + "'. "
1447 				 + ale.getMessage() );
1448 	    }
1449 	  }
1450 	}
1451       }
1452     }
1453 		
1454     return (AppModule[]) apps.toArray( new AppModule[ apps.size() ] );
1455   }
1456 
1457   // =========== generic AppModule info retrieval methods ====================
1458 	
1459   private static AppLauncher mAppLauncher = null;
1460 
1461   /*** Returns the AppLauncher used to load this class.  */
1462   private static AppLauncher getAppLauncher()
1463   {
1464     if ( mAppLauncher == null )
1465       mAppLauncher = AppLauncher.getAppLauncher( TestUtils.class );
1466     return mAppLauncher;
1467   }
1468 	
1469   /*** Retrieves a File resource from a Dynamo Module.  Note that the
1470    *  module does not need to be started, it simply has to be
1471    *  installed in the Dynamo.  Returned file is <u>not</u> verified
1472    *  to exist.
1473    * 
1474    *  @param String pModuleName the name of the Dynamo module to look in.  e.g. "SystemTests.JSPTest"
1475    *  @param String pResourceURI the URI of the File to get from the module.  e.g. "mite.xml"
1476    *  @return File the requested file.
1477    **/
1478   public static File getModuleResourceFile(String pModuleName, String pResourceURI ) 
1479   {
1480     return getAppLauncher().getAppModuleManager().getResourceFile(pModuleName, pResourceURI);
1481   }	
1482 	
1483   /*** Resolves an appModuleResource reference by parsing the string
1484    *  into its constituent ModuleID and ResourceURI.
1485    *  
1486    * @param String pReference The AppModuleResource reference to resolve.  Expected to be of format:
1487    * <br><tt>appModuleResource?moduleID=<i>moduleID</i>&resourceURI=<i>some/URI</i></tt>
1488    * @return File the referenced module resource.
1489    * @exception IllegalArgumentException if the specified reference does not have the proper structure.
1490    */
1491   public static File resolveAppModuleResourceReference( String pReference )
1492   {
1493     // there's probably a standard utility method in Dynamo to do this
1494     // resolution, but i can't find it...
1495     String moduleID = null;
1496     String resourceURI = null;
1497     String ref = pReference;
1498     try {
1499       int idx = ref.indexOf("moduleID=");       // locate moduleID delimiter
1500       if ( idx == -1 ) throw new Exception();
1501       ref = ref.substring( idx + 9 );  // strip up to and including 'moduleID='
1502       idx = ref.indexOf("&resourceURI="); // get index of resourceURI delimiter
1503       moduleID = ref.substring( 0,idx );        // extract moduleID
1504       resourceURI = ref.substring( idx + 13 );  // extract resourceURI
1505     } catch (Throwable t) {
1506       throw new IllegalArgumentException("Can not resolve appModuleReference. "
1507 					 + "Illegal reference syntax: "
1508 					 + pReference);
1509     }
1510     return getModuleResourceFile(moduleID, resourceURI);
1511   }
1512 	
1513   /*** Retrieves a piece of information from the MANIFEST of the
1514    *  supplied AppModule.  Returns null if the specified information
1515    *  can't be found.
1516    */
1517   public static String getManifestInfo( AppModule pModule, String pEntry)
1518   {
1519     return getManifestInfo( pModule.getManifest(), pEntry );
1520   }
1521   /***
1522    * Logs a message using Nucleus.logInfo() if Nucleus is available.
1523    * Otherwise it logs using log.info()
1524    * @param pMessage
1525    */
1526   public static void log (String pMessage) {
1527     Nucleus n = Nucleus.getGlobalNucleus();
1528     if (n != null)
1529       n.logInfo(pMessage);
1530     else
1531       log.info(new java.util.Date() + ":" + pMessage);
1532   }
1533     
1534   /*** Retrieves a piece of information from the specified Manifest file.
1535    *  Returns null if the specified information can't be found.
1536    */
1537   public static String getManifestInfo( Manifest pManifest, String pEntry) 
1538   {
1539     // if manifest or entry key is null return null...
1540     if ( pManifest == null || pEntry == null ) return null;
1541 
1542     if ( pManifest.getMainAttributes() == null ) return null;
1543     else return pManifest.getMainAttributes().getValue( pEntry );
1544   }
1545 
1546   /*** Returns the ATG product version ("ATG-Version") of the specified module.
1547    *  Returns UNKNOWN_INFO if the product version can't be determined. 
1548    **/
1549   public static String getAtgVersion(AppModule pModule) {
1550     String version = getManifestInfo(pModule, "ATG-Version");
1551     if ( version != null ) return version;
1552     else return UNKNOWN_INFO;
1553   }
1554 
1555   /*** Returns the ATG product build number ("ATG-Build") of the
1556    *  specified module.  Returns UNKNOWN_INFO if the build number
1557    *  can't be determined.
1558    **/
1559   public static String getAtgBuildNumber(AppModule pModule) {
1560     String build = getManifestInfo(pModule, "ATG-Build");
1561     if ( build != null ) return build;
1562     else return UNKNOWN_INFO;
1563   }
1564 
1565   /*** Returns the ATG patch version ("ATG-Patch-Version") of the
1566    *  specified module.  Returns null if the module's version can be
1567    *  determined, but a patch version can't.  Returns UNKNOWN_INFO if
1568    *  neither the product or patch version can be determined.
1569    **/
1570   public static String getAtgPatchVersion(AppModule pModule) {
1571     String version = getManifestInfo(pModule, "ATG-Patch-Version");
1572     if ( version != null ) return version;
1573     else if (getAtgVersion(pModule).equals(UNKNOWN_INFO)) return UNKNOWN_INFO;
1574     else return null;
1575   }
1576 
1577   /*** Returns the ATG patch build number ("ATG-Patch-Build") of the
1578    *  specified module.  Returns null if the module's build number can
1579    *  be determined, but a patch build number can't.  Returns
1580    *  UNKNOWN_INFO if neither the product or patch build number can be
1581    *  determined.
1582    **/
1583   public static String getAtgPatchBuildNumber(AppModule pModule) {
1584     String build = getManifestInfo(pModule, "ATG-Patch-Build");
1585     if ( build != null ) return build;
1586     else if (getAtgBuildNumber(pModule).equals(UNKNOWN_INFO))
1587       return UNKNOWN_INFO;
1588     else return null;
1589   }
1590 	
1591   /*** Returns the ATG full product version ("ATG-Version-Full") of the
1592    *  specified module.  Returns UNKNOWN_INFO if the full product
1593    *  version can't be determined.
1594    **/
1595   public static String getAtgFullVersion(AppModule pModule) {
1596     String version = getManifestInfo(pModule, "ATG-Version-Full");
1597     if ( version != null ) return version;
1598     else return UNKNOWN_INFO;
1599   }
1600 
1601   // ==================== Dynamo Environment Information =====================
1602 	
1603   // some methods called on DynamoEnv are not available in older
1604   // versions of the class, so use reflection to maintain backward
1605   // compatibility.
1606 	
1607   private static DynamoEnv mDynamoEnv = null;
1608   private static DynamoEnv dynamoEnv() { 
1609     if ( mDynamoEnv == null ) {
1610       try { mDynamoEnv = (DynamoEnv) DynamoEnv.class.newInstance(); } 
1611       catch (Throwable t) {}
1612     }
1613     return mDynamoEnv;
1614   }
1615 	
1616   /*** Returns true if Dynamo is running as BigEar; otherwise returns false. */
1617   public static boolean isBigEar() 
1618   {
1619     Boolean isBigEar = (Boolean) invokeMethod(dynamoEnv(), "isBigEar",
1620 					      null, null, Boolean.FALSE );
1621     return isBigEar.booleanValue();
1622   }
1623 	
1624   /*** Returns true if Dynamo is running as BigEar in standalone mode;
1625    * otherwise returns false. */
1626   public static boolean isBigEarStandalone() 
1627   {
1628     Boolean isStandalone = (Boolean) invokeMethod(dynamoEnv(),
1629 						  "getStandaloneMode",
1630 						  null, null, Boolean.FALSE );
1631     return isStandalone.booleanValue();
1632   }	
1633 	
1634   /*** Returns true is Dynamo is running with liveconfig enabled;
1635    * otherwise returns false. */
1636   public static boolean isLiveconfig()
1637   {
1638     // 'isLiveconfig' is a new method in Koko (pr 88105).  try it, but
1639     // if that doesn't work try the 'getProperty' method that was used
1640     // for ATG 7.0.  finally, if that doesn't work just examine System
1641     // properties as a last check.
1642     Boolean isliveconfig = (Boolean) invokeMethod(dynamoEnv(),
1643 						  "isLiveconfig",
1644 						  null, null, null);
1645     if ( isliveconfig == null ) {
1646       // that method didn't work, so try this method - which should
1647       // work in ATG 7
1648       String[] args = { "atg.dynamo.liveconfig" };
1649       String propval = (String) invokeMethod(dynamoEnv(),
1650 					     "getProperty", new Class[]{ String.class }, args, null);
1651       if ( propval != null ) {
1652 	isliveconfig = Boolean.valueOf("on".equalsIgnoreCase(propval));
1653       } else {
1654 	isliveconfig = Boolean.valueOf("on".equalsIgnoreCase( System.getProperty("atg.dynamo.liveconfig") )); 
1655       } 
1656     }
1657     if ( isliveconfig != null ) return isliveconfig.booleanValue();
1658     else return false;
1659   }
1660 	
1661   public static Object invokeMethod(Object pObj, String pMethodName,
1662 				    Class<?>[] pSignature, Object[] pParams,
1663 				    Object pDefault )
1664   {
1665     Object returnval = null;
1666     try {
1667       Method meth = pObj.getClass().getMethod( pMethodName, pSignature );
1668       returnval = meth.invoke( pObj, pParams );
1669       //if ( isLoggingDebug() ) logDebug("Method '" + pMethodName + "'
1670       //invoked - return value: " + returnval);
1671     } catch (Throwable t) {
1672       //if ( isLoggingDebug() ) logDebug("Method '" + pMethodName + "'
1673       //could not be invoked.", t);
1674       returnval = pDefault;
1675     }
1676     return returnval;
1677   }	
1678 	
1679 } // end of class