1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.kathrynhuxtable.maven.plugins.htmlfiltersite;
21
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileWriter;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.io.Reader;
28 import java.io.StringReader;
29 import java.io.StringWriter;
30 import java.io.UnsupportedEncodingException;
31 import java.text.DateFormat;
32 import java.text.SimpleDateFormat;
33 import java.util.ArrayList;
34 import java.util.Date;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Locale;
38
39 import org.apache.maven.artifact.repository.ArtifactRepository;
40 import org.apache.maven.doxia.site.decoration.DecorationModel;
41 import org.apache.maven.doxia.tools.SiteTool;
42 import org.apache.maven.doxia.tools.SiteToolException;
43 import org.apache.maven.plugin.AbstractMojo;
44 import org.apache.maven.plugin.MojoExecutionException;
45 import org.apache.maven.project.MavenProject;
46 import org.apache.velocity.Template;
47 import org.apache.velocity.VelocityContext;
48 import org.apache.velocity.app.VelocityEngine;
49 import org.apache.velocity.exception.MethodInvocationException;
50 import org.apache.velocity.exception.ParseErrorException;
51 import org.apache.velocity.exception.ResourceNotFoundException;
52 import org.codehaus.plexus.i18n.DefaultI18N;
53 import org.codehaus.plexus.i18n.I18N;
54 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
55 import org.codehaus.plexus.util.FileUtils;
56 import org.codehaus.plexus.util.PathTool;
57 import org.codehaus.plexus.util.ReaderFactory;
58 import org.codehaus.plexus.util.StringUtils;
59 import org.jdom.Document;
60 import org.jdom.Element;
61 import org.jdom.JDOMException;
62 import org.jdom.input.SAXBuilder;
63 import org.jdom.output.Format;
64 import org.jdom.xpath.XPath;
65
66
67
68
69
70
71
72
73
74
75 public class MergeMojo extends AbstractMojo {
76
77
78
79
80
81
82
83 private String inputEncoding;
84
85
86
87
88
89
90
91 private String outputEncoding;
92
93
94
95
96
97
98
99
100
101 protected List<?> repositories;
102
103
104
105
106
107
108 protected ArtifactRepository localRepository;
109
110
111
112
113
114
115
116 private String filePattern;
117
118
119
120
121
122
123
124 private String filterExtension;
125
126
127
128
129
130
131
132
133 private File siteDirectory;
134
135
136
137
138
139
140
141 private File sourceDirectory;
142
143
144
145
146
147
148
149 private File targetDirectory;
150
151
152
153
154
155
156
157 private File templateFile;
158
159
160
161
162
163
164
165
166 protected MavenProject project;
167
168
169 private I18N i18n = new DefaultI18N();
170
171
172
173
174
175
176
177
178 protected List<?> reactorProjects;
179
180
181
182
183
184
185 private SiteTool siteTool;
186
187
188
189
190
191
192 public MavenProject getProject() {
193 return project;
194 }
195
196
197
198
199
200
201 public void setProject(MavenProject project) {
202 this.project = project;
203 }
204
205
206
207
208
209
210 protected String getInputEncoding() {
211 return (inputEncoding == null) ? ReaderFactory.ISO_8859_1 : inputEncoding;
212 }
213
214
215
216
217
218
219 public void setInputEncoding(String inputEncoding) {
220 this.inputEncoding = inputEncoding;
221 }
222
223
224
225
226
227
228 public void setOutputEncoding(String outputEncoding) {
229 this.outputEncoding = outputEncoding;
230 }
231
232
233
234
235
236
237
238 protected String getOutputEncoding() {
239 return (outputEncoding == null) ? ReaderFactory.UTF_8 : outputEncoding;
240 }
241
242
243
244
245
246
247 public void setRepositories(List<?> repositories) {
248 this.repositories = repositories;
249 }
250
251
252
253
254
255
256 public void setLocalRepository(ArtifactRepository localRepository) {
257 this.localRepository = localRepository;
258 }
259
260
261
262
263
264
265 public void setFilePattern(String filePattern) {
266 this.filePattern = filePattern;
267 }
268
269
270
271
272
273
274 public void setSiteDirectory(File siteDirectory) {
275 this.siteDirectory = siteDirectory;
276 }
277
278
279
280
281
282
283 public void setSourceDirectory(File sourceDirectory) {
284 this.sourceDirectory = sourceDirectory;
285 }
286
287
288
289
290
291
292 public void setTargetDirectory(File targetDirectory) {
293 this.targetDirectory = targetDirectory;
294 }
295
296
297
298
299
300
301 public void setTemplateFile(File templateFile) {
302 this.templateFile = templateFile;
303 }
304
305
306
307
308
309
310 public void setI18n(I18N i18n) {
311 this.i18n = i18n;
312 }
313
314
315
316
317
318
319 public void setReactorProjects(List<?> reactorProjects) {
320 this.reactorProjects = reactorProjects;
321 }
322
323
324
325
326
327
328 public void setSiteTool(SiteTool siteTool) {
329 this.siteTool = siteTool;
330 }
331
332
333
334
335 public void execute() throws MojoExecutionException {
336 VelocityEngine ve = initializeVelocityEngine();
337 Template template = getVelocityTemplate(ve);
338 AttributeMap attributes = new AttributeMap();
339
340 try {
341 ((DefaultI18N) i18n).initialize();
342 } catch (InitializationException e) {
343 e.printStackTrace();
344 throw new MojoExecutionException("Unable to initialize I18N object", e);
345 }
346
347 if (attributes.get("project") == null) {
348 attributes.put("project", project);
349 }
350
351
352 attributes.putAll(project.getProperties());
353
354 if (attributes.get("inputEncoding") == null) {
355 attributes.put("inputEncoding", getInputEncoding());
356 }
357
358 if (attributes.get("outputEncoding") == null) {
359 attributes.put("outputEncoding", getOutputEncoding());
360 }
361
362 DecorationModel decorationModel;
363
364 try {
365 decorationModel = siteTool.getDecorationModel(project, reactorProjects, localRepository, repositories,
366 getRelativeFilePath(project.getBasedir(), siteDirectory),
367 Locale.getDefault(), getInputEncoding(), getOutputEncoding());
368 } catch (SiteToolException e) {
369 throw new MojoExecutionException("SiteToolException: " + e.getMessage(), e);
370 }
371
372 attributes.put("currentDate", new Date());
373
374 attributes.put("lastPublished", new SimpleDateFormat("dd MMM yyyy").format(new Date()));
375
376 List<String> fileList = getFileList();
377
378 for (String file : fileList) {
379 mergeFile(file, ve, template, decorationModel, attributes);
380 }
381 }
382
383
384
385
386
387
388
389
390 private VelocityEngine initializeVelocityEngine() throws MojoExecutionException {
391 VelocityEngine ve = new VelocityEngine();
392
393 try {
394 ve.addProperty("resource.loader", "file, class");
395 ve.addProperty("class.resource.loader.description", "Velocity Classpath Resource Loader");
396 ve.addProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
397 ve.init();
398 } catch (Exception e) {
399 e.printStackTrace();
400 throw new MojoExecutionException("Unable to initialize Velocity engine", e);
401 }
402
403 return ve;
404 }
405
406
407
408
409
410
411
412
413
414
415 private Template getVelocityTemplate(VelocityEngine ve) throws MojoExecutionException {
416 Template template = null;
417
418 try {
419 template = ve.getTemplate(getRelativeFilePath(project.getBasedir(), templateFile));
420 } catch (ResourceNotFoundException e) {
421 e.printStackTrace();
422 throw new MojoExecutionException("Unable to locate template " + templateFile, e);
423 } catch (ParseErrorException e) {
424 e.printStackTrace();
425 throw new MojoExecutionException("Problem parsing the template", e);
426 } catch (MethodInvocationException e) {
427 e.printStackTrace();
428 throw new MojoExecutionException("Something invoked in the template threw an exception", e);
429 } catch (Exception e) {
430 e.printStackTrace();
431 throw new MojoExecutionException("Some random template parsing error occurred", e);
432 }
433
434 return template;
435 }
436
437
438
439
440
441
442
443
444
445
446
447
448 private void mergeFile(String file, VelocityEngine ve, Template template, DecorationModel decorationModel, AttributeMap attributes)
449 throws MojoExecutionException {
450 File sourceFile = new File(sourceDirectory, file);
451 boolean doFiltering = (filterExtension != null && filterExtension.equals(file.substring(file.length() - filterExtension.length())));
452
453 File targetFile = null;
454 FileWriter fileWriter = null;
455 Reader fileReader = null;
456
457 if (doFiltering) {
458 targetFile = new File(targetDirectory, file.substring(0, file.length() - filterExtension.length()) + ".html");
459 } else {
460 targetFile = new File(targetDirectory, file);
461 }
462
463 if (!targetFile.getParentFile().exists()) {
464 targetFile.getParentFile().mkdirs();
465 }
466
467 VelocityContext context = createContext(sourceFile, decorationModel, attributes);
468
469 try {
470 StringWriter sw = new StringWriter();
471
472 fileReader = new InputStreamReader(new FileInputStream(sourceFile), "UTF-8");
473
474 if (doFiltering) {
475 if (!ve.evaluate(context, sw, "htmlfilter-site", fileReader)) {
476 throw new MojoExecutionException("Unable to evaluate html file " + sourceFile);
477 }
478
479 closeReader(fileReader);
480 fileReader = new StringReader(sw.toString());
481 }
482
483 Document doc = parseXHTMLDocument(fileReader);
484
485 addInfoFromDocument(context, decorationModel, doc, null);
486
487 fileWriter = new FileWriter(targetFile);
488
489 template.merge(context, fileWriter);
490 } catch (Exception e) {
491 e.printStackTrace();
492 throw new MojoExecutionException("Unable to merge Velocity", e);
493 } finally {
494 closeReader(fileReader);
495 closeWriter(fileWriter);
496 }
497 }
498
499
500
501
502
503
504
505
506
507 private void addInfoFromDocument(VelocityContext context, DecorationModel decorationModel, Document doc, Date createDate) {
508 context.put("authors", getAuthors(doc));
509
510 String title = "";
511
512 if (decorationModel.getName() != null) {
513 title = decorationModel.getName();
514 } else if (project.getName() != null) {
515 title = project.getName();
516 }
517
518 if (title.length() > 0) {
519 title += " - ";
520 }
521
522 title += getTitle(doc);
523
524 context.put("title", title);
525
526 context.put("headContent", getHeadContent(doc));
527
528 context.put("bodyContent", getBodyContent(doc));
529
530 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
531
532 if (createDate != null) {
533 context.put("dateCreation", sdf.format(createDate));
534 }
535 }
536
537
538
539
540
541
542 private void closeReader(Reader reader) {
543 if (reader != null) {
544 try {
545 reader.close();
546 } catch (IOException e) {
547 }
548 }
549 }
550
551
552
553
554
555
556 private void closeWriter(FileWriter writer) {
557 if (writer != null) {
558 try {
559 writer.close();
560 } catch (IOException e) {
561 }
562 }
563 }
564
565
566
567
568
569
570 @SuppressWarnings("unchecked")
571 private List<String> getFileList() {
572 List<String> fileList = null;
573
574 try {
575 fileList = FileUtils.getFileNames(sourceDirectory, filePattern, "", false, true);
576 } catch (IOException e) {
577
578 e.printStackTrace();
579 }
580
581 return fileList;
582 }
583
584
585
586
587
588
589
590
591
592 private String getRelativeFilePath(File oldPath, File newPath) {
593 List<String> names = new ArrayList<String>();
594
595 oldPath = oldPath.getAbsoluteFile();
596 newPath = newPath.getAbsoluteFile();
597
598 while (newPath != null && !newPath.equals(oldPath)) {
599 names.add(newPath.getName());
600 newPath = newPath.getParentFile();
601 }
602
603 if (newPath == null) {
604 return "";
605 }
606
607 StringBuilder result = new StringBuilder();
608
609 for (int i = names.size() - 1; i >= 0; i--) {
610 if (result.length() > 0) {
611 result.append('/');
612 }
613
614 result.append(names.get(i));
615 }
616
617 return result.toString();
618 }
619
620
621
622
623
624
625
626
627
628
629 private VelocityContext createContext(File sourceFile, DecorationModel decorationModel, AttributeMap attributes) {
630 VelocityContext context = new VelocityContext();
631
632
633
634
635
636 String relativePath = PathTool.getRelativePath(sourceDirectory.getPath(), sourceFile.getPath());
637
638 context.put("relativePath", relativePath);
639
640 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
641
642 context.put("dateRevision", sdf.format(new Date()));
643
644 context.put("decoration", decorationModel);
645
646 context.put("currentDate", new Date());
647
648 Locale locale = Locale.getDefault();
649
650 context.put("dateFormat", DateFormat.getDateInstance(DateFormat.DEFAULT, locale));
651
652 String currentFileName = sourceFile.getName();
653 String alignedFileName = PathTool.calculateLink(getRelativeFilePath(sourceDirectory, sourceFile), relativePath);
654 boolean doFiltering = (filterExtension != null
655 && filterExtension.equals(currentFileName.substring(currentFileName.length() - filterExtension.length())));
656
657 if (doFiltering) {
658 currentFileName = currentFileName.substring(0, currentFileName.length() - filterExtension.length()) + ".html";
659 alignedFileName = alignedFileName.substring(0, alignedFileName.length() - filterExtension.length()) + ".html";
660 }
661
662 context.put("currentFileName", currentFileName);
663
664 context.put("alignedFileName", alignedFileName);
665
666 context.put("locale", locale);
667
668
669 if (attributes != null) {
670 for (Object o : attributes.keySet()) {
671 context.put((String) o, attributes.get(o));
672 }
673 }
674
675
676
677
678
679 context.put("PathTool", new PathTool());
680
681 context.put("FileUtils", new FileUtils());
682
683 context.put("StringUtils", new StringUtils());
684
685 context.put("i18n", i18n);
686
687 return context;
688 }
689
690
691
692
693
694
695
696
697
698
699
700 private Document parseXHTMLDocument(Reader reader) throws JDOMException, IOException {
701 Document document = null;
702
703 SAXBuilder builder = new SAXBuilder();
704
705 builder.setEntityResolver(new DTDHandler());
706 builder.setIgnoringElementContentWhitespace(false);
707 builder.setIgnoringBoundaryWhitespace(false);
708 document = builder.build(reader);
709
710 return document;
711 }
712
713
714
715
716
717
718
719
720 private String getTitle(Document document) {
721 Element element = null;
722
723 element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:head/xhtml:title");
724 if (element == null) {
725 return null;
726 }
727
728 return element.getText();
729 }
730
731
732
733
734
735
736
737
738 private List<String> getAuthors(Document document) {
739 List<Element> nl = getXPathList(document, "/xhtml:html/xhtml:head/xhtml:meta[@class='author']");
740 List<String> list = new ArrayList<String>();
741
742 for (Element elem : nl) {
743 String author = selectSingleNode(elem, "xhtml:td[@class='author']").getText();
744
745 list.add(author);
746 }
747
748 return list;
749 }
750
751
752
753
754
755
756
757
758
759
760 @SuppressWarnings("unchecked")
761 protected List<Element> getXPathList(Document document, String path) {
762 try {
763 XPath xpath = XPath.newInstance(path);
764
765 xpath.addNamespace("xhtml", "http://www.w3.org/1999/xhtml");
766 List<Element> nl = (List<Element>) xpath.selectNodes(document);
767
768 return nl;
769 } catch (JDOMException e) {
770 return new ArrayList<Element>();
771 }
772 }
773
774
775
776
777
778
779
780
781 private String getBodyContent(Document document) {
782 Element element = null;
783
784 element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:body");
785 if (element == null) {
786 return null;
787 }
788
789 return getElementContentsAsText(element);
790 }
791
792
793
794
795
796
797
798
799 private String getHeadContent(Document document) {
800 Element element = null;
801
802 element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:head");
803 if (element == null) {
804 return null;
805 }
806
807 return getElementContentsAsText(element);
808 }
809
810
811
812
813
814
815
816
817 private String getElementContentsAsText(Element element) {
818 StringBuilder text = new StringBuilder();
819 HTMLOutputter writer = new HTMLOutputter(Format.getPrettyFormat().setTextMode(Format.TextMode.TRIM_FULL_WHITE)
820 .setExpandEmptyElements(true));
821 List<Element> children = getChildren(element);
822
823 if (children.size() == 0) {
824 return element.getText();
825 }
826
827 for (Element child : children) {
828 StringWriter sw = new StringWriter();
829
830 try {
831 writer.output(child, sw);
832 } catch (UnsupportedEncodingException e) {
833 e.printStackTrace();
834 } catch (IOException e) {
835
836 e.printStackTrace();
837 }
838
839 text.append(sw);
840 }
841
842 return text.toString();
843 }
844
845
846
847
848
849
850
851
852
853 @SuppressWarnings("unchecked")
854 private List<Element> getChildren(Element element) {
855 return (List<Element>) element.getChildren();
856 }
857
858
859
860
861
862
863
864
865
866 private Element selectSingleNode(Element element, String path) {
867 try {
868 XPath xpath = XPath.newInstance(path);
869
870 xpath.addNamespace("xhtml", "http://www.w3.org/1999/xhtml");
871 return (Element) xpath.selectSingleNode(element);
872 } catch (JDOMException e) {
873
874 e.printStackTrace();
875 return null;
876 }
877 }
878
879
880
881
882 private static class AttributeMap extends HashMap<Object, Object> {
883 private static final long serialVersionUID = 1787343499009497124L;
884 }
885 }