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             this.handler = getElementHandler(qName).newInstance();
 284             this.handler.setOwner(this);
 285             this.handler.setParent(parent);
 286         }
 287         catch (Exception exception) {
 288             throw new SAXException(exception);
 289         }
 290         for (int i = 0; i < attributes.getLength(); i++)
 291             try {
 292                 String name = attributes.getQName(i);
 293                 String value = attributes.getValue(i);
 294                 this.handler.addAttribute(name, value);
 295             }
 296             catch (RuntimeException exception) {
 297                 handleException(exception);
 298             }
 299 
 300         this.handler.startElement();
 301     }
 302 
 303     /**
 304      * Parses closing tag of XML element
 305      * using corresponding element handler.
 306      *
 307      * @param uri        the namespace URI, or the empty string
 308      *                   if the element has no namespace URI or
 309      *                   if namespace processing is not being performed
 310      * @param localName  the local name (without prefix), or the empty string
 311      *                   if namespace processing is not being performed
 312      * @param qName      the qualified name (with prefix), or the empty string
 313      *                   if qualified names are not available
 314      */
 315     @Override
 316     public void endElement(String uri, String localName, String qName) {
 317         try {
 318             this.handler.endElement();
 319         }
 320         catch (RuntimeException exception) {
 321             handleException(exception);
 322         }
 323         finally {
 324             this.handler = this.handler.getParent();
 325         }
 326     }
 327 
 328     /**
 329      * Parses character data inside XML element.
 330      *
 331      * @param chars   the array of characters
 332      * @param start   the start position in the character array
 333      * @param length  the number of characters to use
 334      */
 335     @Override
 336     public void characters(char[] chars, int start, int length) {
 337         if (this.handler != null) {
 338             try {
 339                 while (0 < length--) {
 340                     this.handler.addCharacter(chars[start++]);
 341                 }
 342             }
 343             catch (RuntimeException exception) {
 344                 handleException(exception);
 345             }
 346         }
 347     }
 348 
 349     /**
 350      * Handles an exception using current exception listener.
 351      *
 352      * @param exception  an exception to handle
 353      * @see #setExceptionListener
 354      */
 355     public void handleException(Exception exception) {
 356         if (this.listener == null) {
 357             throw new IllegalStateException(exception);
 358         }
 359         this.listener.exceptionThrown(exception);
 360     }
 361 
 362     /**
 363      * Starts parsing of the specified input source.
 364      *
 365      * @param input  the input source to parse
 366      */
 367     public void parse(final InputSource input) {
 368         if ((this.acc == null) && (null != System.getSecurityManager())) {
 369             throw new SecurityException("AccessControlContext is not set");
 370         }
 371         AccessControlContext stack = AccessController.getContext();
 372         SharedSecrets.getJavaSecurityAccess().doIntersectionPrivilege(new PrivilegedAction<Void>() {
 373             public Void run() {
 374                 try {
 375                     SAXParserFactory.newInstance().newSAXParser().parse(input, DocumentHandler.this);
 376                 }
 377                 catch (ParserConfigurationException exception) {
 378                     handleException(exception);
 379                 }
 380                 catch (SAXException wrapper) {
 381                     Exception exception = wrapper.getException();
 382                     if (exception == null) {
 383                         exception = wrapper;
 384                     }
 385                     handleException(exception);
 386                 }
 387                 catch (IOException exception) {
 388                     handleException(exception);
 389                 }
 390                 return null;
 391             }
 392         }, stack, this.acc);
 393     }
 394 
 395     /**
 396      * Resolves class by name using current class loader.
 397      * This method handles exception using current exception listener.
 398      *
 399      * @param name  the name of the class
 400      * @return the object that represents the class
 401      */
 402     public Class<?> findClass(String name) {
 403         try {
 404             return ClassFinder.resolveClass(name, getClassLoader());
 405         }
 406         catch (ClassNotFoundException exception) {
 407             handleException(exception);
 408             return null;
 409         }
 410     }
 411 }