1 /*
   2  * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package com.sun.beans.decoder;
  26 
  27 import com.sun.beans.finder.ClassFinder;
  28 
  29 import java.beans.ExceptionListener;
  30 
  31 import java.io.IOException;
  32 import java.io.StringReader;
  33 
  34 import java.lang.ref.Reference;
  35 import java.lang.ref.WeakReference;
  36 
  37 import java.util.ArrayList;
  38 import java.util.HashMap;
  39 import java.util.List;
  40 import java.util.Map;
  41 import java.security.AccessControlContext;
  42 import java.security.AccessController;
  43 import java.security.PrivilegedAction;
  44 
  45 import javax.xml.parsers.ParserConfigurationException;
  46 import javax.xml.parsers.SAXParserFactory;
  47 
  48 import org.xml.sax.Attributes;
  49 import org.xml.sax.InputSource;
  50 import org.xml.sax.SAXException;
  51 import org.xml.sax.helpers.DefaultHandler;
  52 
  53 import jdk.internal.misc.SharedSecrets;
  54 
  55 /**
  56  * The main class to parse JavaBeans XML archive.
  57  *
  58  * @since 1.7
  59  *
  60  * @author Sergey A. Malenkov
  61  *
  62  * @see ElementHandler
  63  */
  64 public final class DocumentHandler extends DefaultHandler {
  65     private final AccessControlContext acc = AccessController.getContext();
  66     private final Map<String, Class<? extends ElementHandler>> handlers = new HashMap<>();
  67     private final Map<String, Object> environment = new HashMap<>();
  68     private final List<Object> objects = new ArrayList<>();
  69 
  70     private Reference<ClassLoader> loader;
  71     private ExceptionListener listener;
  72     private Object owner;
  73 
  74     private ElementHandler handler;
  75 
  76     /**
  77      * Creates new instance of document handler.
  78      */
  79     public DocumentHandler() {
  80         setElementHandler("java", JavaElementHandler.class); // NON-NLS: the element name
  81         setElementHandler("null", NullElementHandler.class); // NON-NLS: the element name
  82         setElementHandler("array", ArrayElementHandler.class); // NON-NLS: the element name
  83         setElementHandler("class", ClassElementHandler.class); // NON-NLS: the element name
  84         setElementHandler("string", StringElementHandler.class); // NON-NLS: the element name
  85         setElementHandler("object", ObjectElementHandler.class); // NON-NLS: the element name
  86 
  87         setElementHandler("void", VoidElementHandler.class); // NON-NLS: the element name
  88         setElementHandler("char", CharElementHandler.class); // NON-NLS: the element name
  89         setElementHandler("byte", ByteElementHandler.class); // NON-NLS: the element name
  90         setElementHandler("short", ShortElementHandler.class); // NON-NLS: the element name
  91         setElementHandler("int", IntElementHandler.class); // NON-NLS: the element name
  92         setElementHandler("long", LongElementHandler.class); // NON-NLS: the element name
  93         setElementHandler("float", FloatElementHandler.class); // NON-NLS: the element name
  94         setElementHandler("double", DoubleElementHandler.class); // NON-NLS: the element name
  95         setElementHandler("boolean", BooleanElementHandler.class); // NON-NLS: the element name
  96 
  97         // some handlers for new elements
  98         setElementHandler("new", NewElementHandler.class); // NON-NLS: the element name
  99         setElementHandler("var", VarElementHandler.class); // NON-NLS: the element name
 100         setElementHandler("true", TrueElementHandler.class); // NON-NLS: the element name
 101         setElementHandler("false", FalseElementHandler.class); // NON-NLS: the element name
 102         setElementHandler("field", FieldElementHandler.class); // NON-NLS: the element name
 103         setElementHandler("method", MethodElementHandler.class); // NON-NLS: the element name
 104         setElementHandler("property", PropertyElementHandler.class); // NON-NLS: the element name
 105     }
 106 
 107     /**
 108      * Returns the class loader used to instantiate objects.
 109      * If the class loader has not been explicitly set
 110      * then {@code null} is returned.
 111      *
 112      * @return the class loader used to instantiate objects
 113      */
 114     public ClassLoader getClassLoader() {
 115         return (this.loader != null)
 116                 ? this.loader.get()
 117                 : null;
 118     }
 119 
 120     /**
 121      * Sets the class loader used to instantiate objects.
 122      * If the class loader is not set
 123      * then default class loader will be used.
 124      *
 125      * @param loader  a classloader to use
 126      */
 127     public void setClassLoader(ClassLoader loader) {
 128         this.loader = new WeakReference<ClassLoader>(loader);
 129     }
 130 
 131     /**
 132      * Returns the exception listener for parsing.
 133      * The exception listener is notified
 134      * when handler catches recoverable exceptions.
 135      * If the exception listener has not been explicitly set
 136      * then default exception listener is returned.
 137      *
 138      * @return the exception listener for parsing
 139      */
 140     public ExceptionListener getExceptionListener() {
 141         return this.listener;
 142     }
 143 
 144     /**
 145      * Sets the exception listener for parsing.
 146      * The exception listener is notified
 147      * when handler catches recoverable exceptions.
 148      *
 149      * @param listener  the exception listener for parsing
 150      */
 151     public void setExceptionListener(ExceptionListener listener) {
 152         this.listener = listener;
 153     }
 154 
 155     /**
 156      * Returns the owner of this document handler.
 157      *
 158      * @return the owner of this document handler
 159      */
 160     public Object getOwner() {
 161         return this.owner;
 162     }
 163 
 164     /**
 165      * Sets the owner of this document handler.
 166      *
 167      * @param owner  the owner of this document handler
 168      */
 169     public void setOwner(Object owner) {
 170         this.owner = owner;
 171     }
 172 
 173     /**
 174      * Returns the handler for the element with specified name.
 175      *
 176      * @param name  the name of the element
 177      * @return the corresponding element handler
 178      */
 179     public Class<? extends ElementHandler> getElementHandler(String name) {
 180         Class<? extends ElementHandler> type = this.handlers.get(name);
 181         if (type == null) {
 182             throw new IllegalArgumentException("Unsupported element: " + name);
 183         }
 184         return type;
 185     }
 186 
 187     /**
 188      * Sets the handler for the element with specified name.
 189      *
 190      * @param name     the name of the element
 191      * @param handler  the corresponding element handler
 192      */
 193     public void setElementHandler(String name, Class<? extends ElementHandler> handler) {
 194         this.handlers.put(name, handler);
 195     }
 196 
 197     /**
 198      * Indicates whether the variable with specified identifier is defined.
 199      *
 200      * @param id  the identifier
 201      * @return @{code true} if the variable is defined;
 202      *         @{code false} otherwise
 203      */
 204     public boolean hasVariable(String id) {
 205         return this.environment.containsKey(id);
 206     }
 207 
 208     /**
 209      * Returns the value of the variable with specified identifier.
 210      *
 211      * @param id  the identifier
 212      * @return the value of the variable
 213      */
 214     public Object getVariable(String id) {
 215         if (!this.environment.containsKey(id)) {
 216             throw new IllegalArgumentException("Unbound variable: " + id);
 217         }
 218         return this.environment.get(id);
 219     }
 220 
 221     /**
 222      * Sets new value of the variable with specified identifier.
 223      *
 224      * @param id     the identifier
 225      * @param value  new value of the variable
 226      */
 227     public void setVariable(String id, Object value) {
 228         this.environment.put(id, value);
 229     }
 230 
 231     /**
 232      * Returns the array of readed objects.
 233      *
 234      * @return the array of readed objects
 235      */
 236     public Object[] getObjects() {
 237         return this.objects.toArray();
 238     }
 239 
 240     /**
 241      * Adds the object to the list of readed objects.
 242      *
 243      * @param object  the object that is readed from XML document
 244      */
 245     void addObject(Object object) {
 246         this.objects.add(object);
 247     }
 248 
 249     /**
 250      * Disables any external entities.
 251      */
 252     @Override
 253     public InputSource resolveEntity(String publicId, String systemId) {
 254         return new InputSource(new StringReader(""));
 255     }
 256 
 257     /**
 258      * Prepares this handler to read objects from XML document.
 259      */
 260     @Override
 261     public void startDocument() {
 262         this.objects.clear();
 263         this.handler = null;
 264     }
 265 
 266     /**
 267      * Parses opening tag of XML element
 268      * using corresponding element handler.
 269      *
 270      * @param uri         the namespace URI, or the empty string
 271      *                    if the element has no namespace URI or
 272      *                    if namespace processing is not being performed
 273      * @param localName   the local name (without prefix), or the empty string
 274      *                    if namespace processing is not being performed
 275      * @param qName       the qualified name (with prefix), or the empty string
 276      *                    if qualified names are not available
 277      * @param attributes  the attributes attached to the element
 278      */
 279     @Override
 280     public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
 281         ElementHandler parent = this.handler;
 282         try {
 283             @SuppressWarnings("deprecation")
 284             ElementHandler tmp = getElementHandler(qName).newInstance();
 285             this.handler = tmp;
 286             this.handler.setOwner(this);
 287             this.handler.setParent(parent);
 288         }
 289         catch (Exception exception) {
 290             throw new SAXException(exception);
 291         }
 292         for (int i = 0; i < attributes.getLength(); i++)
 293             try {
 294                 String name = attributes.getQName(i);
 295                 String value = attributes.getValue(i);
 296                 this.handler.addAttribute(name, value);
 297             }
 298             catch (RuntimeException exception) {
 299                 handleException(exception);
 300             }
 301 
 302         this.handler.startElement();
 303     }
 304 
 305     /**
 306      * Parses closing tag of XML element
 307      * using corresponding element handler.
 308      *
 309      * @param uri        the namespace URI, or the empty string
 310      *                   if the element has no namespace URI or
 311      *                   if namespace processing is not being performed
 312      * @param localName  the local name (without prefix), or the empty string
 313      *                   if namespace processing is not being performed
 314      * @param qName      the qualified name (with prefix), or the empty string
 315      *                   if qualified names are not available
 316      */
 317     @Override
 318     public void endElement(String uri, String localName, String qName) {
 319         try {
 320             this.handler.endElement();
 321         }
 322         catch (RuntimeException exception) {
 323             handleException(exception);
 324         }
 325         finally {
 326             this.handler = this.handler.getParent();
 327         }
 328     }
 329 
 330     /**
 331      * Parses character data inside XML element.
 332      *
 333      * @param chars   the array of characters
 334      * @param start   the start position in the character array
 335      * @param length  the number of characters to use
 336      */
 337     @Override
 338     public void characters(char[] chars, int start, int length) {
 339         if (this.handler != null) {
 340             try {
 341                 while (0 < length--) {
 342                     this.handler.addCharacter(chars[start++]);
 343                 }
 344             }
 345             catch (RuntimeException exception) {
 346                 handleException(exception);
 347             }
 348         }
 349     }
 350 
 351     /**
 352      * Handles an exception using current exception listener.
 353      *
 354      * @param exception  an exception to handle
 355      * @see #setExceptionListener
 356      */
 357     public void handleException(Exception exception) {
 358         if (this.listener == null) {
 359             throw new IllegalStateException(exception);
 360         }
 361         this.listener.exceptionThrown(exception);
 362     }
 363 
 364     /**
 365      * Starts parsing of the specified input source.
 366      *
 367      * @param input  the input source to parse
 368      */
 369     public void parse(final InputSource input) {
 370         if ((this.acc == null) && (null != System.getSecurityManager())) {
 371             throw new SecurityException("AccessControlContext is not set");
 372         }
 373         AccessControlContext stack = AccessController.getContext();
 374         SharedSecrets.getJavaSecurityAccess().doIntersectionPrivilege(new PrivilegedAction<Void>() {
 375             public Void run() {
 376                 try {
 377                     SAXParserFactory.newInstance().newSAXParser().parse(input, DocumentHandler.this);
 378                 }
 379                 catch (ParserConfigurationException exception) {
 380                     handleException(exception);
 381                 }
 382                 catch (SAXException wrapper) {
 383                     Exception exception = wrapper.getException();
 384                     if (exception == null) {
 385                         exception = wrapper;
 386                     }
 387                     handleException(exception);
 388                 }
 389                 catch (IOException exception) {
 390                     handleException(exception);
 391                 }
 392                 return null;
 393             }
 394         }, stack, this.acc);
 395     }
 396 
 397     /**
 398      * Resolves class by name using current class loader.
 399      * This method handles exception using current exception listener.
 400      *
 401      * @param name  the name of the class
 402      * @return the object that represents the class
 403      */
 404     public Class<?> findClass(String name) {
 405         try {
 406             return ClassFinder.resolveClass(name, getClassLoader());
 407         }
 408         catch (ClassNotFoundException exception) {
 409             handleException(exception);
 410             return null;
 411         }
 412     }
 413 }