1 package com.atlassian.maven.plugins.amps.product.studio;
2
3 import static com.atlassian.maven.plugins.amps.product.ProductHandlerFactory.STUDIO;
4 import static com.atlassian.maven.plugins.amps.product.ProductHandlerFactory.STUDIO_BAMBOO;
5 import static com.atlassian.maven.plugins.amps.product.ProductHandlerFactory.STUDIO_CONFLUENCE;
6 import static com.atlassian.maven.plugins.amps.product.ProductHandlerFactory.STUDIO_CROWD;
7 import static com.atlassian.maven.plugins.amps.product.ProductHandlerFactory.STUDIO_FECRU;
8 import static com.atlassian.maven.plugins.amps.product.ProductHandlerFactory.STUDIO_JIRA;
9 import static com.atlassian.maven.plugins.amps.util.ZipUtils.unzip;
10 import static org.apache.commons.io.FileUtils.copyDirectory;
11
12 import java.io.BufferedReader;
13 import java.io.File;
14 import java.io.IOException;
15 import java.io.InputStreamReader;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Locale;
22 import java.util.Map;
23 import java.util.Set;
24
25 import org.apache.commons.io.FileUtils;
26 import org.apache.maven.plugin.MojoExecutionException;
27 import org.apache.maven.plugin.logging.Log;
28
29 import com.atlassian.maven.plugins.amps.MavenContext;
30 import com.atlassian.maven.plugins.amps.MavenGoals;
31 import com.atlassian.maven.plugins.amps.Product;
32 import com.atlassian.maven.plugins.amps.ProductArtifact;
33 import com.atlassian.maven.plugins.amps.ProductExecution;
34 import com.atlassian.maven.plugins.amps.product.AmpsProductHandler;
35 import com.atlassian.maven.plugins.amps.product.ProductHandler;
36 import com.atlassian.maven.plugins.amps.product.ProductHandlerFactory;
37 import com.atlassian.maven.plugins.amps.util.ConfigFileUtils;
38 import com.atlassian.maven.plugins.amps.util.ConfigFileUtils.Replacement;
39 import com.atlassian.maven.plugins.amps.util.ProjectUtils;
40 import com.google.common.collect.Lists;
41 import com.google.common.collect.Maps;
42 import com.google.common.collect.Sets;
43
44
45
46
47
48 final public class StudioProductHandler extends AmpsProductHandler
49 {
50
51 private static final String STUDIO_PROPERTIES = "home/studio.properties";
52 private static final String STUDIO_INITIAL_DATA_PROPERTIES = "home/studio-initial-data.properties";
53 private static final String DEVMODE_HAL_LICENSES_XML = "home/devmode-hal-licenses.xml";
54 private static final String STUDIO_INITIAL_DATA_XML = "home/studio-initial-data.xml";
55
56
57 private static final String STUDIO_VERSION_TOKEN = "STUDIO-VERSION";
58
59 private final static String LAUNCH_INSTANCES_SYSTEM_PROPERTY = "studio.instanceIds";
60
61 private final static Map<String, String> defaultContextPaths = new HashMap<String, String>()
62 {
63 {
64 put(STUDIO_BAMBOO, "/builds");
65 put(STUDIO_CONFLUENCE, "/wiki");
66 put(STUDIO_CROWD, "/crowd");
67 put(STUDIO_FECRU, "/");
68 put(STUDIO_JIRA, "/jira");
69 }
70 };
71
72 public StudioProductHandler(MavenContext context, MavenGoals goals)
73 {
74 super(context, goals);
75 }
76
77
78
79 @Override
80 protected ProductArtifact getTestResourcesArtifact()
81 {
82 return new ProductArtifact("com.atlassian.studio", "studio-test-resources");
83 }
84
85
86
87 @Override
88 public String getId()
89 {
90 return STUDIO;
91 }
92
93
94
95
96
97
98
99
100
101
102 public List<String> getDependantInstances(Product studioContext) throws MojoExecutionException
103 {
104 StudioProperties studioProperties = getStudioProperties(studioContext);
105 studioProperties.setStudioProduct(studioContext);
106 List<String> instanceIds = studioContext.getInstanceIds();
107 if (instanceIds.isEmpty())
108 {
109 instanceIds.add(STUDIO_CROWD);
110 instanceIds.add(STUDIO_JIRA);
111 instanceIds.add(STUDIO_CONFLUENCE);
112 instanceIds.add(STUDIO_FECRU);
113 instanceIds.add(STUDIO_BAMBOO);
114 }
115 return instanceIds;
116 }
117
118
119
120
121
122
123
124 public Set<String> getExcludedInstances(Product studioContext)
125 {
126 String restriction = System.getProperty(LAUNCH_INSTANCES_SYSTEM_PROPERTY);
127 if (restriction == null)
128 {
129 return null;
130 }
131 String[] restrictionList = restriction.split(",");
132 log.info(String.format("Excluding %s from the %s instance.", Arrays.toString(restrictionList), studioContext.getInstanceId()));
133 return Sets.newHashSet(restrictionList);
134 }
135
136
137
138
139
140 @Override
141 public int start(Product ctx) throws MojoExecutionException
142 {
143
144 sanityCheck(ctx);
145
146
147 createStudioHome(ctx);
148
149
150
151
152 File symlink = new File(context.getProject().getBuild().getDirectory(), "svn");
153 if (!symlink.exists())
154 {
155
156 createSymlink(ctx.getStudioProperties().getSvnRoot(), symlink);
157 }
158
159 return 0;
160 }
161
162
163
164
165 private void sanityCheck(Product studioProduct) throws MojoExecutionException
166 {
167 StudioProperties properties = studioProduct.getStudioProperties();
168 if (properties == null)
169 {
170 throw new MojoExecutionException(String.format("Something went wrong when starting %s. The 'studio' handler was not initialised propertly.",
171 studioProduct.getInstanceId()));
172 }
173 if (properties.getCrowd() == null || properties.getCrowd().getStudioProperties() == null)
174 {
175 log.error(String.format(
176 "You won't be able to run %s, Studio-Crowd was not configured properly.", studioProduct.getInstanceId()));
177 }
178 if (properties.isJiraEnabled() && (properties.getJira() == null || properties.getJira().getStudioProperties() == null))
179 {
180 log.error(String.format(
181 "You won't be able to run %s, Studio-JIRA was not configured properly.", studioProduct.getInstanceId()));
182 }
183 if (properties.isConfluenceEnabled() && (properties.getConfluence() == null || properties.getConfluence().getStudioProperties() == null))
184 {
185 log.error(String.format(
186 "You won't be able to run %s, Studio-Confluence was not configured properly.", studioProduct.getInstanceId()));
187 }
188 if (properties.isFisheyeEnabled() && (properties.getFisheye() == null || properties.getFisheye().getStudioProperties() == null))
189 {
190 log.error(String.format(
191 "You won't be able to run %s, Studio-Fisheye was not configured properly.", studioProduct.getInstanceId()));
192 }
193 if (properties.isBambooEnabled() && (properties.getBamboo() == null || properties.getBamboo().getStudioProperties() == null))
194 {
195 log.error(String.format(
196 "You won't be able to run %s, Studio-Bamboo was not configured properly.", studioProduct.getInstanceId()));
197 }
198 }
199
200 @Override
201 public void stop(Product ctx) throws MojoExecutionException
202 {
203
204 File symlink = new File(context.getProject().getBuild().getDirectory(), "svn");
205 symlink.deleteOnExit();
206
207
208 }
209
210 @Override
211 public int getDefaultHttpPort()
212 {
213
214 return 0;
215 }
216
217
218
219
220
221
222
223
224
225 public static void setDefaultValues(Product product)
226 {
227 String defaultContextPath = defaultContextPaths.get(product.getId());
228 if (defaultContextPath != null)
229 {
230
231 if (product.getOutput() == null)
232 {
233 product.setOutput("target/" + product.getInstanceId() + ".log");
234 }
235 if (product.getContextPath() == null)
236 {
237 product.setContextPath(defaultContextPath);
238 }
239 if (product.getVersion() == null)
240 {
241
242
243 product.setVersion(STUDIO_VERSION_TOKEN);
244 }
245 }
246 }
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263 public void configure(Product studioContext, List<ProductExecution> dependantProducts) throws MojoExecutionException
264 {
265 StudioProperties studioProperties = getStudioProperties(studioContext);
266
267 boolean confluenceStandalone = true;
268
269
270 for (ProductExecution execution : dependantProducts)
271 {
272
273 Product product = execution.getProduct();
274 if (STUDIO_CROWD.equals(product.getId()))
275 {
276 studioProperties.setCrowd(product);
277 }
278 else if (STUDIO_CONFLUENCE.equals(product.getId()))
279 {
280 studioProperties.setConfluence(product);
281 }
282 else if (STUDIO_JIRA.equals(product.getId()))
283 {
284 studioProperties.setJira(product);
285 confluenceStandalone = false;
286 }
287 else if (STUDIO_FECRU.equals(product.getId()))
288 {
289 studioProperties.setFisheye(product);
290 confluenceStandalone = false;
291 }
292 else if (STUDIO_BAMBOO.equals(product.getId()))
293 {
294 studioProperties.setBamboo(product);
295 confluenceStandalone = false;
296 }
297 else
298 {
299 throw new MojoExecutionException("A non-studio product was listed in a Studio instance: " + product.getInstanceId());
300 }
301
302 studioProperties.setModeConfluenceStandalone(confluenceStandalone);
303
304
305 product.setStudioProperties(studioProperties);
306
307
308 if (STUDIO_VERSION_TOKEN.equals(product.getVersion()))
309 {
310 product.setVersion(studioProperties.getVersion());
311 }
312 }
313 }
314
315
316
317
318
319
320
321
322
323 private static StudioProperties getStudioProperties(Product studioContext)
324 {
325 StudioProperties properties = studioContext.getStudioProperties();
326 if (properties == null)
327 {
328 properties = new StudioProperties(studioContext);
329 studioContext.setStudioProperties(properties);
330 }
331 return properties;
332 }
333
334
335
336
337
338
339
340
341
342
343
344 @Override
345 public File getSnapshotDirectory(Product studio)
346 {
347 return getHomeDirectory(studio).getParentFile();
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385 public void createStudioHome(Product studio) throws MojoExecutionException
386 {
387 StudioProperties properties = getStudioProperties(studio);
388
389
390 File studioHomeDir = getHomeDirectory(studio);
391 File studioCommonsDir = studioHomeDir.getParentFile();
392 String studioProductDataOrigin = studioCommonsDir.getAbsolutePath();
393
394
395 if (!studioHomeDir.exists())
396 {
397 extractHome(studioCommonsDir, studio);
398 if (!studioHomeDir.exists())
399 {
400 throw new MojoExecutionException(studioProductDataOrigin + "studio-test-resources.zip must contain a 'xx/xx/home' folder");
401 }
402 }
403
404 File svnHomeDir = new File(studioCommonsDir, "svn-home");
405 if (!svnHomeDir.exists())
406 {
407 throw new MojoExecutionException(studioProductDataOrigin + " must contain a 'xx/xx/svn-home' folder");
408 }
409
410 File webDavDir = new File(studioCommonsDir, "webdav-home");
411 if (!webDavDir.exists())
412 {
413 throw new MojoExecutionException(studioProductDataOrigin + " must contain a 'xx/xx/webdav-home' folder");
414 }
415
416 String svnPublicUrl;
417 if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows"))
418 {
419 svnPublicUrl = "file:///" + svnHomeDir.getAbsolutePath();
420 }
421 else
422 {
423 svnPublicUrl = "file://" + svnHomeDir.getAbsolutePath();
424 }
425
426 properties.setStudioHome(studioHomeDir.getAbsolutePath());
427 properties.setSvnRoot(svnHomeDir.getAbsolutePath());
428 properties.setSvnPublicUrl(svnPublicUrl);
429 properties.setWebDavHome(webDavDir.getAbsolutePath());
430
431
432 parameteriseFiles(studioCommonsDir, studio);
433
434
435 properties.overrideSystemProperty("studio.home", studioHomeDir.getAbsolutePath());
436 properties.overrideSystemProperty("studio.initial.data.xml", new File(studioCommonsDir, STUDIO_INITIAL_DATA_XML).getAbsolutePath());
437 properties.overrideSystemProperty("studio.initial.data.properties", new File(studioCommonsDir, STUDIO_INITIAL_DATA_PROPERTIES).getAbsolutePath());
438 properties.overrideSystemProperty("studio.hal.instance.uri", new File(studioCommonsDir, DEVMODE_HAL_LICENSES_XML).getAbsolutePath());
439
440
441
442 Product crowd = properties.getCrowd();
443 if (crowd.getDataPath() == null)
444 {
445 crowd.setDataPath(new File(studioCommonsDir, "crowd-home").getAbsolutePath());
446 }
447
448 Product confluence = properties.getConfluence();
449 if (confluence != null && confluence.getDataPath() == null)
450 {
451 confluence.setDataPath(new File(studioCommonsDir, "confluence-home").getAbsolutePath());
452 }
453
454 Product jira = properties.getJira();
455 if (jira != null && jira.getDataPath() == null)
456 {
457 jira.setDataPath(new File(studioCommonsDir, "jira-home").getAbsolutePath());
458 }
459
460 Product bamboo = properties.getBamboo();
461 if (bamboo != null && bamboo.getDataPath() == null)
462 {
463 bamboo.setDataPath(new File(studioCommonsDir, "bamboo-home").getAbsolutePath());
464 }
465
466 Product fecru = properties.getFisheye();
467 if (fecru != null && fecru.getDataPath() == null)
468 {
469 fecru.setDataPath(new File(studioCommonsDir, "fecru-home").getAbsolutePath());
470 }
471 }
472
473 private void parameteriseFiles(File studioSnapshotCopyDir, Product studio) throws MojoExecutionException
474 {
475 ConfigFileUtils.replace(getConfigFiles(studio, studioSnapshotCopyDir), getReplacements(studio), false, log);
476 }
477
478 @Override
479 public List<File> getConfigFiles(Product studio, File studioSnapshotDir)
480 {
481 List<File> list = Lists.newArrayList();
482 list.add(new File(studioSnapshotDir, STUDIO_PROPERTIES));
483 list.add(new File(studioSnapshotDir, STUDIO_INITIAL_DATA_PROPERTIES));
484 list.add(new File(studioSnapshotDir, STUDIO_INITIAL_DATA_XML));
485 list.add(new File(studioSnapshotDir, DEVMODE_HAL_LICENSES_XML));
486 return list;
487 }
488
489
490
491
492 @Override
493 public List<Replacement> getReplacements(final Product studio)
494 {
495 List<Replacement> replacements = super.getReplacements(studio);
496 replacements.addAll(new ArrayList<Replacement>()
497 {
498 private void putNonReversibleIfNotNull(String key, String value)
499 {
500 if (value != null)
501 {
502 add(new Replacement(key, value, false));
503 }
504 }
505
506 private void putIfNotNull(String key, String value)
507 {
508 if (value != null)
509 {
510 add(new Replacement(key, value));
511 }
512 }
513
514
515 {
516 StudioProperties properties = studio.getStudioProperties();
517 putIfNotNull("%GREENHOPPER-LICENSE%", "test-classes/greenhopper.license");
518
519 if (properties.isJiraEnabled())
520 {
521 File attachmentsFolder = new File(getHomeDirectory(properties.getJira()), "attachments");
522 putIfNotNull("%JIRA-ATTACHMENTS%", attachmentsFolder.getAbsolutePath());
523 putIfNotNull("%JIRA-BASE-URL%", properties.getJiraUrl());
524 putIfNotNull("%JIRA-HOST-URL%", properties.getJiraHostUrl());
525 putIfNotNull("%JIRA-CONTEXT%", properties.getJiraContextPath());
526 }
527
528 if (properties.isConfluenceEnabled())
529 {
530 putIfNotNull("%CONFLUENCE-BASE-URL%", properties.getConfluenceUrl());
531 putIfNotNull("%CONFLUENCE-HOST-URL%", properties.getConfluenceHostUrl());
532 putIfNotNull("%CONFLUENCE-CONTEXT%", properties.getConfluenceContextPath());
533 }
534
535 if (properties.isFisheyeEnabled())
536 {
537 putIfNotNull("%FISHEYE-BASE-URL%", properties.getFisheyeUrl());
538 putIfNotNull("%FISHEYE-HOST-URL%", properties.getFisheyeHostUrl());
539 putIfNotNull("%FISHEYE-CONTROL-PORT%", properties.getFisheyeControlPort());
540 putNonReversibleIfNotNull("%FISHEYE-CONTEXT%", properties.getFisheyeContextPath());
541 putIfNotNull("%FISHEYE-SHUTDOWN-ENABLED%", properties.getFisheyeShutdownEnabled());
542 }
543
544 if (properties.isBambooEnabled())
545 {
546 putIfNotNull("%BAMBOO-BASE-URL%", properties.getBambooUrl());
547 putIfNotNull("%BAMBOO-HOST-URL%", properties.getBambooHostUrl());
548 putIfNotNull("%BAMBOO-CONTEXT%", properties.getBambooContextPath());
549 putNonReversibleIfNotNull("%BAMBOO-ENABLED%", "true");
550 }
551 else
552 {
553 putNonReversibleIfNotNull("%BAMBOO-ENABLED%", "false");
554 }
555
556 putIfNotNull("%CROWD-BASE-URL%", properties.getCrowdUrl());
557 putIfNotNull("%CROWD-HOST-URL%", properties.getCrowdHostUrl());
558 putIfNotNull("%CROWD-CONTEXT%", properties.getCrowdContextPath());
559
560 putIfNotNull("%SVN-BASE-URL%", properties.getSvnRoot());
561 putIfNotNull("%SVN-PUBLIC-URL%", properties.getSvnPublicUrl());
562 putIfNotNull("%SVN-HOOKS%", properties.getSvnHooks());
563
564 putNonReversibleIfNotNull("%STUDIO-DATA-LOCATION%", "");
565 putIfNotNull("%STUDIO-HOME%", properties.getStudioHome());
566 putNonReversibleIfNotNull("%GAPPS-ENABLED%", Boolean.toString(properties.isGappsEnabled()));
567 if (properties.isGappsEnabled())
568 {
569 putNonReversibleIfNotNull("%GAPPS-ENABLED%", Boolean.toString(true));
570 putIfNotNull("%STUDIO-GAPPS-DOMAIN%", properties.getGappsDomain());
571 }
572 else
573 {
574 putNonReversibleIfNotNull("%GAPPS-ENABLED%", Boolean.toString(false));
575 }
576 putIfNotNull("%STUDIO-WEBDAV-DIRECTORY%", properties.getWebDavHome());
577 putIfNotNull("%STUDIO-SVN-IMPORT-STAGING-DIRECTORY%", properties.getSvnImportStagingDirectory());
578 }
579 });
580 return replacements;
581 }
582
583 private void createSymlink(String source, File target) throws MojoExecutionException
584 {
585 String[] systemCommand = {
586 "ln",
587 "-s",
588 source,
589 target.getAbsolutePath()
590 };
591 try
592 {
593
594 Process symlinkCreation = Runtime.getRuntime().exec(systemCommand);
595
596
597 BufferedReader errorStream = new BufferedReader(
598 new InputStreamReader(symlinkCreation.getErrorStream()));
599 String errorLine = null;
600 boolean hasErrors = false;
601 while ((errorLine = errorStream.readLine()) != null)
602 {
603 if (!hasErrors)
604 {
605 System.err.println("Error while executing " + systemCommand + ": ");
606 hasErrors = true;
607 }
608 System.err.println(errorLine);
609 }
610 if (hasErrors)
611 {
612 System.out.println("Please execute this command in your command line and press a key to continue");
613 System.in.read();
614 }
615 }
616 catch (IOException e)
617 {
618 throw new MojoExecutionException("Could not create the symlink: " + systemCommand, e);
619 }
620 }
621
622
623
624
625
626 private void extractHome(File target, Product studio) throws MojoExecutionException
627 {
628
629 File testResourcesZip = getProductHomeData(studio);
630
631 try
632 {
633 if (!testResourcesZip.exists())
634 {
635 throw new MojoExecutionException(String.format("This source doesn't exist: %s", testResourcesZip.getAbsoluteFile()));
636 }
637 if (testResourcesZip.isDirectory())
638 {
639 copyDirectory(testResourcesZip, target);
640 }
641 else
642 {
643 unzip(testResourcesZip, target.getAbsolutePath(), 2);
644 }
645 }
646 catch (IOException ioe)
647 {
648 throw new MojoExecutionException(String.format("Unable to copy/unzip the studio home from %s to %s", testResourcesZip.getAbsolutePath(),
649 target.getAbsolutePath()), ioe);
650 }
651 }
652
653
654
655
656 static void processProductsHomeDirectory(Log log, Product ctx, File homeDir) throws MojoExecutionException
657 {
658
659
660 if (ctx.getStudioProperties() == null)
661 {
662 throw new MojoExecutionException(String.format("%s product is dependant on Studio. You must include the Studio product in your execution.",
663 ctx.getInstanceId()));
664 }
665 }
666
667
668
669
670
671
672
673
674
675
676
677
678 static void addProductHandlerOverrides(Log log, Product ctx, File homeDir, File explodedWarDir) throws MojoExecutionException
679 {
680 addProductHandlerOverrides(log, ctx, homeDir, explodedWarDir, "WEB-INF/classes/crowd.properties");
681 }
682
683
684
685
686
687
688
689
690
691
692
693
694 static void addProductHandlerOverrides(Log log, Product ctx, File homeDir, File explodedWarDir, String crowdPropertiesPath) throws MojoExecutionException
695 {
696 File crowdProperties = new File(explodedWarDir, crowdPropertiesPath);
697 if (checkFileExists(crowdProperties, log))
698 {
699 parametriseCrowdFile(crowdProperties, ctx.getStudioProperties().getCrowdUrl(), log);
700 }
701 }
702
703
704
705
706
707
708
709
710
711
712
713 private static void parametriseCrowdFile(File crowdProperties, String crowdUrl, Log log) throws MojoExecutionException
714 {
715 List<Replacement> replacements = Lists.newArrayList();
716 replacements.add(new Replacement("%CROWD-INTERNAL-URL%", crowdUrl));
717 replacements.add(new Replacement("%CROWD-URL%", crowdUrl));
718
719 ConfigFileUtils.replace(crowdProperties, replacements, false, log);
720 }
721
722
723 public void cleanupProductHomeForZip(Product studioProduct, File studioHome) throws MojoExecutionException
724 {
725 try
726 {
727
728 StudioProperties studioProperties = studioProduct.getStudioProperties();
729
730
731
732 Map<String, Product> products = Maps.newHashMap();
733 products.put("crowd-home", studioProperties.getCrowd());
734 products.put("confluence-home", studioProperties.getConfluence());
735 products.put("jira-home", studioProperties.getJira());
736 products.put("fecru-home", studioProperties.getFisheye());
737 products.put("bamboo-home", studioProperties.getBamboo());
738
739
740 Iterator<String> productKeys = products.keySet().iterator();
741 while (productKeys.hasNext())
742 {
743 String productHomeName = productKeys.next();
744 Product product = products.get(productHomeName);
745 if (product != null)
746 {
747 File productDestinationDirectory = new File(studioHome, productHomeName);
748 copyAndCleanProductHome(product, productDestinationDirectory);
749 }
750 }
751
752
753
754 super.cleanupProductHomeForZip(studioProduct, studioHome);
755 }
756 catch (IOException ioe)
757 {
758 throw new MojoExecutionException("Could not copy a product home directory.", ioe);
759 }
760
761 }
762
763 private void copyAndCleanProductHome(Product product, File productDestinationDirectory) throws IOException, MojoExecutionException
764 {
765 ProductHandler handler = ProductHandlerFactory.create(product.getId(), context, goals);
766 File productHomeDirectory = getHomeDirectory(product);
767
768
769 if (productDestinationDirectory.exists())
770 {
771 FileUtils.deleteDirectory(productDestinationDirectory);
772 }
773 ProjectUtils.createDirectory(productDestinationDirectory);
774 copyDirectory(productHomeDirectory, productDestinationDirectory);
775
776
777 handler.cleanupProductHomeForZip(product, productDestinationDirectory);
778 }
779
780 static String fixWindowsSlashes(final String path)
781 {
782 return path.replaceAll("\\\\", "/");
783 }
784
785 static boolean checkFileExists(File file, Log log)
786 {
787 if (!file.exists())
788 {
789 log.warn(String.format("%s does not exist. Will skip customisation", file.getAbsolutePath()));
790 return false;
791 }
792 return true;
793 }
794
795
796
797
798
799
800
801 public static <T> T firstNotNull(T... values)
802 {
803 for (T t : values)
804 {
805 if (t != null)
806 {
807 return t;
808 }
809 }
810 return null;
811 }
812 }