1 /***
2 * Copyright 2009 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 package atg.nucleus;
15
16 import java.io.File;
17 import java.io.FileWriter;
18 import java.io.IOException;
19 import java.net.MalformedURLException;
20 import java.net.ServerSocket;
21 import java.net.URI;
22 import java.net.URISyntaxException;
23 import java.net.URL;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Properties;
31
32 import javax.servlet.ServletException;
33
34 import org.apache.log4j.Logger;
35
36 import atg.applauncher.AppLauncher;
37 import atg.applauncher.AppLauncherException;
38 import atg.applauncher.AppModuleManager;
39 import atg.applauncher.MultiInstallLocalAppModuleManager;
40 import atg.applauncher.dynamo.DynamoServerLauncher;
41 import atg.core.io.FileUtils;
42 import atg.core.util.CommandProcessor;
43 import atg.core.util.JarUtils;
44 import atg.core.util.StringUtils;
45 import atg.nucleus.naming.ComponentName;
46 import atg.nucleus.servlet.NucleusServlet;
47 import atg.service.dynamo.ServerConfig;
48 import atg.test.util.FileUtil;
49
50 /***
51 * NucleusTestUtils
52 * @author adamb
53 * @version $Id: //test/UnitTests/base/main/src/Java/atg/nucleus/NucleusTestUtils.java#21 $
54 *
55 * This class contains some utility methods to make it faster
56 * to write a unit test that needs to resolve componants against Nucleus.
57 *
58 */
59 public class NucleusTestUtils {
60
61
62 /*** Class version string */
63
64 public static String CLASS_VERSION = "$Id: //test/UnitTests/base/main/src/Java/atg/nucleus/NucleusTestUtils.java#21 $$Change: 556195 $";
65
66 private static final String EXTRACT_TEMP_JAR_FILE_FOR_PATH = "Extract temp jar file for path ";
67 private static final String ATG_DUST_CONFIG = "atg-dust-config-";
68 private static final String FILE = "file:";
69 public static final Logger log = Logger
70 .getLogger(NucleusTestUtils.class);
71 public static final String ATG_DUST_TESTCONFIG = "atg.dust.testconfig";
72 private static final String ATG_DUST_TESTCONFIG_ENV = "ATG_DUST_TESTCONFIG";
73
74 /*** A map from Nucleus instance to temporary directory. Used by
75 * startNucleusWithModules. */
76 static Map<Nucleus, File> sNucleusToTempAtgServerDirectory =
77 Collections.synchronizedMap(new HashMap<Nucleus, File>());
78
79 /*** Whether or not to remove tempoary ATG server directories
80 * created by startNucleusWithModules(). True by default, but
81 * can be set to false for debugging. */
82 public static boolean sRemoveTempAtgServerDirectories = true;
83
84
85 /***
86 * Creates an Initial.properties file
87 * pRoot The root directory of the configpath
88 * pInitialServices A list of initial services
89 * @param pRoot The root of the config path entry.
90 * @param pInitialServices initial services list
91 * @return the create initial services properties file.
92 * @exception IOException if an error occurs
93 */
94 public static File createInitial(File pRoot,List pInitialServices) throws IOException {
95 Properties prop = new Properties();
96 Iterator iter = pInitialServices.iterator();
97 StringBuffer services = new StringBuffer();
98 while (iter.hasNext()) {
99 if (services.length() != 0) services.append(",");
100 services.append((String)iter.next());
101 }
102 prop.put("initialServices",services.toString());
103 return NucleusTestUtils.createProperties("Initial", new File(pRoot.getAbsolutePath()),"atg.nucleus.InitialService", prop);
104 }
105
106 /***
107 * Allows the absoluteName of the given service to be explicitly defined.
108 * Normally this is determined by the object's location in the Nucleus
109 * hierarchy. For test items that are not really bound to Nucleus, it's
110 * convenient to just give it an absolute name rather than going through
111 * the whole configuration and binding process.
112
113 * @param pName The absolute name value to set
114 * @param pService The service whose absolute nameshould be set.
115 */
116 public static void setAbsoluteName(String pName, GenericService pService) {
117 pService.mAbsoluteName = pName;
118 }
119
120 /***
121 * Adds the given object, pComponent to Nucleus, pNucleus at the path given
122 * by pComponentPath.
123 * @param pNucleus The Nucleus instance to which the component should be added
124 * @param pComponentPath the component path at which the component should be added
125 * @param pComponent the component instance to add
126 */
127 public static void addComponent(
128 Nucleus pNucleus,
129 String pComponentPath,
130 Object pComponent) {
131
132 if (pNucleus.resolveName(pComponentPath) != null)
133 return;
134 ComponentName name = ComponentName.getComponentName(pComponentPath);
135 ComponentName[] subNames = name.getSubNames();
136 GenericContext[] contexts = new GenericContext[subNames.length - 1];
137 contexts[0] = pNucleus;
138 for (int i = 1; i < subNames.length - 1; i++) {
139 contexts[i] = new GenericContext();
140
141 GenericContext tmpContext =
142 (GenericContext) contexts[i - 1].getElement(subNames[i].getName());
143 if (tmpContext == null)
144 contexts[i - 1].putElement(subNames[i].getName(), contexts[i]);
145 else
146 contexts[i] = tmpContext;
147 }
148 contexts[contexts.length
149 - 1].putElement(subNames[subNames.length - 1].getName(), pComponent);
150 }
151
152 /***
153 * Creates a .properties file
154 * @param pComponentName Name of the component
155 * @param pConfigDir Name of the configuration directory. If null,
156 * will add to the current working directory.
157 * @param pClass The class of the component (used for $class property).
158 * @param pProps Other properties of the component.
159 * @return The created file.
160 * @throws IOException
161 */
162 public static File createProperties(String pComponentName, File pConfigDir, String pClass, Properties pProps)
163 throws IOException {
164 File prop;
165 if (pConfigDir == null)
166 prop = new File("./" + pComponentName + ".properties");
167 else {
168 pConfigDir.mkdirs();
169 prop = new File(pConfigDir, pComponentName + ".properties");
170 new File(prop.getParent()).mkdirs();
171 }
172
173 if (prop.exists()) prop.delete();
174 prop.createNewFile();
175 FileWriter fw = new FileWriter(prop);
176 String classLine = "$class=" + pClass + "\n";
177 try {
178 if (pClass != null) fw.write(classLine);
179 if (pProps != null) {
180 Iterator iter = pProps.keySet().iterator();
181 while (iter.hasNext()) {
182 String key = (String) iter.next();
183 String thisLine = key + "=" + StringUtils.replace(pProps.getProperty(key),'//', "////") + "\n";
184 fw.write(thisLine);
185 }
186 }
187 }
188 finally {
189 fw.flush();
190 fw.close();
191 }
192 return prop;
193 }
194 /***
195 * Starts Nucleus using the given config directory
196 * @param configpath the config path directory entry
197 * to use as the entire config path.
198 * @return the started Nucleus
199 */
200 public static Nucleus startNucleus(File configpath) {
201 return startNucleus(configpath.getAbsolutePath());
202 }
203
204 /***
205 * Starts Nucleus using the given config directory
206 * @param pSingleConfigpathEntry the path name of the config path
207 * entry to specify.
208 * @return The started nucleus.
209 */
210 public static Nucleus startNucleus(String pSingleConfigpathEntry) {
211 System.setProperty("atg.dynamo.license.read", "true");
212 System.setProperty("atg.license.read", "true");
213 NucleusServlet.addNamingFactoriesAndProtocolHandlers();
214 Nucleus n = Nucleus.startNucleus(new String[] {pSingleConfigpathEntry});
215 return n;
216 }
217
218
219 /*** Cache of the config path for a given Class. Used by getConfigpath. */
220 static Map<Class, Map<String, File>> sConfigDir =
221 new HashMap<Class, Map<String, File>>();
222
223
224 /***
225 * A convenience method for returning the configpath for a test.
226 * pConfigDirectory is the top level name to be used for the configpath.
227 * Returns a file in the "config/data" subdirectory of the passed in file.
228 *
229 * @param pBaseConfigDirectory the base configuration directory.
230 * @return The calculated configuration path.
231 */
232 public static File getConfigpath(String pBaseConfigDirectory) {
233 return getConfigpath(NucleusTestUtils.class, pBaseConfigDirectory, true);
234 }
235
236
237 /***
238 * A convenience method for returning the configpath for a test.
239 * pConfigDirectory is the top level name to be used for the configpath.
240 * Returns a file in the "data" subdirectory of the passed in file.
241 *
242 * @param pBaseConfigDirectory the base configuration directory.
243 * @param pCreate whether to create the config/data subdirectory if
244 * it does not exist.
245 *
246 *
247 * @return The calculated configuration path.
248 */
249 public static File getConfigpath(String pBaseConfigDirectory, boolean pCreate) {
250 return getConfigpath(NucleusTestUtils.class, pBaseConfigDirectory, pCreate);
251 }
252 /***
253 * A convenience method for returning the configpath for a test.
254 * pConfigDirectory is the top level name to be used for the configpath.
255 * Returns a file in the pBaseConfigDirectory (or pBaseConfigDirectory +
256 * "data") subdirectory of the the passed in class's location.
257 * <P>
258 * The directory location is calculated as (in psuedocode): <code>
259 * (pClassRelativeTo's package location) + "/" + (pConfigDirectory or "data") + "/config"
260 * </code>
261 * This method always creates the config/data subdirectory if it does not
262 * exist.
263 *
264 * @param pClassRelativeTo
265 * the class whose package the config/data (or
266 * pBaseConfigDirectory/data) should be relative in.
267 * @param pBaseConfigDirectory
268 * the base configuration directory If null, uses "config".
269 * @return The calculated configuration path.
270 */
271
272 public static File getConfigpath(Class pClassRelativeTo,
273 String pBaseConfigDirectory) {
274 return getConfigpath(pClassRelativeTo, pBaseConfigDirectory, true);
275 }
276
277
278
279 /***
280 * A convenience method for returning the configpath for a test.
281 * pConfigDirectory is the top level name to be used for the configpath.
282 * Returns a file in the pBaseConfigDirectory (or pBaseConfigDirectory +
283 * "data") subdirectory of the the passed in class's location.<P>
284 *
285 * The directory location is calculated as (in psuedocode):
286 * <code>
287 * (pClassRelativeTo's package location) + "/" + (pConfigDirectory or "data") + "/config"
288 * </code>
289 *
290 *
291 * @param pClassRelativeTo the class whose package the config/data
292 * (or pBaseConfigDirectory/data) should be relative in.
293 * @param pBaseConfigDirectory the base configuration directory If null,
294 * uses "config".
295 * @param pCreate whether to create the config/data subdirectory if
296 * it does not exist.
297 *
298 *
299 * @return The calculated configuration path.
300 */
301 public static File getConfigpath(Class pClassRelativeTo, String pBaseConfigDirectory, boolean pCreate) {
302
303
304
305 Map<String, File> baseConfigToFile = sConfigDir.get(pClassRelativeTo);
306 if (baseConfigToFile == null) {
307 baseConfigToFile = new HashMap<String, File>();
308 sConfigDir.put(pClassRelativeTo, baseConfigToFile);
309 }
310
311 File fileFound = baseConfigToFile.get(pBaseConfigDirectory);
312
313 if (fileFound == null) {
314 String configdirname = "config";
315 String packageName = StringUtils.replace(pClassRelativeTo.getPackage()
316 .getName(), '.', "/");
317 if (pBaseConfigDirectory != null)
318 configdirname = pBaseConfigDirectory;
319
320 String configFolder = packageName + "/data/" + configdirname;
321 URL dataURL = pClassRelativeTo.getClassLoader().getResource(configFolder);
322
323
324 if (dataURL == null) {
325 URL root = pClassRelativeTo.getClassLoader().getResource(packageName);
326
327 File f = new File(root.getFile());
328 File f2 = new File(f, "/data/" + configdirname);
329 if (pCreate) {
330 f2.mkdirs();
331 }
332 dataURL = NucleusTestUtils.class.getClassLoader().getResource(configFolder);
333 if (dataURL == null) {
334 System.err.println("Warning: Could not find resource \"" +
335 configFolder + "\" in CLASSPATH");
336 }
337 }
338 if (dataURL != null) {
339
340
341 fileFound = extractJarDataURL(dataURL);
342 baseConfigToFile.put(pBaseConfigDirectory ,fileFound);
343 }
344 }
345 if (fileFound != null)
346 System.setProperty("atg.configpath",
347 fileFound.getAbsolutePath());
348 return fileFound;
349 }
350
351
352
353 /***
354 * This method is used to extract a configdir from a jar archive.
355 * Given a URL this method will extract the jar contents to a temp dir and return that path.
356 * It also adds a shutdown hook to cleanup the tmp dir on normal jvm completion.
357 * If the given URL does not appear to be a path into a jar archive, this method returns
358 * a new File object initialied with <code>dataURL.getFile()</code>.
359 * @ param A URL referring to an archive path contained within a jar. Ex: jar:file:foo.jar!/atg/data/config
360 * @return A temporary directory to be used as a configdir
361 */
362 private static File extractJarDataURL(URL dataURL) {
363
364
365 int endIndex = dataURL.getFile().lastIndexOf('!');
366 if (endIndex == -1) {
367
368 return new File(dataURL.getFile());
369 }
370 log.info(EXTRACT_TEMP_JAR_FILE_FOR_PATH + dataURL.getFile());
371 File configDir = null;
372 try {
373 File tmpFile = File.createTempFile("atg-dust" + System.currentTimeMillis() ,".tmp");
374 tmpFile.deleteOnExit();
375 final File tmpDir = new File(tmpFile.getParentFile(), ATG_DUST_CONFIG
376 + System.currentTimeMillis());
377
378 String jarPath = dataURL.getFile().substring(0, endIndex);
379
380 int fileColonIndex = jarPath.indexOf(FILE) + FILE.length();
381 jarPath = jarPath.substring(fileColonIndex, jarPath.length());
382 JarUtils.unJar(new File(jarPath), tmpDir, false);
383
384 String relativePath = dataURL.getFile().substring(endIndex + 1,
385 dataURL.getFile().length());
386
387 FileUtil.deleteDirectoryOnShutdown(tmpDir);
388 configDir = new File(tmpDir, relativePath);
389 } catch (IOException e) {
390 e.printStackTrace();
391 }
392 return configDir;
393 }
394
395 /***
396 * Look up the global testconfig path.
397 * This path is specified in either an
398 * @return
399 */
400 public static String getGlobalTestConfig() {
401
402 String config = System.getProperty(ATG_DUST_TESTCONFIG);
403
404 if (config == null)
405 config = System.getenv(ATG_DUST_TESTCONFIG_ENV);
406
407 return config;
408 }
409
410 /***
411 * This method starts nucleus with a config path calculated from the
412 * specified list of Dynamo modules ("DAS", "DPS", "DSS",
413 * "Publishing.base", etc). Additionally adds a directory calculated relative
414 * to the location of pClassRelativeTo's package name from the classloader.
415 * The added config directory is calculated as (in psuedocode):
416 * <code>
417 * (pClassRelativeTo's package location) + "/data/" + (pClassRelativeTo's simpleClassName) + "/config"
418 * </code>
419 * and is only added if the directory exists. <P>
420 *
421 * You must specify a <code>pInitialService</code> parameter, which
422 * will be the initial service started by Nucleus (rather than the
423 * normally Initial component, which would do a full Nucleus component
424 * start-up). <P>
425 *
426 * This method also creates a temporary server directory, which is deleted
427 * when shutdownNucleus in invoked on the returned directory. <P>
428 *
429 * Note: If you need to start up a complete ATG instance, you should
430 * use DUST rather than a unit test. <P>
431 *
432 * Note: You may also wish to use a {@see
433 * atg.nucleus.ConfigCreationFilter}. You can either set a value for
434 * Nucleus.CREATION_FILTER_CLASS_PROPERTY_NAME
435 * ("atg.nucleus.Nucleus.creationFilterClass") as a DynamoEnv or System
436 * property, or set the creationFilter property in Nucleus.properties in
437 * your configuration. This allows on to block creation of referenced
438 * components without having to make additional properties file changes.
439 *
440 * Note 3: Nucleus's classReplacementMap can also be useful for replacing
441 * a component instance with a subclass.
442 *
443 * @param pModules the list of modules to use to calculate the
444 * Nucleus configuration path.
445 * @param pClassRelativeTo the class whose name and package
446 * will be used for the {packageName}/config/{ClassName}/data directory
447 * @param pInitialService the nucleus path of the Nucleus component
448 * to start-up. This is a required property to prevent accidental
449 * full start-up.
450 * @return the started Nucleus instance that should later be shut down
451 * with the shutdownNucleus method.
452 * @exception ServletException if an error occurs
453 */
454 public static Nucleus startNucleusWithModules(
455 String[] pModules, Class pClassRelativeTo,
456 String pInitialService) throws ServletException {
457 return startNucleusWithModules(
458 new NucleusStartupOptions(
459 pModules, pClassRelativeTo,
460 pClassRelativeTo.getSimpleName() + "/config",
461 pInitialService));
462 }
463
464
465 /***
466 * This method starts nucleus with a config path calculated from the
467 * specified list of Dynamo modules ("DAS", "DPS", "DSS",
468 * "Publishing.base", etc). Additionally adds a directory calculated relative
469 * to the location of pClassRelativeTo's package name from the classloader.
470 * The added config directory is calculated as (in psuedocode):
471 * <code>
472 * (pClassRelativeTo's package location) + "/data/" + (pBaseConfigDirectory or "config")
473 * </code>
474 * and is only added if the directory exists. <P>
475 *
476 * You must specify a <code>pInitialService</code> parameter, which
477 * will be the initial service started by Nucleus (rather than the
478 * normally Initial component, which would do a full Nucleus component
479 * start-up). <P>
480 *
481 * This method also creates a temporary server directory, which is deleted
482 * when shutdownNucleus in invoked on the returned directory. <P>
483 *
484 * Note: If you need to start up a complete ATG instance, you should
485 * use DUST rather than a unit test. <P>
486 *
487 * Note: You may also wish to use a {@see
488 * atg.nucleus.ConfigCreationFilter}. You can either set a value for
489 * Nucleus.CREATION_FILTER_CLASS_PROPERTY_NAME
490 * ("atg.nucleus.Nucleus.creationFilterClass") as a DynamoEnv or System
491 * property, or set the creationFilter property in Nucleus.properties in
492 * your configuration. This allows on to block creation of referenced
493 * components without having to make additional properties file changes.
494 *
495 * Note 3: Nucleus's classReplacementMap can also be useful for replacing
496 * a component instance with a subclass.
497 *
498 * @param pModules the list of modules to use to calculate the
499 * Nucleus configuration path.
500 * @param pClassRelativeTo the class whose package the config/data
501 * (or pBaseConfigDirectory/data) should be relative in.
502 * @param pBaseConfigDirectory the base configuration directory. If
503 * this parameter is non-null, the relative configuration subdirectory will be
504 * ("data/" + pBaseConfigDirectory) rather than "data/config".
505 * @param pInitialService the nucleus path of the Nucleus component
506 * to start-up. This is a required property to prevent accidental
507 * full start-up.
508 * @return the started Nucleus instance that should later be shut down
509 * with the shutdownNucleus method.
510 * @exception ServletException if an error occurs
511 */
512 public static Nucleus startNucleusWithModules(
513 String[] pModules, Class pClassRelativeTo, String pBaseConfigDirectory,
514 String pInitialService) throws ServletException {
515 return startNucleusWithModules(
516 new NucleusStartupOptions(pModules, pClassRelativeTo, pBaseConfigDirectory,
517 pInitialService));
518 }
519
520
521 /***
522 * This method starts nucleus with a config path calculated from the
523 * specified list of Dynamo modules ("DAS", "DPS", "DSS",
524 * "Publishing.base", etc). Additionally adds a directory calculated relative
525 * to the location of pClassRelativeTo's package name from the classloader.
526 * The added config directory is calculated as (in psuedocode):
527 * <code>
528 * (pClassRelativeTo's package location) + "/data/" + (pBaseConfigDirectory or "config")
529 * </code>
530 * and is only added if the directory exists. <P>
531 *
532 * You must specify a <code>pInitialService</code> parameter, which
533 * will be the initial service started by Nucleus (rather than the
534 * normally Initial component, which would do a full Nucleus component
535 * start-up). <P>
536 *
537 * This method also creates a temporary server directory, which is deleted
538 * when shutdownNucleus in invoked on the returned directory. <P>
539 *
540 * Note: If you need to start up a complete ATG instance, you should
541 * use DUST rather than a unit test. <P>
542 *
543 * Note: You may also wish to use a {@see
544 * atg.nucleus.ConfigCreationFilter}. You can either set a value for
545 * Nucleus.CREATION_FILTER_CLASS_PROPERTY_NAME
546 * ("atg.nucleus.Nucleus.creationFilterClass") as a DynamoEnv or System
547 * property, or set the creationFilter property in Nucleus.properties in
548 * your configuration. This allows on to block creation of referenced
549 * components without having to make additional properties file changes.
550 *
551 * Note 3: Nucleus's classReplacementMap can also be useful for replacing
552 * a component instance with a subclass.
553 *
554 * See NucleusStartupOptions for the effects of individual properties
555 * of pOptions.
556 *
557 *
558 * @param pOptions the startup Options for Nucleus.
559 *
560 * @return the started Nucleus instance that should later be shut down
561 * with the shutdownNucleus method.
562 * @exception ServletException if an error occurs
563 */
564 public static Nucleus startNucleusWithModules(
565 NucleusStartupOptions pOptions) throws ServletException {
566
567 if (pOptions.getInitialService() == null) {
568 throw new IllegalArgumentException("Initial service must be specified.");
569 }
570
571
572 String dynamoRootStr = findDynamoRoot();
573
574 if (dynamoRootStr == null) {
575 throw new ServletException("Could not find dynamo root.");
576 }
577
578 if (!new File(dynamoRootStr).exists()) {
579 throw new ServletException(
580 "Could not find dynamo root at " +
581 dynamoRootStr + " because directory does not exist.");
582 }
583
584 if (DynamoEnv.getProperty("atg.dynamo.root") == null) {
585
586 DynamoEnv.setProperty("atg.dynamo.root", dynamoRootStr);
587 }
588
589 if (DynamoEnv.getProperty("atg.dynamo.home") == null) {
590
591 DynamoEnv.setProperty("atg.dynamo.home",
592 dynamoRootStr + File.separator + "home");
593 }
594
595
596
597 File dynamoRootFile = new File(dynamoRootStr);
598
599 String strModulesString = StringUtils.joinStringsWithQuoting(
600 pOptions.getModules(), File.pathSeparatorChar);
601
602 DynamoEnv.setProperty("atg.dynamo.modules", strModulesString);
603
604
605
606 File fileServerDir = null;
607
608 try {
609
610 AppModuleManager modMgr = new MultiInstallLocalAppModuleManager(
611 dynamoRootStr, dynamoRootFile,
612 strModulesString);
613
614 AppLauncher launcher = AppLauncher.getLauncher(modMgr, strModulesString);
615
616
617 String configpath = DynamoServerLauncher.calculateConfigPath(
618 launcher, pOptions.getLiveconfig(),
619 pOptions.getLayersAsString(), false, null);
620
621
622
623 File fileBaseConfig = getConfigpath(NucleusTestUtils.class, null, false);
624
625 if ((fileBaseConfig != null) && fileBaseConfig.exists()) {
626 configpath = configpath + File.pathSeparator +
627 fileBaseConfig.getAbsolutePath();
628 }
629
630
631
632 File fileTestConfig = getConfigpath(
633 pOptions.getClassRelativeTo(),
634 pOptions.getBaseConfigDirectory(), false);
635
636
637 if ((fileTestConfig != null) && fileTestConfig.exists()) {
638 configpath = configpath + File.pathSeparator +
639 fileTestConfig.getAbsolutePath();
640 }
641 else if (fileTestConfig != null) {
642 System.err.println("Warning: did not find directory " +
643 fileTestConfig.getAbsolutePath());
644 }
645
646
647 fileServerDir = createTempServerDir();
648
649 System.setProperty("atg.dynamo.server.home",
650 fileServerDir.getAbsolutePath());
651 System.setProperty("atg.dynamo.license.read", "true");
652 System.setProperty("atg.license.read", "true");
653 NucleusServlet.addNamingFactoriesAndProtocolHandlers();
654
655 ArrayList<String> listArgs = new ArrayList<String>();
656 listArgs.add(configpath);
657 listArgs.add("-initialService");
658 listArgs.add(pOptions.getInitialService());
659
660 PropertyEditors.registerEditors();
661 System.out.println("Starting nucleus with arguments: " + listArgs);
662 Nucleus n = Nucleus.startNucleus(listArgs.toArray(new String[0]));
663
664
665 sNucleusToTempAtgServerDirectory.put(n, fileServerDir);
666
667
668 fileServerDir = null;
669
670 return n;
671 } catch (AppLauncherException e) {
672 throw new ServletException(e);
673 }
674 catch (IOException e) {
675 throw new ServletException(e);
676 }
677 finally {
678 if ((fileServerDir != null) && sRemoveTempAtgServerDirectories) {
679 try {
680
681
682 FileUtils.deleteDir(fileServerDir.getAbsolutePath());
683 } catch (IOException e) {
684
685
686
687 e.printStackTrace();
688 }
689 }
690 }
691 }
692
693 /***
694 * Shutdown the specified Nucleus and try to delete the associated
695 * temporary server directory. Typically used on a Nucleus created
696 * by startNucleusWithModules.
697 * @param pNucleus the nucleus instance to shut down.
698 * @exception ServiceException if an error occurs
699 * @exception IOException if an error occurs (such as a failure
700 * to remove the temporary server directory).
701 */
702 public static void shutdownNucleus(Nucleus pNucleus) throws ServiceException, IOException {
703 boolean bComplete = false;
704 try {
705 if (pNucleus.isRunning()) {
706 pNucleus.stopService();
707 }
708 bComplete = true;
709 } finally {
710 File fileTempAtgServDirectory =
711 sNucleusToTempAtgServerDirectory.get(pNucleus);
712
713
714 if (sRemoveTempAtgServerDirectories &&
715 (fileTempAtgServDirectory != null) &&
716 fileTempAtgServDirectory.exists()) {
717 try {
718 FileUtils.deleteDir(fileTempAtgServDirectory.getAbsolutePath());
719 } catch (IOException e) {
720 if (bComplete) {
721
722
723 throw e;
724 }
725 } finally {
726 sNucleusToTempAtgServerDirectory.remove(pNucleus);
727 }
728 }
729 }
730 }
731
732 /***
733 * Create a temporary, empty server directory. This is to satisfy
734 * Dynamo's need to have a server directory, yet not conflict if
735 * multiple tests are running at the same time against the same Dynamo
736 * instance. The directory name is generated by File.createTempFile.
737 * @return the created temporary server directory.
738 * @exception IOException if an error occurs
739 */
740 protected static File createTempServerDir() throws IOException {
741 File fileTemp = File.createTempFile("tempServer", "dir");
742 fileTemp.delete();
743 if (!fileTemp.mkdir()) {
744 throw new IOException("Unable to create directory " +
745 fileTemp.getAbsolutePath());
746 }
747 for (String strSubDir : ServerConfig.smConfigFileDirs) {
748 File fileSubDir = new File(fileTemp, strSubDir);
749 if (!fileSubDir.mkdirs()) {
750 throw new IOException("Unable to create directory " +
751 fileSubDir.getAbsolutePath());
752 }
753 }
754 return fileTemp;
755 }
756
757
758 /*** A crazily ugly and elaborate method where we try to discover
759 * DYNAMO_ROOT by various means. This is mostly made complicated
760 * by the ROAD DUST environment being so different from devtools.
761 */
762 static String findDynamoRoot() {
763
764 String dynamoRootStr = DynamoEnv.getProperty("atg.dynamo.root");
765
766
767 if (dynamoRootStr == null) {
768
769
770 dynamoRootStr =
771 CommandProcessor.getProcEnvironmentVar("DYNAMO_ROOT");
772 }
773
774 if (dynamoRootStr == null) {
775
776 String dynamoHomeStr = DynamoEnv.getProperty("atg.dynamo.home");
777 if (StringUtils.isEmpty(dynamoHomeStr)) {
778 dynamoHomeStr = null;
779 }
780
781 if (dynamoHomeStr == null) {
782 dynamoHomeStr =
783 CommandProcessor.getProcEnvironmentVar("DYNAMO_HOME");
784
785 if (StringUtils.isEmpty(dynamoHomeStr)) {
786 dynamoHomeStr = null;
787 }
788
789 if (dynamoHomeStr != null) {
790
791 DynamoEnv.setProperty("atg.dynamo.home", dynamoHomeStr);
792 }
793 }
794
795 if (dynamoHomeStr != null) {
796 dynamoRootStr = dynamoHomeStr.trim() + File.separator + "..";
797 }
798 }
799
800 if (dynamoRootStr == null) {
801
802
803 File currentDir = new File(new File(".").getAbsolutePath());
804 String strDynamoHomeLocalConfig = "Dynamo" + File.separator + "home" + File.separator + "localconfig";
805
806 while (currentDir != null) {
807 File filePotentialHomeLocalconfigDir = new File(currentDir,
808 strDynamoHomeLocalConfig);
809 if (filePotentialHomeLocalconfigDir.exists()) {
810 dynamoRootStr = new File(currentDir, "Dynamo").getAbsolutePath();
811 System.out.println("Found dynamo root via parent directory: " + dynamoRootStr);
812 break;
813 }
814 currentDir = currentDir.getParentFile();
815 }
816 }
817
818 if (dynamoRootStr == null) {
819
820
821
822 URL urlClass =
823 NucleusTestUtils.class.getClassLoader().getResource("atg/nucleus/Nucleus.class");
824
825
826 if ((urlClass != null) && "jar".equals(urlClass.getProtocol())) {
827 String strFile = urlClass.getFile();
828 int separator = strFile.indexOf('!');
829 strFile = strFile.substring(0, separator);
830
831 File fileCur = null;
832 try {
833 fileCur = urlToFile(new URL(strFile));
834 } catch (MalformedURLException e) {
835
836 }
837
838 if (fileCur != null) {
839 String strSubPath =
840 "DAS/taglib/dspjspTaglib/1.0".replace('/', File.separatorChar);
841 while ((fileCur != null) && fileCur.exists()) {
842 if (new File(fileCur, strSubPath).exists()) {
843 dynamoRootStr = fileCur.getAbsolutePath();
844 System.out.println("Found dynamo root by Nucleus.class location: " +
845 dynamoRootStr);
846
847
848 break;
849 }
850 fileCur = fileCur.getParentFile();
851 }
852 }
853 }
854 }
855
856 return dynamoRootStr;
857 }
858
859 /*** Try to convert a file URL to a File object. You'd think this would be
860 * easier, but no. */
861 static File urlToFile(URL url) {
862 URI uri;
863
864 if (!"file".equals(url.getProtocol())) {
865 throw new IllegalArgumentException("URL must be a file URL, got " + url);
866 }
867
868 try {
869
870
871 uri = url.toURI();
872 } catch (URISyntaxException e) {
873
874
875
876
877
878
879
880
881
882 try {
883 uri = new URI(url.getProtocol(), url.getUserInfo(), url
884 .getHost(), url.getPort(), url.getPath(), url
885 .getQuery(), url.getRef());
886 } catch (URISyntaxException e1) {
887
888 throw new IllegalArgumentException("broken URL: " + url);
889 }
890 }
891 return new File(uri);
892 }
893
894 /***
895 * This method returns a free port number on the current machine. There is
896 * some chance that the port number could be taken by the time the caller
897 * actually gets around to using it.
898 *
899 * This method returns -9999 if it's not able to find a port.
900 */
901 public static int findFreePort() {
902 ServerSocket socket = null;
903 int freePort = -9999;
904 try {
905 socket = new ServerSocket(0);
906 freePort = socket.getLocalPort();
907 } catch (IOException e) {
908 ;
909 } finally {
910 try {
911 socket.close();
912 } catch (IOException e) {
913 ;
914 }
915 }
916 return freePort;
917 }
918
919
920 /*** A class representing NucleusStartupOptions, used by
921 * startNucleusWithModules().
922 */
923 public static class NucleusStartupOptions {
924
925 /*** List of dynamo modules. */
926 private String[] mModules;
927 /*** Class whose package data subdir is relative to. */
928 private Class mClassRelativeTo;
929 /*** The base config directory, realtive to mClassRelativeTo's package
930 * + "/data". If null, then "config"
931 */
932 private String mBaseConfigDirectory;
933
934 /*** The Nucleus path of the intial service to resolve. */
935 private String mInitialService;
936
937
938 /***
939 * This constructor creates NucleusStartupOptions with the
940 * specified list of Dynamo modules ("DAS", "DPS", "DSS",
941 * "Publishing.base", etc).
942 * Additionally sets opts to add a directory calculated relative
943 * to the location of pClassRelativeTo's package name from the classloader.
944 * The added config directory is calculated as (in psuedocode):
945 * <code>
946 * (pClassRelativeTo's package location) + "/data/" + (pClassRelativeTo's simpleClassName) + "/config"
947 * </code>
948 * and is only added if the directory exists. <P>
949 *
950 * You must specify a <code>pInitialService</code> parameter, which
951 * will be the initial service started by Nucleus (rather than the
952 * normally Initial component, which would do a full Nucleus component
953 * start-up). <P>
954 *
955 *
956 * @param pModules the list of modules to use to calculate the
957 * Nucleus configuration path.
958 * @param pClassRelativeTo the class whose name and package
959 * will be used for the {packageName}/config/{simpleClassName}/data directory
960 * @param pInitialService the nucleus path of the Nucleus component
961 * to start-up. This is a required property to prevent accidental
962 * full start-up.
963 * @return the started Nucleus instance that should later be shut down
964 * with the shutdownNucleus method.
965 * @exception ServletException if an error occurs
966 */
967 public NucleusStartupOptions(String[] pModules, Class pClassRelativeTo,
968 String pInitialService) {
969
970
971 mModules = pModules;
972 mClassRelativeTo = pClassRelativeTo;
973 mInitialService = pInitialService;
974 mBaseConfigDirectory = pClassRelativeTo.getSimpleName() + "/config";
975 }
976
977
978 /***
979 * This constructor creates NucleusStartupOptions with the
980 * specified list of Dynamo modules ("DAS", "DPS", "DSS",
981 * "Publishing.base", etc).
982 * Additionally sets opts to add a directory calculated relative
983 * to the location of pClassRelativeTo's package name from the classloader.
984 * The added config directory is calculated as (in psuedocode):
985 * <code>
986 * (pClassRelativeTo's package location) + "/" + (pConfigDirectory or "data") + "/config"
987 * </code>
988 * and is only added if the directory exists. <P>
989 *
990 * You must specify a <code>pInitialService</code> parameter, which
991 * will be the initial service started by Nucleus (rather than the
992 * normally Initial component, which would do a full Nucleus component
993 * start-up). <P>
994 *
995 *
996 * @param pModules the list of modules to use to calculate the
997 * Nucleus configuration path.
998 * @param pClassRelativeTo the class whose package the config/data
999 * (or pBaseConfigDirectory/data) should be relative in.
1000 * @param pBaseConfigDirectory the base configuration directory. If
1001 * this parameter is non-null, the relative configuration subdirectory will be
1002 * ("data/" + pBaseConfigDirectory) rather than "data/config".
1003 * @param pInitialService the nucleus path of the Nucleus component
1004 * to start-up. This is a required property to prevent accidental
1005 * full start-up.
1006 * @return the started Nucleus instance that should later be shut down
1007 * with the shutdownNucleus method.
1008 * @exception ServletException if an error occurs
1009 */
1010 public NucleusStartupOptions(String[] pModules, Class pClassRelativeTo,
1011 String pBaseConfigDirectory,
1012 String pInitialService) {
1013
1014 mModules = pModules;
1015 mClassRelativeTo = pClassRelativeTo;
1016 mInitialService = pInitialService;
1017 mBaseConfigDirectory = pBaseConfigDirectory;
1018 }
1019
1020 /*** Return the list of modules for starting Nucleus. These modules
1021 * are the modules whose config path will be included. */
1022 public String[] getModules() {
1023 return mModules;
1024 }
1025
1026 /*** Return the "class relative to" property. This is the Class whose
1027 * package the config directory will be relative to. */
1028 public Class getClassRelativeTo() {
1029 return mClassRelativeTo;
1030 }
1031
1032 /*** Gets the initialService. This is the InitialService for Nucleus
1033 * to resolve at start-up. Required. */
1034 public String getInitialService() {
1035 return mInitialService;
1036 }
1037
1038
1039
1040
1041 /*** Set the basic config directory. This is the directory that will be
1042 * taked on to the package path of the classRelativeTo class. If this
1043 * property is non-null, the relative configuration subdirectory will
1044 * be ("data/" + baseConfigDirectory). */
1045 public String getBaseConfigDirectory() {
1046 return mBaseConfigDirectory;
1047 }
1048
1049
1050
1051
1052
1053 private String[] mLayers;
1054
1055 /*** Sets the Dynamo layers to run with. */
1056 public void setLayers(String[] pLayers) {
1057 mLayers = pLayers;
1058 }
1059
1060 /*** Returns the Dynamo layers to run with. */
1061 public String[] getLayers() {
1062 return mLayers;
1063 }
1064
1065 /*** Return the layers as a string appropriate for passing to
1066 * DynamoServerLauncher, calculateConfigPath.
1067 *
1068 * @return null if layers is null. Otherwise returns a space delimited
1069 * list of layers.
1070 */
1071 public String getLayersAsString() {
1072 if (mLayers == null) {
1073 return null;
1074 }
1075 StringBuilder strbuf = new StringBuilder();
1076 for (String strCur : mLayers) {
1077 if (strbuf.length() != 0) {
1078 strbuf.append(" ");
1079 }
1080 strbuf.append(strCur);
1081 }
1082 return strbuf.toString();
1083 }
1084
1085
1086
1087
1088 private boolean mLiveconfig;
1089
1090 /*** Sets property liveconfig.
1091 *
1092 * @param pLiveconfig true if Nucleus should be started in liveconfig
1093 * mode, false otherwise.
1094 */
1095 public void setLiveconfig(boolean pLiveconfig) {
1096 mLiveconfig = pLiveconfig;
1097 }
1098
1099 /*** Returns property liveconfig.
1100 * @return true if liveconfig should be set, false otherwise.
1101 */
1102 public boolean getLiveconfig() {
1103 return mLiveconfig;
1104 }
1105
1106 /*** Modify Nucleus command-line options, as needed. This will
1107 * be invoked just before Nucleus is started as a final
1108 * chance to adjust any command-line options.
1109 */
1110 public void modifyNucleusCommandLineOptions(List<String> listArgs) {
1111 return;
1112 }
1113 }
1114
1115 }