1 package org.apache.velocity.tools.view;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.InputStream;
23 import java.io.IOException;
24 import java.net.URL;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.LinkedHashMap;
28 import java.util.List;
29 import java.util.Map;
30 import javax.servlet.ServletContext;
31 import javax.servlet.http.HttpServletRequest;
32 import org.xml.sax.Attributes;
33 import org.xml.sax.SAXException;
34 import org.apache.commons.digester.Digester;
35 import org.apache.commons.digester.Rule;
36 import org.apache.velocity.tools.ClassUtils;
37 import org.apache.velocity.tools.view.ViewContext;
38 import org.apache.velocity.runtime.log.Log;
39 import org.apache.velocity.tools.Scope;
40 import org.apache.velocity.tools.ToolContext;
41 import org.apache.velocity.tools.config.DefaultKey;
42 import org.apache.velocity.tools.config.ValidScope;
43
44
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 @DefaultKey("depends")
115 @ValidScope(Scope.REQUEST)
116 public class UiDependencyTool {
117
118 public static final String GROUPS_KEY_SPACE = UiDependencyTool.class.getName() + ":";
119 public static final String TYPES_KEY_SPACE = UiDependencyTool.class.getName() + ":types:";
120 public static final String SOURCE_FILE_KEY = "file";
121 public static final String DEFAULT_SOURCE_FILE = "ui.xml";
122 private static final List<Type> DEFAULT_TYPES;
123 static {
124 List<Type> types = new ArrayList<Type>();
125
126 types.add(new Type("style", "<link rel=\"stylesheet\" type=\"text/css\" href=\"{context}/css/{file}\"/>"));
127 types.add(new Type("script", "<script type=\"text/javascript\" src=\"{context}/js/{file}\"></script>"));
128 DEFAULT_TYPES = Collections.unmodifiableList(types);
129 }
130
131 private Map<String,Group> groups = null;
132 private List<Type> types = DEFAULT_TYPES;
133 private Map<String,List<String>> dependencies;
134 private Log LOG;
135 private String context = "";
136
137 private void debug(String msg, Object... args) {
138 if (LOG.isDebugEnabled()) {
139 LOG.debug(String.format("UiDependencyTool: "+msg, args));
140 }
141 }
142
143 protected static final void trace(Log log, String msg, Object... args) {
144 if (log.isTraceEnabled()) {
145 log.trace(String.format("UiDependencyTool: "+msg, args));
146 }
147 }
148
149 public void configure(Map params) {
150 ServletContext app = (ServletContext)params.get(ViewContext.SERVLET_CONTEXT_KEY);
151 LOG = (Log)params.get(ToolContext.LOG_KEY);
152
153 HttpServletRequest request = (HttpServletRequest)params.get(ViewContext.REQUEST);
154 context = request.getContextPath();
155
156 String file = (String)params.get(SOURCE_FILE_KEY);
157 if (file == null) {
158 file = DEFAULT_SOURCE_FILE;
159 } else {
160 debug("Loading file: %s", file);
161 }
162
163 synchronized (app) {
164
165 groups = (Map<String,Group>)app.getAttribute(GROUPS_KEY_SPACE+file);
166 if (groups == null) {
167 groups = new LinkedHashMap<String,Group>();
168
169 read(file, (file != DEFAULT_SOURCE_FILE));
170 app.setAttribute(GROUPS_KEY_SPACE+file, groups);
171 if (types != DEFAULT_TYPES) {
172 app.setAttribute(TYPES_KEY_SPACE+file, types);
173 }
174 } else {
175
176 List<Type> alt = (List<Type>)app.getAttribute(TYPES_KEY_SPACE+file);
177 if (alt != null) {
178 types = alt;
179 }
180 }
181 }
182 }
183
184
185
186
187
188
189 public UiDependencyTool on(String name) {
190 Map<String,List<String>> groupDeps = getGroupDependencies(name);
191 if (groupDeps == null) {
192 return null;
193 } else {
194 addDependencies(groupDeps);
195 return this;
196 }
197 }
198
199
200
201
202
203
204 public UiDependencyTool on(String type, String file) {
205 if (type == null || file == null) {
206 return null;
207 } else {
208 addFile(type, file);
209 return this;
210 }
211 }
212
213
214
215
216
217 public String print() {
218 return printAll("\n");
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232 public String print(String typeOrDelim) {
233 if (getType(typeOrDelim) == null) {
234
235 return printAll(typeOrDelim);
236 } else {
237
238 return print(typeOrDelim, "\n");
239 }
240 }
241
242
243
244
245
246
247 public String print(String type, String delim) {
248 List<String> files = getDependencies(type);
249 if (files == null) {
250 return null;
251 }
252
253 String format = getFormat(type);
254 StringBuilder out = new StringBuilder();
255 for (String file : files) {
256 out.append(format(format, file));
257 out.append(delim);
258 }
259 return out.toString();
260 }
261
262
263
264
265
266 public String printAll(String delim) {
267 if (dependencies == null) {
268 return null;
269 }
270
271 StringBuilder out = new StringBuilder();
272 for (Type type : types) {
273 if (out.length() > 0) {
274 out.append(delim);
275 }
276 List<String> files = dependencies.get(type.name);
277 if (files != null) {
278 for (int i=0; i < files.size(); i++) {
279 if (i > 0) {
280 out.append(delim);
281 }
282 out.append(format(type.format, files.get(i)));
283 }
284 }
285 }
286 return out.toString();
287 }
288
289
290
291
292 public UiDependencyTool context(String path)
293 {
294 this.context = path;
295 return this;
296 }
297
298
299
300
301 public String getFormat(String type) {
302 Type t = getType(type);
303 if (t == null) {
304 return null;
305 }
306 return t.format;
307 }
308
309
310
311
312 public void setFormat(String type, String format) {
313 if (format == null || type == null) {
314 throw new NullPointerException("Type name and format must not be null");
315 }
316
317 if (types == DEFAULT_TYPES) {
318 types = new ArrayList<Type>();
319 for (Type t : DEFAULT_TYPES) {
320 types.add(new Type(t.name, t.format));
321 }
322 }
323 Type t = getType(type);
324 if (t == null) {
325 types.add(new Type(type, format));
326 } else {
327 t.format = format;
328 }
329 }
330
331
332
333
334
335
336 public Map<String,List<String>> getDependencies() {
337 return dependencies;
338 }
339
340
341
342
343 public List<String> getDependencies(String type) {
344 if (dependencies == null) {
345 return null;
346 }
347 return dependencies.get(type);
348 }
349
350
351
352
353
354
355 public Map<String,List<String>> getGroupDependencies(String name) {
356 Group group = getGroup(name);
357 if (group == null) {
358 return null;
359 }
360 return group.getDependencies(this);
361 }
362
363
364
365
366
367 @Override
368 public String toString() {
369 return "";
370 }
371
372
373
374
375
376
377
378
379
380 protected void read(String file, boolean required) {
381 debug("UiDependencyTool: Reading file from %s", file);
382 URL url = toURL(file);
383 if (url == null) {
384 String msg = "UiDependencyTool: Could not read file from '"+file+"'";
385 if (required) {
386 LOG.error(msg);
387 throw new IllegalArgumentException(msg);
388 } else {
389 LOG.debug(msg);
390 }
391 } else {
392 Digester digester = createDigester();
393 try
394 {
395 digester.parse(url.openStream());
396 }
397 catch (SAXException saxe)
398 {
399 LOG.error("UiDependencyTool: Failed to parse '"+file+"'", saxe);
400 throw new RuntimeException("While parsing the InputStream", saxe);
401 }
402 catch (IOException ioe)
403 {
404 LOG.error("UiDependencyTool: Failed to read '"+file+"'", ioe);
405 throw new RuntimeException("While handling the InputStream", ioe);
406 }
407 }
408 }
409
410
411
412
413
414 protected Digester createDigester() {
415 Digester digester = new Digester();
416 digester.setValidating(false);
417 digester.setUseContextClassLoader(true);
418 digester.addRule("ui/type", new TypeRule());
419 digester.addRule("ui/group", new GroupRule());
420 digester.addRule("ui/group/file", new FileRule());
421 digester.addRule("ui/group/needs", new NeedsRule());
422 digester.push(this);
423 return digester;
424 }
425
426
427
428
429
430
431 protected String format(String format, String value) {
432 if (format == null) {
433 return value;
434 }
435 return format.replace("{file}", value).replace("{context}", this.context);
436 }
437
438
439
440
441
442 protected Group getGroup(String name) {
443 if (groups == null) {
444 return null;
445 }
446 return groups.get(name);
447 }
448
449
450
451
452
453 protected Group makeGroup(String name) {
454 trace(LOG, "Creating group '%s'", name);
455 Group group = new Group(name, LOG);
456 groups.put(name, group);
457 return group;
458 }
459
460
461
462
463
464 protected void addDependencies(Map<String,List<String>> fbt) {
465 if (this.dependencies == null) {
466 dependencies = new LinkedHashMap<String,List<String>>(fbt.size());
467 }
468 for (Map.Entry<String,List<String>> entry : fbt.entrySet()) {
469 String type = entry.getKey();
470 if (getType(type) == null) {
471 LOG.error("UiDependencyTool: Type '"+type+"' is unknown and will not be printed unless defined.");
472 }
473 List<String> existing = dependencies.get(type);
474 if (existing == null) {
475 existing = new ArrayList<String>(entry.getValue().size());
476 dependencies.put(type, existing);
477 }
478 for (String file : entry.getValue()) {
479 if (!existing.contains(file)) {
480 trace(LOG, "Adding %s: %s", type, file);
481 existing.add(file);
482 }
483 }
484 }
485 }
486
487
488
489
490 protected void addFile(String type, String file) {
491 List<String> files = null;
492 if (dependencies == null) {
493 dependencies = new LinkedHashMap<String,List<String>>(types.size());
494 } else {
495 files = dependencies.get(type);
496 }
497 if (files == null) {
498 files = new ArrayList<String>();
499 dependencies.put(type, files);
500 }
501 if (!files.contains(file)) {
502 trace(LOG, "Adding %s: %s", type, file);
503 files.add(file);
504 }
505 }
506
507
508
509
510
511 private Type getType(String type) {
512 for (Type t : types) {
513 if (t.name.equals(type)) {
514 return t;
515 }
516 }
517 return null;
518 }
519
520
521
522 private URL toURL(String file) {
523 try
524 {
525 return ClassUtils.getResource(file, this);
526 }
527 catch (Exception e) {
528 return null;
529 }
530 }
531
532
533
534
535
536
537 protected static class Group {
538
539 private volatile boolean resolved = true;
540 private String name;
541 private Map<String,Integer> typeCounts = new LinkedHashMap<String,Integer>();
542 private Map<String,List<String>> dependencies = new LinkedHashMap<String,List<String>>();
543 private List<String> groups;
544 private Log LOG;
545
546 public Group(String name, Log log) {
547 this.name = name;
548 this.LOG = log;
549 }
550
551 private void trace(String msg, Object... args) {
552 if (LOG.isTraceEnabled()) {
553 UiDependencyTool.trace(LOG, "Group "+name+": "+msg, args);
554 }
555 }
556
557 public void addFile(String type, String value) {
558 List<String> files = dependencies.get(type);
559 if (files == null) {
560 files = new ArrayList<String>();
561 dependencies.put(type, files);
562 }
563 if (!files.contains(value)) {
564 trace("Adding %s: %s", type, value);
565 files.add(value);
566 }
567 }
568
569 public void addGroup(String group) {
570 if (this.groups == null) {
571 this.resolved = false;
572 this.groups = new ArrayList<String>();
573 }
574 if (!this.groups.contains(group)) {
575 trace("Adding group %s", group, name);
576 this.groups.add(group);
577 }
578 }
579
580 public Map<String,List<String>> getDependencies(UiDependencyTool parent) {
581 resolve(parent);
582 return this.dependencies;
583 }
584
585 protected void resolve(UiDependencyTool parent) {
586 if (!resolved) {
587
588 resolved = true;
589 trace("resolving...");
590 for (String name : groups) {
591 Group group = parent.getGroup(name);
592 if (group == null) {
593 throw new NullPointerException("No group named '"+name+"'");
594 }
595 Map<String,List<String>> dependencies = group.getDependencies(parent);
596 for (Map.Entry<String,List<String>> type : dependencies.entrySet()) {
597 for (String value : type.getValue()) {
598 addFileFromGroup(type.getKey(), value);
599 }
600 }
601 }
602 trace(" is resolved.");
603 }
604 }
605
606 private void addFileFromGroup(String type, String value) {
607 List<String> files = dependencies.get(type);
608 if (files == null) {
609 files = new ArrayList<String>();
610 files.add(value);
611 trace("adding %s '%s' first", type, value);
612 dependencies.put(type, files);
613 typeCounts.put(type, 1);
614 } else if (!files.contains(value)) {
615 Integer count = typeCounts.get(type);
616 if (count == null) {
617 count = 0;
618 }
619 files.add(count, value);
620 trace("adding %s '%s' at %s", type, value, count);
621 typeCounts.put(type, ++count);
622 }
623 }
624 }
625
626
627
628
629
630 protected static class TypeRule extends Rule {
631
632 private UiDependencyTool parent;
633
634 public void begin(String ns, String el, Attributes attributes) throws Exception {
635 parent = (UiDependencyTool)digester.peek();
636
637 for (int i=0; i < attributes.getLength(); i++) {
638 String name = attributes.getLocalName(i);
639 if ("".equals(name)) {
640 name = attributes.getQName(i);
641 }
642 if ("name".equals(name)) {
643 digester.push(attributes.getValue(i));
644 }
645 }
646 }
647
648 public void body(String ns, String el, String typeFormat) throws Exception {
649 String typeName = (String)digester.pop();
650 parent.setFormat(typeName, typeFormat);
651 }
652 }
653
654
655
656
657
658 protected static class GroupRule extends Rule {
659
660 private UiDependencyTool parent;
661
662 public void begin(String ns, String el, Attributes attributes) throws Exception {
663 parent = (UiDependencyTool)digester.peek();
664
665 for (int i=0; i < attributes.getLength(); i++) {
666 String name = attributes.getLocalName(i);
667 if ("".equals(name)) {
668 name = attributes.getQName(i);
669 }
670 if ("name".equals(name)) {
671 digester.push(parent.makeGroup(attributes.getValue(i)));
672 }
673 }
674 }
675
676 public void end(String ns, String el) throws Exception {
677 digester.pop();
678 }
679 }
680
681
682
683
684
685 protected static class FileRule extends Rule {
686
687 public void begin(String ns, String el, Attributes attributes) throws Exception {
688 for (int i=0; i < attributes.getLength(); i++) {
689 String name = attributes.getLocalName(i);
690 if ("".equals(name)) {
691 name = attributes.getQName(i);
692 }
693 if ("type".equals(name)) {
694 digester.push(attributes.getValue(i));
695 }
696 }
697 }
698
699 public void body(String ns, String el, String value) throws Exception {
700 String type = (String)digester.pop();
701 Group group = (Group)digester.peek();
702 group.addFile(type, value);
703 }
704 }
705
706
707
708
709
710 protected static class NeedsRule extends Rule {
711
712 public void body(String ns, String el, String otherGroup) throws Exception {
713 Group group = (Group)digester.peek();
714 group.addGroup(otherGroup);
715 }
716 }
717
718
719 private static final class Type {
720
721 protected String name;
722 protected String format;
723
724 Type(String n, String f) {
725 name = n;
726 format = f;
727 }
728 }
729
730 }