1 package org.codehaus.mojo.tomcat;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 import java.io.File;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.net.InetAddress;
27 import java.net.MalformedURLException;
28 import java.net.URL;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Set;
35
36 import org.apache.catalina.Context;
37 import org.apache.catalina.Engine;
38 import org.apache.catalina.Host;
39 import org.apache.catalina.LifecycleException;
40 import org.apache.catalina.connector.Connector;
41 import org.apache.catalina.loader.WebappLoader;
42 import org.apache.catalina.realm.MemoryRealm;
43 import org.apache.catalina.startup.Catalina;
44 import org.apache.catalina.startup.Embedded;
45 import org.apache.maven.artifact.Artifact;
46 import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
47 import org.apache.maven.plugin.MojoExecutionException;
48 import org.apache.maven.plugin.MojoFailureException;
49 import org.apache.maven.project.MavenProject;
50 import org.codehaus.plexus.archiver.ArchiverException;
51 import org.codehaus.plexus.archiver.UnArchiver;
52 import org.codehaus.plexus.archiver.manager.ArchiverManager;
53 import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
54 import org.codehaus.plexus.classworlds.ClassWorld;
55 import org.codehaus.plexus.classworlds.realm.ClassRealm;
56 import org.codehaus.plexus.classworlds.realm.DuplicateRealmException;
57 import org.codehaus.plexus.util.DirectoryScanner;
58 import org.codehaus.plexus.util.FileUtils;
59
60 import org.w3c.dom.Document;
61 import org.w3c.dom.NamedNodeMap;
62 import org.w3c.dom.Node;
63 import org.xml.sax.SAXException;
64
65 import javax.xml.parsers.DocumentBuilder;
66 import javax.xml.parsers.DocumentBuilderFactory;
67 import javax.xml.parsers.ParserConfigurationException;
68
69
70
71
72
73
74
75
76 public abstract class AbstractRunMojo
77 extends AbstractI18NMojo
78 {
79
80
81
82
83
84
85
86
87
88
89
90 private String packaging;
91
92
93
94
95
96
97 private File configurationDir;
98
99
100
101
102
103
104 private int port;
105
106
107
108
109
110
111
112
113 private int ajpPort;
114
115
116
117
118
119
120
121
122 private String ajpProtocol;
123
124
125
126
127
128
129
130
131 private int httpsPort;
132
133
134
135
136
137
138 private String uriEncoding;
139
140
141
142
143
144
145
146 private Map<String, String> systemProperties;
147
148
149
150
151
152
153
154 private File additionalConfigFilesDir;
155
156
157
158
159
160
161
162 private File serverXml;
163
164
165
166
167
168
169
170 private File tomcatWebXml;
171
172
173
174
175
176
177
178
179 private boolean fork;
180
181
182
183
184
185
186
187
188
189
190
191
192 private boolean addContextWarDependencies;
193
194
195
196
197
198
199
200
201
202 protected MavenProject project;
203
204
205
206
207
208
209 private ArchiverManager archiverManager;
210
211
212
213
214
215
216 protected boolean useSeparateTomcatClassLoader;
217
218
219
220
221
222
223 @SuppressWarnings("rawtypes")
224 private List pluginArtifacts;
225
226
227
228
229
230
231 private boolean ignorePackaging;
232
233
234
235
236
237
238 private String keystoreFile;
239
240
241
242
243
244
245 private String keystorePass;
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 private boolean useNaming;
268
269
270
271
272
273
274
275
276 protected boolean contextReloadable;
277
278
279
280
281
282
283
284 protected File contextFile;
285
286
287
288
289
290
291
292
293 private ClassRealm tomcatRealm;
294
295
296
297
298
299
300
301
302 public void execute()
303 throws MojoExecutionException, MojoFailureException
304 {
305
306 if ( !isWar() )
307 {
308 getLog().info( getMessage( "AbstractRunMojo.nonWar" ) );
309 return;
310 }
311 ClassLoader originalClassLoaser = Thread.currentThread().getContextClassLoader();
312 try
313 {
314 if ( useSeparateTomcatClassLoader )
315 {
316 Thread.currentThread().setContextClassLoader( getTomcatClassLoader() );
317 }
318 getLog().info( getMessage( "AbstractRunMojo.runningWar", getWebappUrl() ) );
319
320 initConfiguration();
321 startContainer();
322 if ( !fork )
323 {
324 waitIndefinitely();
325 }
326 }
327 catch ( LifecycleException exception )
328 {
329 throw new MojoExecutionException( getMessage( "AbstractRunMojo.cannotStart" ), exception );
330 }
331 catch ( IOException exception )
332 {
333 throw new MojoExecutionException( getMessage( "AbstractRunMojo.cannotCreateConfiguration" ), exception );
334 } finally
335 {
336 if (useSeparateTomcatClassLoader)
337 {
338 Thread.currentThread().setContextClassLoader( originalClassLoaser );
339 }
340 }
341 }
342
343
344
345
346
347
348
349
350
351
352 protected String getPath()
353 {
354 return path;
355 }
356
357
358
359
360
361
362
363
364
365 protected Context createContext( Embedded container )
366 throws IOException, MojoExecutionException
367 {
368 String contextPath = getPath();
369 Context context = container.createContext( "/".equals( contextPath ) ? "" : contextPath, getDocBase()
370 .getAbsolutePath() );
371
372 if ( useSeparateTomcatClassLoader )
373 {
374 context.setParentClassLoader( getTomcatClassLoader() );
375 }
376
377 context.setLoader( createWebappLoader() );
378 File contextFile = getContextFile();
379 if (contextFile != null)
380 {
381 context.setConfigFile( getContextFile().getAbsolutePath() );
382 }
383 return context;
384 }
385
386
387
388
389
390
391
392
393 protected WebappLoader createWebappLoader()
394 throws IOException, MojoExecutionException
395 {
396 if ( useSeparateTomcatClassLoader )
397 {
398 return ( isContextReloadable() )
399 ? new ExternalRepositoriesReloadableWebappLoader( getTomcatClassLoader(), getLog() )
400 : new WebappLoader( getTomcatClassLoader() );
401 }
402
403 return ( isContextReloadable() )
404 ? new ExternalRepositoriesReloadableWebappLoader( Thread.currentThread().getContextClassLoader(), getLog() )
405 : new WebappLoader( Thread.currentThread().getContextClassLoader() );
406 }
407
408
409
410
411
412 protected boolean isContextReloadable() throws MojoExecutionException
413 {
414 if ( contextReloadable )
415 {
416 return true;
417 }
418
419 boolean reloadable = false;
420 try
421 {
422 if ( contextFile !=null && contextFile.exists() )
423 {
424 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
425 DocumentBuilder builder = builderFactory.newDocumentBuilder();
426 Document contextDoc = builder.parse( contextFile );
427 contextDoc.getDocumentElement().normalize();
428
429 NamedNodeMap nodeMap = contextDoc.getDocumentElement().getAttributes();
430 Node reloadableAttribute = nodeMap.getNamedItem( "reloadable" );
431
432 reloadable = ( reloadableAttribute != null ) ? Boolean.valueOf( reloadableAttribute.getNodeValue() )
433 : false;
434 }
435 getLog().debug( "context reloadable: " + reloadable );
436 }
437 catch ( IOException ioe )
438 {
439 getLog().error( "Could not parse file: [" + contextFile.getAbsolutePath() + "]", ioe );
440 }
441 catch ( ParserConfigurationException pce )
442 {
443 getLog().error( "Could not configure XML parser", pce );
444 }
445 catch ( SAXException se )
446 {
447 getLog().error( "Could not parse file: [" + contextFile.getAbsolutePath() + "]", se );
448 }
449
450 return reloadable;
451 }
452
453
454
455
456
457
458
459 protected abstract File getDocBase();
460
461
462
463
464
465
466 protected abstract File getContextFile() throws MojoExecutionException;
467
468
469
470
471
472
473
474
475
476
477 protected boolean isWar()
478 {
479 return "war".equals( packaging ) || ignorePackaging;
480 }
481
482
483
484
485
486
487
488 private URL getWebappUrl()
489 throws MalformedURLException
490 {
491 return new URL( "http", "localhost", port, getPath() );
492 }
493
494
495
496
497
498
499
500 private void initConfiguration()
501 throws IOException, MojoExecutionException
502 {
503 if ( configurationDir.exists() )
504 {
505 getLog().info( getMessage( "AbstractRunMojo.usingConfiguration", configurationDir ) );
506 }
507 else
508 {
509 getLog().info( getMessage( "AbstractRunMojo.creatingConfiguration", configurationDir ) );
510
511 configurationDir.mkdirs();
512
513 File confDir = new File( configurationDir, "conf");
514 confDir.mkdir();
515
516 copyFile("/conf/tomcat-users.xml", new File( confDir, "tomcat-users.xml" ) );
517 if ( tomcatWebXml != null )
518 {
519 if ( !tomcatWebXml.exists() )
520 {
521 throw new MojoExecutionException( " tomcatWebXml " + tomcatWebXml.getPath() + " not exists" );
522 }
523
524 FileUtils.copyFile( tomcatWebXml, new File( confDir, "web.xml" ) );
525
526 }
527 else
528 {
529 copyFile("/conf/web.xml", new File( confDir, "web.xml" ) );
530 }
531
532 File logDir = new File( configurationDir, "logs" );
533 logDir.mkdir();
534
535 File webappsDir = new File( configurationDir, "webapps" );
536 webappsDir.mkdir();
537
538 if ( additionalConfigFilesDir != null && additionalConfigFilesDir.exists() )
539 {
540 DirectoryScanner scanner = new DirectoryScanner();
541 scanner.addDefaultExcludes();
542 scanner.setBasedir( additionalConfigFilesDir.getPath() );
543 scanner.scan();
544
545 String[] files = scanner.getIncludedFiles();
546
547 if ( files != null && files.length > 0 )
548 {
549 getLog().info( "Coping additional tomcat config files" );
550
551 for ( int i = 0; i < files.length; i++ )
552 {
553 File file = new File( additionalConfigFilesDir, files[i] );
554
555 getLog().info( " copy " + file.getName() );
556
557 FileUtils.copyFileToDirectory( file, confDir );
558 }
559 }
560 }
561 }
562 }
563
564
565
566
567
568
569
570
571 private void copyFile( String fromPath, File toFile )
572 throws IOException
573 {
574 URL fromURL = getClass().getResource( fromPath );
575
576 if ( fromURL == null )
577 {
578 throw new FileNotFoundException( fromPath );
579 }
580
581 FileUtils.copyURLToFile( fromURL, toFile );
582 }
583
584
585
586
587
588
589
590
591 private void startContainer()
592 throws IOException, LifecycleException, MojoExecutionException
593 {
594
595
596 setupSystemProperties();
597 final Embedded container;
598 if ( serverXml != null )
599 {
600 if ( !serverXml.exists() )
601 {
602 throw new MojoExecutionException( serverXml.getPath() + " not exists" );
603 }
604
605 container = new Catalina();
606 container.setCatalinaHome( configurationDir.getAbsolutePath() );
607 ( (Catalina) container).setConfigFile( serverXml.getPath() );
608 container.start();
609 }
610 else
611 {
612
613 container = new Embedded();
614 container.setCatalinaHome( configurationDir.getAbsolutePath() );
615 container.setRealm( new MemoryRealm() );
616 container.setUseNaming( useNaming );
617
618
619
620
621 Context context = createContext(container);
622
623
624
625 String appBase = new File( configurationDir, "webapps" ).getAbsolutePath();
626 Host host = container.createHost( "localHost", appBase );
627
628 host.addChild( context );
629
630 if ( addContextWarDependencies )
631 {
632 Collection<Context> dependecyContexts = createDependencyContexts(container);
633 for ( Context extraContext : dependecyContexts )
634 {
635 host.addChild( extraContext );
636 }
637 }
638
639
640 Engine engine = container.createEngine();
641 engine.setName( "localEngine" );
642 engine.addChild( host );
643 engine.setDefaultHost( host.getName() );
644 container.addEngine( engine );
645 if ( useSeparateTomcatClassLoader )
646 {
647 engine.setParentClassLoader( getTomcatClassLoader() );
648 }
649
650 Connector httpConnector = container.createConnector( (InetAddress) null, port, false );
651 if ( httpsPort > 0 )
652 {
653 httpConnector.setRedirectPort( httpsPort );
654 }
655 httpConnector.setURIEncoding( uriEncoding );
656 container.addConnector( httpConnector );
657
658
659 if ( httpsPort > 0 )
660 {
661 Connector httpsConnector = container.createConnector( (InetAddress) null, httpsPort, true );
662 if ( keystoreFile!=null)
663 {
664 httpsConnector.setAttribute("keystoreFile", keystoreFile);
665 }
666 if ( keystorePass!=null)
667 {
668 httpsConnector.setAttribute("keystorePass", keystorePass);
669 }
670 container.addConnector( httpsConnector );
671
672 }
673
674
675 if ( ajpPort > 0 )
676 {
677 Connector ajpConnector = container.createConnector( (InetAddress) null, ajpPort, ajpProtocol );
678 ajpConnector.setURIEncoding( uriEncoding );
679 container.addConnector( ajpConnector );
680 }
681 }
682 container.start();
683
684 EmbeddedRegistry.getInstance().register(container);
685 }
686
687 protected ClassRealm getTomcatClassLoader()
688 throws MojoExecutionException
689 {
690 if ( this.tomcatRealm != null )
691 {
692 return tomcatRealm;
693 }
694 try
695 {
696 ClassWorld world = new ClassWorld();
697 ClassRealm root = world.newRealm( "tomcat", Thread.currentThread().getContextClassLoader() );
698
699 for ( @SuppressWarnings("rawtypes")
700 Iterator i = pluginArtifacts.iterator(); i.hasNext(); )
701 {
702 Artifact pluginArtifact = (Artifact) i.next();
703 if ( "org.apache.tomcat".equals( pluginArtifact.getGroupId() ) )
704 {
705 if ( pluginArtifact.getFile() != null )
706 {
707 root.addURL( pluginArtifact.getFile().toURI().toURL() );
708 }
709 }
710 }
711 tomcatRealm = root;
712 return root;
713 }
714 catch ( DuplicateRealmException e )
715 {
716 throw new MojoExecutionException( e.getMessage(), e );
717 }
718 catch ( MalformedURLException e )
719 {
720 throw new MojoExecutionException( e.getMessage(), e );
721 }
722 }
723
724 @SuppressWarnings( "unchecked" )
725 public Set<Artifact> getProjectArtifacts()
726 {
727 return project.getArtifacts();
728 }
729
730
731
732
733 private void waitIndefinitely()
734 {
735 Object lock = new Object();
736
737 synchronized ( lock )
738 {
739 try
740 {
741 lock.wait();
742 }
743 catch ( InterruptedException exception )
744 {
745 getLog().warn( getMessage( "AbstractRunMojo.interrupted" ), exception );
746 }
747 }
748 }
749
750
751
752
753
754 private void setupSystemProperties()
755 {
756 if ( systemProperties != null && !systemProperties.isEmpty() )
757 {
758 getLog().info( "setting SystemProperties:" );
759
760 for ( String key : systemProperties.keySet() )
761 {
762 String value = systemProperties.get( key );
763
764 if ( value != null )
765 {
766 getLog().info( " " + key + "=" + value );
767 System.setProperty( key, value );
768 }
769 else
770 {
771 getLog().info( "skip sysProps " + key + " with empty value" );
772 }
773 }
774 }
775 }
776
777
778
779
780
781
782
783
784
785 private Collection<Context> createDependencyContexts( Embedded container ) throws MojoExecutionException
786 {
787 getLog().info( "Deploying dependency wars" );
788
789 List<Context> contexts = new ArrayList<Context>();
790
791 ScopeArtifactFilter filter = new ScopeArtifactFilter( "tomcat" );
792 @SuppressWarnings("unchecked")
793 Set<Artifact> artifacts = project.getArtifacts();
794 for ( Artifact artifact : artifacts )
795 {
796
797
798
799 if ( "war".equals( artifact.getType() ) && !artifact.isOptional() && filter.include( artifact ) )
800 {
801 getLog().info( "Deploy warfile: " + String.valueOf( artifact.getFile() ) );
802 File webapps = new File( configurationDir, "webapps" );
803 File artifactWarDir = new File( webapps, artifact.getArtifactId() );
804 if ( !artifactWarDir.exists() )
805 {
806
807 artifactWarDir.mkdir();
808 try
809 {
810 UnArchiver unArchiver = archiverManager.getUnArchiver( "zip" );
811 unArchiver.setSourceFile( artifact.getFile() );
812 unArchiver.setDestDirectory( artifactWarDir );
813
814
815 unArchiver.extract();
816 }
817 catch ( IOException e )
818 {
819 getLog().error( e );
820 continue;
821 }
822 catch ( NoSuchArchiverException e )
823 {
824 getLog().error( e );
825 continue;
826 }
827 catch ( ArchiverException e )
828 {
829 getLog().error( e );
830 continue;
831 }
832 }
833 WebappLoader webappLoader = new WebappLoader( Thread.currentThread().getContextClassLoader() );
834 Context context = container.createContext( "/" + artifact.getArtifactId(), artifactWarDir.getAbsolutePath() );
835 context.setLoader( webappLoader );
836 File contextFile = getContextFile();
837 if (contextFile != null)
838 {
839 context.setConfigFile( getContextFile().getAbsolutePath() );
840 }
841 contexts.add( context );
842
843 }
844 }
845 return contexts;
846 }
847 }