1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.devacfr.maven.skins.reflow;
20
21 import javax.annotation.Nonnull;
22 import javax.annotation.Nullable;
23
24 import java.text.ParseException;
25 import java.text.SimpleDateFormat;
26 import java.util.Date;
27 import java.util.List;
28
29 import com.google.common.base.Strings;
30 import org.apache.commons.lang3.StringUtils;
31 import org.apache.maven.doxia.site.SiteModel;
32 import org.apache.maven.project.MavenProject;
33 import org.apache.velocity.tools.ToolContext;
34 import org.apache.velocity.tools.config.DefaultKey;
35 import org.apache.velocity.tools.generic.RenderTool;
36 import org.apache.velocity.tools.generic.SafeConfig;
37 import org.apache.velocity.tools.generic.ValueParser;
38 import org.codehaus.plexus.util.PathTool;
39 import org.codehaus.plexus.util.xml.Xpp3Dom;
40 import org.devacfr.maven.skins.reflow.context.Context;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import static java.util.Objects.requireNonNull;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 @DefaultKey("config")
91 public class SkinConfigTool extends SafeConfig implements ISkinConfig {
92
93
94 private static final Logger LOGGER = LoggerFactory.getLogger(SkinConfigTool.class);
95
96
97 private static final SimpleDateFormat ISO_8601BASIC_DATE = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
98
99 private static final String PROJECT_BUILD_OUTPUTTIMESTAMP = "project.build.outputTimestamp";
100
101
102 public static final String DEFAULT_KEY = "config";
103
104
105 public static final String SKIN_KEY = "reflowSkin";
106
107
108 private String key = DEFAULT_KEY;
109
110
111 private String skinKey = SKIN_KEY;
112
113
114 private Xpp3Dom globalProperties = new Xpp3Dom("");
115
116
117 private Xpp3Dom pageProperties = new Xpp3Dom("");
118
119
120 private String namespace = "";
121
122
123 private String projectId = null;
124
125
126 private String fileId = null;
127
128
129 private Context<?> context = null;
130
131
132 private MavenProject project = null;
133
134
135 private SiteModel siteModel;
136
137
138 private ToolContext velocityContext;
139
140
141
142
143
144
145 @Override
146 protected void configure(final ValueParser values) {
147 final String altkey = values.getString("key");
148 if (altkey != null) {
149 setKey(altkey);
150 }
151
152
153 final String altSkinKey = values.getString("skinKey");
154 if (altSkinKey != null) {
155 this.skinKey = altSkinKey;
156 }
157
158
159 final Object vc = values.get("velocityContext");
160
161 if (!(vc instanceof ToolContext)) {
162 return;
163 }
164
165 this.velocityContext = (ToolContext) vc;
166
167 final Object projectObj = velocityContext.get("project");
168 if (projectObj instanceof MavenProject) {
169 this.project = (MavenProject) projectObj;
170 final String artifactId = project.getArtifactId();
171
172 projectId = HtmlTool.slug(artifactId);
173 }
174
175
176 final String currentFileObj = getCurrentFileName();
177 fileId = slugFilename(currentFileObj);
178
179 final Object siteModelObj = velocityContext.get("site");
180
181 if (!(siteModelObj instanceof SiteModel)) {
182 return;
183 }
184
185 this.siteModel = (SiteModel) siteModelObj;
186 final Object customObj = siteModel.getCustom();
187
188 if (!(customObj instanceof Xpp3Dom)) {
189 return;
190 }
191
192
193
194 final Xpp3Dom customNode = (Xpp3Dom) customObj;
195 Xpp3Dom skinNode = customNode.getChild(skinKey);
196 final String namespaceKey = ":" + skinKey;
197
198 if (skinNode == null) {
199
200 for (final Xpp3Dom child : customNode.getChildren()) {
201 if (child.getName().endsWith(namespaceKey)) {
202 skinNode = child;
203 break;
204 }
205 }
206 }
207
208 if (skinNode != null) {
209 globalProperties = skinNode;
210
211 if (skinNode.getName().endsWith(namespaceKey)) {
212
213 namespace = Strings.emptyToNull(
214 skinNode.getName().substring(0, skinNode.getName().length() - namespaceKey.length() + 1));
215 }
216
217
218
219 final Xpp3Dom pagesNode = Xpp3Utils.getFirstChild(skinNode, "pages", namespace);
220 if (pagesNode != null) {
221
222
223 Xpp3Dom page = Xpp3Utils.getFirstChild(pagesNode, fileId, namespace);
224
225
226
227
228 if (page != null && projectId != null) {
229 final String pageProject = page.getAttribute("project");
230 if (pageProject != null && !projectId.equals(pageProject)) {
231
232 page = null;
233 }
234 }
235
236 if (page != null) {
237 pageProperties = page;
238 }
239 }
240
241
242
243 this.velocityContext.put("localResources", is("localResources"));
244
245
246 this.velocityContext.put("resourcePath", getResourcePath());
247
248 this.context = Context.buildContext(this);
249 }
250 if (LOGGER.isDebugEnabled()) {
251 LOGGER.debug("Current Filename: {}", currentFileObj);
252 LOGGER.debug("Project id: {}", projectId);
253 LOGGER.debug("File id: {}", fileId);
254 LOGGER.debug("Context: {}", this.context);
255 LOGGER.debug("Namespace: {}", this.namespace);
256 LOGGER.debug("---------------------------------------------------");
257 }
258 }
259
260
261
262
263
264
265
266
267 protected void setKey(final String key) {
268 this.key = requireNonNull(key, "SkinConfigTool key cannot be null");
269 }
270
271
272
273
274
275 public String getKey() {
276 return this.key;
277 }
278
279
280
281
282 @Override
283 @Nullable public <T> T getContextValue(@Nonnull final String key, @Nonnull final Class<T> type) {
284 requireNonNull(type);
285 if (String.class.isAssignableFrom(type)) {
286 return this.eval("$" + key, type);
287 } else {
288 throw new UnsupportedOperationException();
289 }
290 }
291
292
293
294
295 @Override
296 public void setContextValue(@Nonnull final String key, @Nullable final Object value) {
297 requireNonNull(key);
298 if (value instanceof String) {
299 this.eval("#set( $" + key + "= \"" + value.toString() + "\")", Void.class);
300 } else {
301 throw new UnsupportedOperationException();
302 }
303 }
304
305
306
307
308 @Override
309 @Nullable @SuppressWarnings("unchecked")
310 public <T> T getToolbox(@Nonnull final String toolName, @Nonnull final Class<T> toolType) {
311 requireNonNull(toolType);
312 return (T) this.velocityContext.getToolbox().get(requireNonNull(toolName));
313 }
314
315
316
317
318 @Override
319 @Nullable public Xpp3Dom get(@Nonnull final String property) {
320 requireNonNull(property);
321
322 Xpp3Dom propNode = Xpp3Utils.getFirstChild(pageProperties, property, namespace);
323 if (propNode == null) {
324
325 propNode = Xpp3Utils.getFirstChild(globalProperties, property, namespace);
326 }
327
328 return propNode;
329 }
330
331
332
333
334
335
336
337
338
339
340 @Nullable public String value(@Nonnull final String property) {
341 requireNonNull(property);
342 final Xpp3Dom propNode = get(property);
343
344 if (propNode == null) {
345
346 return null;
347 }
348
349 return propNode.getValue();
350 }
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366 @Override
367 @SuppressWarnings("unchecked")
368 @Nullable public <T> T getPropertyValue(@Nonnull final String property,
369 @Nonnull final Class<T> targetType,
370 @Nullable final T defaultValue) {
371 requireNonNull(property, "property is required");
372 requireNonNull(targetType, "targetType is required");
373 final String value = value(property);
374 if (value == null) {
375 return defaultValue;
376 }
377 Object returnedValue = value;
378 if (targetType.isAssignableFrom(Boolean.class)) {
379 returnedValue = Boolean.valueOf(value);
380 } else if (targetType.isAssignableFrom(Integer.class)) {
381 returnedValue = Integer.valueOf(value);
382 } else if (targetType.isAssignableFrom(Long.class)) {
383 returnedValue = Long.valueOf(value);
384 }
385 return (T) returnedValue;
386 }
387
388
389
390
391
392
393
394
395
396
397 public List<String> getChildren(final Xpp3Dom parentNode) {
398 return Xpp3Utils.getChildren(parentNode);
399 }
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417 @Override
418 @SuppressWarnings("unchecked")
419 @Nullable public <T> T getAttributeValue(@Nonnull final String property,
420 @Nonnull final String attribute,
421 @Nonnull final Class<T> targetType,
422 @Nullable final T defaultValue) {
423 requireNonNull(property, "property is required");
424 requireNonNull(attribute, "attribute is required");
425 requireNonNull(targetType, "targetType is required");
426
427 Xpp3Dom element = get(property);
428 if (element == null) {
429 return defaultValue;
430 }
431 String value = element.getAttribute(attribute);
432 if (value == null) {
433 return defaultValue;
434 }
435
436 if ("inherit".equals(value) || Strings.isNullOrEmpty(value)) {
437 element = Xpp3Utils.getFirstChild(globalProperties, property, namespace);
438 if (LOGGER.isDebugEnabled()) {
439 LOGGER.debug("Inherit value property '{}': {}", property, element);
440 }
441 }
442 if (element == null) {
443 return defaultValue;
444 }
445 value = element.getAttribute(attribute);
446 if (value == null) {
447 return defaultValue;
448 }
449
450 Object returnedValue = value;
451 if (targetType.isAssignableFrom(Boolean.class)) {
452 returnedValue = Boolean.valueOf(value);
453 } else if (targetType.isAssignableFrom(Integer.class)) {
454 returnedValue = Integer.valueOf(value);
455 } else if (targetType.isAssignableFrom(Long.class)) {
456 returnedValue = Long.valueOf(value);
457 }
458 return (T) returnedValue;
459 }
460
461
462
463
464 @Override
465 @SuppressWarnings("unchecked")
466 @Nullable public <T> T getAttributeValue(@Nonnull final Xpp3Dom element,
467 @Nonnull final String attribute,
468 @Nonnull final Class<T> targetType,
469 @Nullable final T defaultValue) {
470 if (element == null) {
471 return defaultValue;
472 }
473 final String value = element.getAttribute(attribute);
474 if (value == null) {
475 return defaultValue;
476 }
477 Object returnedValue = value;
478 if (targetType.isAssignableFrom(Boolean.class)) {
479 returnedValue = Boolean.valueOf(value);
480 } else if (targetType.isAssignableFrom(Integer.class)) {
481 returnedValue = Integer.valueOf(value);
482 } else if (targetType.isAssignableFrom(Long.class)) {
483 returnedValue = Long.valueOf(value);
484 }
485 return (T) returnedValue;
486 }
487
488
489
490
491
492
493
494
495
496
497
498 public boolean is(final String property) {
499 return "true".equals(value(property));
500 }
501
502
503
504
505 @Override
506 public boolean not(final String property) {
507 return "false".equals(value(property));
508 }
509
510
511
512
513
514
515
516
517
518
519
520
521 public boolean isValue(final String property, final String value) {
522 return value != null && value.equals(value(property));
523 }
524
525
526
527
528 @Override
529 @Nullable public String getProjectId() {
530 return projectId;
531 }
532
533
534
535
536 @Override
537 @Nullable public String getFileId() {
538 return fileId;
539 }
540
541
542
543
544 @Override
545 @Nonnull
546 public Context<?> getContext() {
547 return context;
548 }
549
550
551
552
553 public ToolContext getVelocityContext() {
554 return velocityContext;
555 }
556
557
558
559
560 @Override
561 @Nonnull
562 public MavenProject getProject() {
563 return project;
564 }
565
566
567
568
569 @Override
570 @Nonnull
571 public SiteModel getSiteModel() {
572 return siteModel;
573 }
574
575
576
577
578 @Override
579 @Nonnull
580 public Xpp3Dom getPageProperties() {
581 return pageProperties;
582 }
583
584
585
586
587 @Override
588 @Nonnull
589 public Xpp3Dom getGlobalProperties() {
590 return globalProperties;
591 }
592
593
594
595
596 @Override
597 @Nonnull
598 public String getNamespace() {
599 return namespace;
600 }
601
602
603
604
605 @Nonnull
606 public String getProjectLocation() {
607 String projectSiteLoc = getProject().getUrl();
608 if (!Strings.isNullOrEmpty(projectSiteLoc)) {
609
610 if (!projectSiteLoc.endsWith("/")) {
611 projectSiteLoc += "/";
612 }
613 }
614 return projectSiteLoc;
615 }
616
617
618
619
620
621
622
623
624
625 @Nonnull
626 public String getCurrentFileName() {
627 return (String) velocityContext.get("currentFileName");
628 }
629
630
631
632
633 @Nonnull
634 public String getCurrentFileLocation() {
635 final String projectSiteLoc = getProjectLocation();
636 return URITool.toURI(projectSiteLoc).resolve(getCurrentFileName()).toString();
637 }
638
639
640
641
642 @Override
643 @SuppressWarnings("unchecked")
644 @Nullable public <T> T eval(@Nullable final String vtl, @Nonnull final Class<T> requiredClass) {
645 if (vtl == null) {
646 return null;
647 }
648 final RenderTool renderTool = (RenderTool) getVelocityContext().get("render");
649 try {
650 return (T) renderTool.eval(getVelocityContext(), vtl);
651 } catch (final Exception ex) {
652 throw new RuntimeException("error when try evaluate '" + vtl + "'", ex);
653 }
654 }
655
656
657
658
659 @Override
660 public String relativeLink(final String href) {
661 if (href == null) {
662 return null;
663 }
664 if (isExternalLink(href)) {
665 return href;
666 }
667 final String relativePath = (String) velocityContext.get("relativePath");
668 String relativeLink = PathTool.calculateLink(href, relativePath);
669 relativeLink = relativeLink.replaceAll("\\\\", "/");
670 if (Strings.isNullOrEmpty(relativeLink)) {
671 relativeLink = "./";
672 }
673
674
675
676
677
678
679
680 final String currentFileLoc = getCurrentFileLocation();
681 final String absoluteLink = URITool.toURI(currentFileLoc).resolve(relativeLink).normalize().toString();
682 if (currentFileLoc.equals(absoluteLink)) {
683
684 relativeLink = StringUtils.EMPTY;
685 } else {
686
687
688 final String currentFileDir = PathTool.getDirectoryComponent(currentFileLoc);
689 relativeLink = URITool.relativizeLink(currentFileDir, absoluteLink);
690 }
691 if (LOGGER.isDebugEnabled()) {
692 LOGGER.debug("-- Relative Link ----------------------------------");
693 LOGGER.debug("link: {}", href);
694 LOGGER.debug("currentFileLoc: {}", currentFileLoc);
695 LOGGER.debug("absoluteLink: {}", absoluteLink);
696 LOGGER.debug("relativeLink: {}", relativeLink);
697 LOGGER.debug("---------------------------------------------------");
698 }
699 return relativeLink;
700 }
701
702
703
704
705
706
707 public boolean isExternalLink(final String url) {
708 if (url == null) {
709 return false;
710 }
711 final String absoluteResourceURL = this.value("absoluteResourceURL");
712 if (!Strings.isNullOrEmpty(absoluteResourceURL) && url.startsWith(absoluteResourceURL)) {
713 return false;
714 }
715 return url.toLowerCase().startsWith("http:/") || url.toLowerCase().startsWith("https:/")
716 || url.toLowerCase().startsWith("ftp:/") || url.toLowerCase().startsWith("mailto:")
717 || url.toLowerCase().startsWith("file:/") || url.toLowerCase().indexOf("://") != -1;
718 }
719
720
721
722
723 @Override
724 public boolean isActiveLink(@Nullable final String href) {
725 final String alignedFileName = (String) velocityContext.get("alignedFileName");
726 if (href == null) {
727 return false;
728 }
729
730
731 return Strings.isNullOrEmpty(href) || alignedFileName.endsWith("index.html") && ".".equals(href);
732 }
733
734
735
736
737
738
739
740
741 @Nullable public static String slugFilename(@Nullable final String fileName) {
742 if (fileName == null) {
743 return null;
744 }
745 String currentFile = fileName;
746
747
748 final int lastDot = currentFile.lastIndexOf(".");
749 if (lastDot >= 0) {
750 currentFile = currentFile.substring(0, lastDot);
751 }
752
753
754
755
756
757
758
759 return HtmlTool.slug(currentFile.replace("/", "-").replace("\\", "-"));
760 }
761
762
763
764
765 @Nonnull
766 public String getResourcePath() {
767 final String absoluteResourceURL = this.value("absoluteResourceURL");
768 String projectUrl = getProjectLocation();
769 final String currentFileName = getCurrentFileName();
770 if (!Strings.isNullOrEmpty(projectUrl) && currentFileName != null) {
771 if (projectUrl.charAt(projectUrl.length() - 1) != '/') {
772 projectUrl += '/';
773 }
774 final String currentFileDir = URITool.toURI(projectUrl).resolve(currentFileName).resolve(".").toString();
775 return URITool.relativizeLink(currentFileDir, absoluteResourceURL);
776 }
777 return (String) velocityContext.get("relativePath");
778 }
779
780
781
782
783
784
785
786 @Nullable public Date getBuildOutputTimestamp() throws ParseException {
787 if (!this.velocityContext.containsKey(PROJECT_BUILD_OUTPUTTIMESTAMP)) {
788 return null;
789 }
790 Object outputTimestamp = this.velocityContext.get(PROJECT_BUILD_OUTPUTTIMESTAMP);
791 if (outputTimestamp != null) {
792 return ISO_8601BASIC_DATE.parse(outputTimestamp.toString());
793 }
794 return null;
795 }
796 }