1 16 package org.apache.cocoon.transformation; 17 18 import org.apache.avalon.framework.CascadingRuntimeException; 19 import org.apache.avalon.framework.component.WrapperComponentManager; 20 import org.apache.avalon.framework.configuration.Configurable; 21 import org.apache.avalon.framework.configuration.Configuration; 22 import org.apache.avalon.framework.configuration.ConfigurationException; 23 import org.apache.avalon.framework.logger.AbstractLogEnabled; 24 import org.apache.avalon.framework.logger.Logger; 25 import org.apache.avalon.framework.parameters.Parameters; 26 import org.apache.avalon.framework.service.ServiceException; 27 import org.apache.avalon.framework.service.ServiceManager; 28 import org.apache.avalon.framework.service.Serviceable; 29 30 import org.apache.cocoon.ProcessingException; 31 import org.apache.cocoon.Processor; 32 import org.apache.cocoon.caching.CacheableProcessingComponent; 33 import org.apache.cocoon.components.CocoonComponentManager; 34 import org.apache.cocoon.components.source.SourceUtil; 35 import org.apache.cocoon.components.source.impl.MultiSourceValidity; 36 import org.apache.cocoon.components.thread.RunnableManager; 37 import org.apache.cocoon.environment.Environment; 38 import org.apache.cocoon.environment.SourceResolver; 39 import org.apache.cocoon.transformation.helpers.NOPRecorder; 40 import org.apache.cocoon.util.NetUtils; 41 import org.apache.cocoon.xml.AbstractXMLPipe; 42 import org.apache.cocoon.xml.IncludeXMLConsumer; 43 import org.apache.cocoon.xml.NamespacesTable; 44 import org.apache.cocoon.xml.SaxBuffer; 45 import org.apache.cocoon.xml.XMLConsumer; 46 47 import org.apache.excalibur.source.Source; 48 import org.apache.excalibur.source.SourceValidity; 49 import org.xml.sax.Attributes ; 50 import org.xml.sax.ContentHandler ; 51 import org.xml.sax.Locator ; 52 import org.xml.sax.SAXException ; 53 import org.xml.sax.ext.LexicalHandler ; 54 55 import java.io.IOException ; 56 import java.io.Serializable ; 57 import java.io.UnsupportedEncodingException ; 58 import java.util.HashMap ; 59 import java.util.Map ; 60 import java.util.Stack ; 61 62 172 public class IncludeTransformer extends AbstractTransformer 173 implements Serviceable, Configurable, 174 Transformer, CacheableProcessingComponent { 175 176 177 private static final String NS_URI = "http://apache.org/cocoon/include/1.0"; 178 179 180 private static final String INCLUDE_ELEMENT = "include"; 181 182 183 private static final String FALLBACK_ELEMENT = "fallback"; 184 185 186 private static final String PARAMETER_ELEMENT = "parameter"; 187 188 189 private static final String SRC_ATTRIBUTE = "src"; 190 191 192 private static final String MIME_ATTRIBUTE = "mime-type"; 193 194 195 private static final String PARSE_ATTRIBUTE = "parse"; 196 197 198 private static final String NAME_ATTRIBUTE = "name"; 199 200 201 private static final String VALUE_ATTRIBUTE = "value"; 202 203 204 private static final String ENCODING = "US-ASCII"; 205 206 210 211 private ServiceManager manager; 212 213 214 private boolean defaultRecursive; 215 216 217 private boolean defaultParallel; 218 219 220 private boolean defaultRecursiveParallel; 221 222 223 private String threadPool; 224 225 226 private String defaultKey; 227 228 232 233 private SourceResolver resolver; 234 235 236 private Environment environment; 237 238 239 private Processor processor; 240 241 242 private String key; 243 244 248 249 private MultiSourceValidity validity; 250 251 252 private NamespacesTable namespaces; 253 254 255 private final IncludeXMLPipe pipe; 256 257 260 public IncludeTransformer() { 261 pipe = new IncludeXMLPipe(); 262 } 263 264 267 public void enableLogging(Logger logger) { 268 super.enableLogging(logger); 269 pipe.enableLogging(logger); 270 } 271 272 277 public void service(ServiceManager manager) throws ServiceException { 278 this.manager = manager; 279 } 280 281 284 public void configure(Configuration configuration) throws ConfigurationException { 285 286 this.defaultRecursive = configuration.getChild("recursive").getValueAsBoolean(false); 287 this.defaultParallel = configuration.getChild("parallel").getValueAsBoolean(false); 288 this.defaultRecursiveParallel = configuration.getChild("recursive-parallel").getValueAsBoolean(false); 289 290 this.threadPool = configuration.getChild("thread-pool").getValue("default"); 291 this.defaultKey = configuration.getChild("key").getValue(null); 292 } 293 294 300 public void setup(SourceResolver resolver, Map om, String src, Parameters parameters) 301 throws ProcessingException, SAXException , IOException { 302 303 this.pipe.recursive = parameters.getParameterAsBoolean("recursive", this.defaultRecursive); 304 this.pipe.parallel = parameters.getParameterAsBoolean("parallel", this.defaultParallel); 305 this.pipe.recursiveParallel = parameters.getParameterAsBoolean("recursive-parallel", this.defaultRecursiveParallel); 306 this.key = parameters.getParameter("key", this.defaultKey); 307 308 309 if (this.pipe.parallel) { 310 this.environment = CocoonComponentManager.getCurrentEnvironment(); 311 this.processor = CocoonComponentManager.getCurrentProcessor(); 312 } 313 this.namespaces = new NamespacesTable(); 314 this.resolver = resolver; 315 this.validity = null; 316 317 super.xmlConsumer = pipe; 320 super.contentHandler = pipe; 321 super.lexicalHandler = pipe; 322 } 323 324 public void setConsumer(XMLConsumer consumer) { 325 pipe.setConsumer(consumer); 326 } 327 328 public void setContentHandler(ContentHandler handler) { 329 pipe.setContentHandler(handler); 330 } 331 332 public void setLexicalHandler(LexicalHandler handler) { 333 pipe.setLexicalHandler(handler); 334 } 335 336 341 public void recycle() { 342 this.namespaces = null; 343 this.validity = null; 344 345 346 this.pipe.recycle(); 347 348 this.resolver = null; 351 352 super.recycle(); 353 } 354 355 356 361 public void startDocument() 362 throws SAXException { 363 364 getValidity(); 365 366 super.startDocument(); 367 } 368 369 374 public void endDocument() 375 throws SAXException { 376 377 this.validity.close(); 378 379 super.endDocument(); 380 } 381 382 391 public void startPrefixMapping(String prefix, String nsuri) 392 throws SAXException { 393 if (NS_URI.equals(nsuri)) { 394 395 this.namespaces.addDeclaration(prefix, nsuri); 396 } else { 397 398 super.startPrefixMapping(prefix, nsuri); 399 } 400 } 401 402 411 public void endPrefixMapping(String prefix) 412 throws SAXException { 413 if (NS_URI.equals(this.namespaces.getUri(prefix))) { 414 415 this.namespaces.removeDeclaration(prefix); 416 } else { 417 418 super.endPrefixMapping(prefix); 419 } 420 } 421 422 432 public Serializable getKey() { 433 442 return key == null? "I": "I" + key; 443 } 444 445 452 public SourceValidity getValidity() { 453 if (validity == null) { 454 validity = new MultiSourceValidity(resolver, -1); 455 } 456 return validity; 457 } 458 459 462 private class IncludeElement extends AbstractLogEnabled { 463 464 private boolean recursive; 465 466 467 private boolean parallel; 468 469 470 private boolean recursiveParallel; 471 472 473 private String base; 474 475 476 private String source; 477 478 479 private boolean parse; 480 481 482 private String mimeType; 483 484 485 private SaxBuffer fallback; 486 487 488 private Map parameters; 489 490 491 private String parameter; 492 493 494 private StringBuffer value; 495 496 497 private IncludeElement(String base, boolean parallel, boolean recursive, boolean recursiveParallel, Logger logger) { 498 this.base = base; 499 this.parallel = parallel; 500 this.recursive = recursive; 501 this.recursiveParallel = recursiveParallel; 502 this.enableLogging(logger); 503 } 504 505 509 public void process(SaxBuffer buffer) 510 throws SAXException { 511 try { 512 process0(buffer, buffer); 513 } catch (SAXException e) { 514 buffer.recycle(); 515 if (this.fallback == null) { 516 throw e; 517 } 518 519 if (getLogger().isInfoEnabled()) { 520 getLogger().info("Failed to load <" + this.source + ">, using fallback.", e); 521 } 522 this.fallback.toSAX(new IncludeXMLPipe(getLogger(), buffer, buffer, 524 recursive, recursiveParallel? parallel: false, recursiveParallel)); 525 } 526 } 527 528 529 public void process(ContentHandler contentHandler, LexicalHandler lexicalHandler) 530 throws SAXException { 531 if (this.fallback != null) { 532 SaxBuffer buffer = new SaxBuffer(); 533 process(buffer); 534 buffer.toSAX(contentHandler); 535 } else { 536 process0(contentHandler, lexicalHandler); 537 } 538 } 539 540 541 private void process0(ContentHandler contentHandler, LexicalHandler lexicalHandler) 542 throws SAXException { 543 Source source = null; 544 if (getLogger().isDebugEnabled()) { 545 getLogger().debug("Loading <" + this.source + ">"); 546 } 547 548 try { 550 if (base != null) { 551 source = resolver.resolveURI(this.source, base, null); 552 } else { 553 source = resolver.resolveURI(this.source); 554 } 555 if (validity != null) { 556 synchronized (validity) { 557 validity.addSource(source); 558 } 559 } 560 561 if (this.parse && recursive) { 563 SourceUtil.toSAX(manager, source, this.mimeType, 564 new IncludeXMLPipe(getLogger(), contentHandler, lexicalHandler, 565 recursive, recursiveParallel? parallel: false, recursiveParallel)); 566 } else if (this.parse) { 567 SourceUtil.toSAX(manager, source, this.mimeType, 568 new IncludeXMLConsumer(contentHandler, lexicalHandler)); 569 } else { 570 SourceUtil.toCharacters(source, "utf-8", 571 contentHandler); 572 } 573 574 if (getLogger().isDebugEnabled()) { 575 getLogger().debug("Loaded <" + this.source + ">"); 576 } 577 } catch (SAXException e) { 578 if (getLogger().isDebugEnabled()) { 579 getLogger().debug("Failed to load <" + this.source + ">", e); 580 } 581 582 throw e; 583 584 } catch (ProcessingException e) { 585 if (getLogger().isDebugEnabled()) { 586 getLogger().debug("Failed to load <" + this.source + ">", e); 587 } 588 589 throw new SAXException (e); 590 591 } catch (IOException e) { 592 if (getLogger().isDebugEnabled()) { 593 getLogger().debug("Failed to load <" + this.source + ">", e); 594 } 595 596 throw new SAXException (e); 597 598 } finally { 599 if (source != null) { 600 resolver.release(source); 601 } 602 } 603 } 604 } 605 606 609 private class IncludeXMLPipe extends AbstractXMLPipe { 610 611 615 616 private final boolean root; 617 618 619 private boolean recursive; 620 621 622 private boolean parallel; 623 624 625 private boolean recursiveParallel; 626 627 631 632 private final Stack consumers = new Stack (); 633 634 635 private int depth; 636 637 638 private String base; 639 640 641 private IncludeElement element; 642 643 644 private boolean buffering; 645 646 651 private SaxBuffer buffer; 652 653 654 private int threads; 655 656 659 public IncludeXMLPipe() { 660 root = true; 661 } 662 663 666 public IncludeXMLPipe(Logger logger, ContentHandler contentHandler, LexicalHandler lexicalHandler, 667 boolean recursive, boolean parallel, boolean recursiveParallel) { 668 root = false; 669 this.enableLogging(logger); 670 this.setContentHandler(contentHandler); 671 this.setLexicalHandler(lexicalHandler); 672 this.recursive = recursive; 673 this.parallel = parallel; 674 this.recursiveParallel = recursiveParallel; 675 } 676 677 680 public void recycle() { 681 if (this.buffering) { 682 waitForThreads(); 684 this.buffering = false; 685 this.buffer = null; 686 } 687 this.threads = 0; 688 689 this.consumers.clear(); 690 this.base = null; 691 this.element = null; 692 693 super.recycle(); 694 } 695 696 697 private void push(XMLConsumer consumer) { 698 this.consumers.push(new Object []{ super.xmlConsumer, super.contentHandler, super.lexicalHandler }); 699 this.setConsumer(consumer); 700 } 701 702 703 private void pop() { 704 Object [] consumer = (Object []) this.consumers.pop(); 705 if (consumer[0] != null) { 706 this.setConsumer((XMLConsumer) consumer[0]); 707 } else { 708 this.setContentHandler((ContentHandler ) consumer[1]); 709 this.setLexicalHandler((LexicalHandler ) consumer[2]); 710 } 711 } 712 713 717 public void setDocumentLocator(Locator locator) { 718 try { 719 if (locator != null && locator.getSystemId() != null) { 720 Source source = resolver.resolveURI(locator.getSystemId()); 721 try { 722 base = source.getURI(); 723 } finally { 724 resolver.release(source); 725 } 726 } 727 } catch (IOException e) { 728 getLogger().warn("Unable to resolve document base URI: <" + locator.getSystemId() + ">"); 729 } 730 731 super.setDocumentLocator(locator); 732 } 733 734 738 public void startDocument() throws SAXException { 739 if (root) { 740 super.startDocument(); 741 } 742 } 743 744 748 public void endDocument() throws SAXException { 749 750 if (this.buffering) { 751 pop(); 752 this.buffer.toSAX(super.contentHandler); 753 } 754 755 if (root) { 756 super.endDocument(); 757 } 758 } 759 760 764 public void startElement(String uri, String localName, String qName, Attributes atts) 765 throws SAXException { 766 767 768 if (NS_URI.equals(uri)) { 769 770 775 depth++; 776 777 778 if (INCLUDE_ELEMENT.equals(localName) && depth == 1) { 779 780 if (element != null) { 781 throw new SAXException ("Element " + INCLUDE_ELEMENT + " nested in another one."); 782 } 783 element = new IncludeElement(this.base, this.parallel, this.recursive, this.recursiveParallel, getLogger()); 784 785 786 element.source = atts.getValue(SRC_ATTRIBUTE); 787 if (element.source == null || element.source.length() == 0) { 788 throw new SAXException ("Attribute '" + SRC_ATTRIBUTE + "' empty or missing."); 789 } 790 791 792 String value = atts.getValue(PARSE_ATTRIBUTE); 793 if (value == null || value.equals("xml")) { 794 element.parse = true; 795 } else if (value.equals("text")) { 796 element.parse = false; 797 } else { 798 throw new SAXException ("Attribute '" + PARSE_ATTRIBUTE + "' has invalid value."); 799 } 800 801 802 element.mimeType = atts.getValue(MIME_ATTRIBUTE); 803 if (!element.parse && element.mimeType != null) { 804 throw new SAXException ("Attribute '" + MIME_ATTRIBUTE + "' can't be specified for text inclusions."); 805 } else if (element.mimeType == null) { 806 element.mimeType = "text/xml"; 807 } 808 809 810 push(new NOPRecorder(){}); 811 812 813 return; 814 } 815 816 817 if (FALLBACK_ELEMENT.equals(localName) && depth == 2) { 818 819 if (element == null) { 820 throw new SAXException ("Element " + FALLBACK_ELEMENT + " specified outside of " + INCLUDE_ELEMENT + "."); 821 } 822 if (element.fallback != null) { 823 throw new SAXException ("Duplicate element " + FALLBACK_ELEMENT + "."); 824 } 825 826 827 push(element.fallback = new SaxBuffer()); 828 829 830 return; 831 } 832 833 834 if (PARAMETER_ELEMENT.equals(localName) && depth == 2) { 835 836 if (element == null) { 837 throw new SAXException ("Element " + PARAMETER_ELEMENT + " specified outside of " + INCLUDE_ELEMENT + "."); 838 } 839 if (element.parameter != null) { 840 throw new SAXException ("Element " + PARAMETER_ELEMENT + " nested in another one."); 841 } 842 843 844 element.parameter = atts.getValue(NAME_ATTRIBUTE); 845 if (element.parameter == null || element.parameter.length() == 0) { 846 throw new SAXException ("Attribute '" + NAME_ATTRIBUTE + "' empty or missing."); 847 } 848 849 850 String value = atts.getValue(VALUE_ATTRIBUTE); 851 if (value != null) { 852 element.value = new StringBuffer (value); 853 } 854 855 856 return; 857 } 858 859 860 if (depth < 2) { 861 throw new SAXException ("Element '" + localName + "' was not expected here."); 862 } 863 } 864 865 super.startElement(uri, localName, qName, atts); 866 } 867 868 872 public void endElement(String uri, String localName, String qName) 873 throws SAXException { 874 875 if (NS_URI.equals(uri)) { 876 877 882 depth--; 883 884 885 if (INCLUDE_ELEMENT.equals(localName) && depth == 0) { 886 887 pop(); 888 889 890 if (element.parameters != null) { 891 element.source = NetUtils.parameterize(element.source, 892 element.parameters); 893 element.parameters = null; 894 } 895 896 897 if (this.parallel) { 898 if (!this.buffering) { 899 this.buffering = true; 900 buffer = new SaxBuffer(); 901 push(buffer); 902 } 903 904 905 buffer.xmlizable(new IncludeBuffer(element)); 906 907 } else { 908 909 element.process(super.contentHandler, super.lexicalHandler); 910 } 911 912 913 this.element = null; 914 return; 915 } 916 917 if (FALLBACK_ELEMENT.equals(localName) && depth == 1) { 918 919 pop(); 920 921 922 return; 923 } 924 925 926 if (PARAMETER_ELEMENT.equals(localName) && depth == 1) { 927 String value = (element.value != null? element.value.toString(): ""); 928 929 930 try { 931 936 if (element.parameters == null) { 937 element.parameters = new HashMap (5); 938 } 939 element.parameters.put(NetUtils.encode(element.parameter, ENCODING), 940 NetUtils.encode(value, ENCODING)); 941 } catch (UnsupportedEncodingException e) { 942 throw new SAXException ("Your platform does not support the " + 943 ENCODING + " encoding", e); 944 } 945 946 947 element.value = null; 948 element.parameter = null; 949 return; 950 } 951 } 952 953 954 super.endElement(uri, localName, qName); 955 } 956 957 961 public void characters(char[] data, int offset, int length) 962 throws SAXException { 963 if (element != null && element.parameter != null) { 964 965 if (element.value == null) { 966 element.value = new StringBuffer (); 967 } 968 element.value.append(data, offset, length); 969 return; 970 } 971 972 973 super.characters(data, offset, length); 974 } 975 976 980 983 int incrementThreads() { 984 synchronized (buffer) { 985 return ++threads; 986 } 987 } 988 989 992 void decrementThreads() { 993 synchronized (buffer) { 994 if (--threads <= 0) { 995 buffer.notify(); 996 } 997 } 998 } 999 1000 1003 private void waitForThreads() { 1004 synchronized (buffer) { 1005 if (threads > 0) { 1006 if (getLogger().isDebugEnabled()) { 1007 getLogger().debug(threads + " threads in progress, waiting"); 1008 } 1009 1010 try { 1011 buffer.wait(); 1012 } catch (InterruptedException ignored) { } 1013 } 1015 } 1016 } 1017 1018 1024 private class IncludeBuffer extends SaxBuffer 1025 implements Runnable { 1026 1027 private IncludeElement element; 1028 private int thread; 1029 private boolean finished; 1030 private SAXException e; 1031 1032 1033 public IncludeBuffer(IncludeElement element) { 1034 this.element = element; 1035 1036 RunnableManager runnable = null; 1037 try { 1038 runnable = (RunnableManager) IncludeTransformer.this.manager.lookup(RunnableManager.ROLE); 1039 runnable.execute(IncludeTransformer.this.threadPool, this); 1040 } catch (final ServiceException e) { 1041 throw new CascadingRuntimeException(e.getMessage(), e); 1043 } finally { 1044 IncludeTransformer.this.manager.release(runnable); 1045 } 1046 1047 this.thread = incrementThreads(); 1049 } 1050 1051 1054 public void run() { 1055 try { 1056 Source source = null; 1057 if (getLogger().isDebugEnabled()) { 1058 getLogger().debug("Thread #" + thread + " loading <" + element.source + ">"); 1059 } 1060 1061 CocoonComponentManager.enterEnvironment(environment, new WrapperComponentManager(manager), processor); 1063 try { 1064 element.process(this); 1065 1066 } catch (SAXException e) { 1067 this.e = e; 1068 1069 } finally { 1070 if (source != null) { 1071 resolver.release(source); 1072 } 1073 CocoonComponentManager.leaveEnvironment(); 1074 } 1075 } catch (RuntimeException e) { 1076 1077 this.e = new SAXException (e); 1078 1079 } finally { 1080 synchronized (this) { 1081 this.finished = true; 1082 notify(); 1083 } 1084 1085 decrementThreads(); 1087 } 1088 1089 if (getLogger().isDebugEnabled()) { 1090 if (this.e == null) { 1091 getLogger().debug("Thread #" + thread + " loaded <" + element.source + ">"); 1092 } else { 1093 getLogger().debug("Thread #" + thread + " failed to load <" + element.source + ">", this.e); 1094 } 1095 } 1096 } 1097 1098 1102 public void toSAX(ContentHandler contentHandler) 1103 throws SAXException { 1104 synchronized (this) { 1105 if (!this.finished) { 1106 try { 1107 wait(); 1108 } catch (InterruptedException ignored) { } 1109 } 1111 } 1112 1113 if (this.e != null) { 1114 throw this.e; 1115 } 1116 1117 super.toSAX(contentHandler); 1118 } 1119 } 1120 } 1121} 1122 | Popular Tags |