示例#1
0
/**
 * Implementation of the ArchiveHandler for war files.
 *
 * @author Jerome Dochez, Sanjeeb Sahoo, Shing Wai Chan
 */
@Service(name = WarDetector.ARCHIVE_TYPE)
public class WarHandler extends AbstractArchiveHandler {

  private static final String GLASSFISH_WEB_XML = "WEB-INF/glassfish-web.xml";
  private static final String SUN_WEB_XML = "WEB-INF/sun-web.xml";
  private static final String WEBLOGIC_XML = "WEB-INF/weblogic.xml";
  private static final String WAR_CONTEXT_XML = "META-INF/context.xml";
  private static final String DEFAULT_CONTEXT_XML = "config/context.xml";
  private static final Logger logger = WebappClassLoader.logger;
  private static final ResourceBundle rb = logger.getResourceBundle();
  private static final LocalStringManagerImpl localStrings =
      new LocalStringManagerImpl(WarHandler.class);

  @LogMessageInfo(
      message = "extra-class-path component [{0}] is not a valid pathname",
      level = "SEVERE",
      cause = "A naming exception is encountered",
      action = "Check the list of resources")
  public static final String CLASSPATH_ERROR = "AS-WEB-UTIL-00027";

  @LogMessageInfo(
      message = "The clearReferencesStatic is not consistent in context.xml for virtual servers",
      level = "WARNING")
  public static final String INCONSISTENT_CLEAR_REFERENCE_STATIC = "AS-WEB-UTIL-00028";

  @LogMessageInfo(
      message = "class-loader attribute dynamic-reload-interval in sun-web.xml not supported",
      level = "WARNING")
  public static final String DYNAMIC_RELOAD_INTERVAL = "AS-WEB-UTIL-00029";

  @LogMessageInfo(
      message = "Property element in sun-web.xml has null 'name' or 'value'",
      level = "WARNING")
  public static final String NULL_WEB_PROPERTY = "AS-WEB-UTIL-00030";

  @LogMessageInfo(message = "Ignoring invalid property [{0}] = [{1}]", level = "WARNING")
  public static final String INVALID_PROPERTY = "AS-WEB-UTIL-00031";

  @LogMessageInfo(message = "The xml element should be [{0}] rather than [{1}]", level = "INFO")
  public static final String UNEXPECTED_XML_ELEMENT = "AS-WEB-UTIL-00032";

  @LogMessageInfo(message = "This is an unexpected end of document", level = "WARNING")
  public static final String UNEXPECTED_END_DOCUMENT = "AS-WEB-UTIL-00033";

  // the following two system properties need to be in sync with DOLUtils
  private static final boolean gfDDOverWLSDD =
      Boolean.valueOf(System.getProperty("gfdd.over.wlsdd"));
  private static final boolean ignoreWLSDD = Boolean.valueOf(System.getProperty("ignore.wlsdd"));

  @Inject
  @Named(WarDetector.ARCHIVE_TYPE)
  private ArchiveDetector detector;

  @Inject
  @Named(ServerEnvironment.DEFAULT_INSTANCE_NAME)
  private Config serverConfig;

  @Inject private ServerEnvironment serverEnvironment;

  @Override
  public String getArchiveType() {
    return WarDetector.ARCHIVE_TYPE;
  }

  @Override
  public String getVersionIdentifier(ReadableArchive archive) {
    String versionIdentifierValue = null;
    try {
      WebXmlParser webXmlParser = getWebXmlParser(archive);
      versionIdentifierValue = webXmlParser.getVersionIdentifier();
    } catch (XMLStreamException e) {
      logger.log(Level.SEVERE, e.getMessage());
    } catch (IOException e) {
      logger.log(Level.SEVERE, e.getMessage());
    }
    return versionIdentifierValue;
  }

  @Override
  public boolean handles(ReadableArchive archive) throws IOException {
    return detector.handles(archive);
  }

  @Override
  public ClassLoader getClassLoader(final ClassLoader parent, DeploymentContext context) {
    WebappClassLoader cloader =
        AccessController.doPrivileged(
            new PrivilegedAction<WebappClassLoader>() {
              @Override
              public WebappClassLoader run() {
                return new WebappClassLoader(parent);
              }
            });
    try {
      WebDirContext r = new WebDirContext();
      File base = new File(context.getSource().getURI());
      r.setDocBase(base.getAbsolutePath());

      cloader.setResources(r);
      cloader.addRepository("WEB-INF/classes/", new File(base, "WEB-INF/classes/"));
      if (context.getScratchDir("ejb") != null) {
        cloader.addRepository(context.getScratchDir("ejb").toURI().toURL().toString().concat("/"));
      }
      if (context.getScratchDir("jsp") != null) {
        cloader.setWorkDir(context.getScratchDir("jsp"));
      }

      // add libraries referenced from manifest
      for (URL url : getManifestLibraries(context)) {
        cloader.addRepository(url.toString());
      }

      WebXmlParser webXmlParser = getWebXmlParser(context.getSource());
      configureLoaderAttributes(cloader, webXmlParser, base);
      configureLoaderProperties(cloader, webXmlParser, base);

      configureContextXmlAttribute(cloader, base, context);

      try {
        final DeploymentContext dc = context;
        final ClassLoader cl = cloader;

        AccessController.doPrivileged(
            new PermsArchiveDelegate.SetPermissionsAction(
                SMGlobalPolicyUtil.CommponentType.war, dc, cl));
      } catch (PrivilegedActionException e) {
        throw new SecurityException(e.getException());
      }

    } catch (XMLStreamException xse) {
      logger.log(Level.SEVERE, xse.getMessage());
      if (logger.isLoggable(Level.FINE)) {
        logger.log(Level.FINE, xse.getMessage(), xse);
      }
      xse.printStackTrace();
    } catch (IOException ioe) {
      logger.log(Level.SEVERE, ioe.getMessage());
      if (logger.isLoggable(Level.FINE)) {
        logger.log(Level.FINE, ioe.getMessage(), ioe);
      }
      ioe.printStackTrace();
    }

    cloader.start();

    return cloader;
  }

  protected WebXmlParser getWebXmlParser(ReadableArchive archive)
      throws XMLStreamException, IOException {

    WebXmlParser webXmlParser = null;
    boolean hasWSLDD = archive.exists(WEBLOGIC_XML);
    File runtimeAltDDFile =
        archive.getArchiveMetaData(DeploymentProperties.RUNTIME_ALT_DD, File.class);
    if (runtimeAltDDFile != null
        && "glassfish-web.xml".equals(runtimeAltDDFile.getPath())
        && runtimeAltDDFile.isFile()) {
      webXmlParser = new GlassFishWebXmlParser(archive);
    } else if (!gfDDOverWLSDD && !ignoreWLSDD && hasWSLDD) {
      webXmlParser = new WeblogicXmlParser(archive);
    } else if (archive.exists(GLASSFISH_WEB_XML)) {
      webXmlParser = new GlassFishWebXmlParser(archive);
    } else if (archive.exists(SUN_WEB_XML)) {
      webXmlParser = new SunWebXmlParser(archive);
    } else if (gfDDOverWLSDD && !ignoreWLSDD && hasWSLDD) {
      webXmlParser = new WeblogicXmlParser(archive);
    } else { // default
      if (gfDDOverWLSDD || ignoreWLSDD) {
        webXmlParser = new GlassFishWebXmlParser(archive);
      } else {
        webXmlParser = new WeblogicXmlParser(archive);
      }
    }
    return webXmlParser;
  }

  protected void configureLoaderAttributes(
      WebappClassLoader cloader, WebXmlParser webXmlParser, File base) {

    boolean delegate = webXmlParser.isDelegate();
    cloader.setDelegate(delegate);
    if (logger.isLoggable(Level.FINE)) {
      logger.fine("WebModule[" + base + "]: Setting delegate to " + delegate);
    }

    String extraClassPath = webXmlParser.getExtraClassPath();
    if (extraClassPath != null) {
      // Parse the extra classpath into its ':' and ';' separated
      // components. Ignore ':' as a separator if it is preceded by
      // '\'
      String[] pathElements = extraClassPath.split(";|((?<!\\\\):)");
      for (String path : pathElements) {
        path = path.replace("\\:", ":");
        if (logger.isLoggable(Level.FINE)) {
          logger.fine("WarHandler[" + base + "]: Adding " + path + " to the classpath");
        }

        try {
          URL url = new URL(path);
          cloader.addRepository(path);
        } catch (MalformedURLException mue1) {
          // Not a URL, interpret as file
          File file = new File(path);
          // START GlassFish 904
          if (!file.isAbsolute()) {
            // Resolve relative extra class path to the
            // context's docroot
            file = new File(base.getPath(), path);
          }
          // END GlassFish 904

          try {
            URL url = file.toURI().toURL();
            cloader.addRepository(url.toString());
          } catch (MalformedURLException mue2) {
            String msg = rb.getString(CLASSPATH_ERROR);
            Object[] params = {path};
            msg = MessageFormat.format(msg, params);
            logger.log(Level.SEVERE, msg, mue2);
          }
        }
      }
    }
  }

  protected void configureLoaderProperties(
      WebappClassLoader cloader, WebXmlParser webXmlParser, File base) {

    cloader.setUseMyFaces(webXmlParser.isUseBundledJSF());

    File libDir = new File(base, "WEB-INF/lib");
    if (libDir.exists()) {
      int baseFileLen = base.getPath().length();
      final boolean ignoreHiddenJarFiles = webXmlParser.isIgnoreHiddenJarFiles();

      for (File file :
          libDir.listFiles(
              new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                  String fileName = pathname.getName();
                  return ((fileName.endsWith(".jar") || fileName.endsWith(".zip"))
                      && (!ignoreHiddenJarFiles || !fileName.startsWith(".")));
                }
              })) {
        try {
          if (file.isDirectory()) {
            // support exploded jar file
            cloader.addRepository("WEB-INF/lib/" + file.getName() + "/", file);
          } else {
            cloader.addJar(file.getPath().substring(baseFileLen), new JarFile(file), file);
            cloader.closeJARs(true);
          }
        } catch (Exception e) {
          // Catch and ignore any exception in case the JAR file
          // is empty.
        }
      }
    }
  }

  protected void configureContextXmlAttribute(
      WebappClassLoader cloader, File base, DeploymentContext dc)
      throws XMLStreamException, IOException {

    boolean consistent = true;
    Boolean value = null;

    File warContextXml = new File(base.getAbsolutePath(), WAR_CONTEXT_XML);
    if (warContextXml.exists()) {
      ContextXmlParser parser = new ContextXmlParser(warContextXml);
      value = parser.getClearReferencesStatic();
    }

    if (value == null) {
      Boolean domainCRS = null;
      File defaultContextXml = new File(serverEnvironment.getInstanceRoot(), DEFAULT_CONTEXT_XML);
      if (defaultContextXml.exists()) {
        ContextXmlParser parser = new ContextXmlParser(defaultContextXml);
        domainCRS = parser.getClearReferencesStatic();
      }

      List<Boolean> csrs = new ArrayList<Boolean>();
      HttpService httpService = serverConfig.getHttpService();
      DeployCommandParameters params = dc.getCommandParameters(DeployCommandParameters.class);
      String vsIDs = params.virtualservers;
      List<String> vsList = StringUtils.parseStringList(vsIDs, " ,");
      if (httpService != null && vsList != null && !vsList.isEmpty()) {
        for (VirtualServer vsBean : httpService.getVirtualServer()) {
          if (vsList.contains(vsBean.getId())) {
            Boolean csr = null;
            Property prop = vsBean.getProperty("contextXmlDefault");
            if (prop != null) {
              File contextXml = new File(serverEnvironment.getInstanceRoot(), prop.getValue());
              if (contextXml.exists()) { // vs context.xml
                ContextXmlParser parser = new ContextXmlParser(contextXml);
                csr = parser.getClearReferencesStatic();
              }
            }

            if (csr == null) {
              csr = domainCRS;
            }
            csrs.add(csr);
          }
        }

        // check that it is consistent
        for (Boolean b : csrs) {
          if (b != null) {
            if (value != null && !b.equals(value)) {
              consistent = false;
              break;
            }
            value = b;
          }
        }
      }
    }

    if (consistent) {
      if (value != null) {
        cloader.setClearReferencesStatic(value);
      }
    } else if (logger.isLoggable(Level.WARNING)) {
      logger.log(Level.WARNING, INCONSISTENT_CLEAR_REFERENCE_STATIC);
    }
  }

  // ---- inner class ----
  protected abstract class BaseXmlParser {
    protected XMLStreamReader parser = null;

    /**
     * This method will parse the input stream and set the XMLStreamReader object for latter use.
     *
     * @param input InputStream
     * @exception XMLStreamException;
     */
    protected abstract void read(InputStream input) throws XMLStreamException;

    protected void init(InputStream input) throws XMLStreamException {

      try {
        read(input);
      } finally {
        if (parser != null) {
          try {
            parser.close();
          } catch (Exception ex) {
            // ignore
          }
        }
      }
    }

    protected void skipRoot(String name) throws XMLStreamException {
      while (true) {
        int event = parser.next();
        if (event == START_ELEMENT) {
          String localName = parser.getLocalName();
          if (!name.equals(localName)) {
            String msg = rb.getString(UNEXPECTED_XML_ELEMENT);
            msg = MessageFormat.format(msg, new Object[] {name, localName});
            throw new XMLStreamException(msg);
          }
          return;
        }
      }
    }

    protected void skipSubTree(String name) throws XMLStreamException {
      while (true) {
        int event = parser.next();
        if (event == END_DOCUMENT) {
          throw new XMLStreamException(rb.getString(UNEXPECTED_END_DOCUMENT));
        } else if (event == END_ELEMENT && name.equals(parser.getLocalName())) {
          return;
        }
      }
    }
  }

  protected abstract class WebXmlParser extends BaseXmlParser {
    protected boolean delegate = true;

    protected boolean ignoreHiddenJarFiles = false;
    protected boolean useBundledJSF = false;
    protected String extraClassPath = null;

    protected String versionIdentifier = null;

    WebXmlParser(ReadableArchive archive) throws XMLStreamException, IOException {

      if (archive.exists(getXmlFileName())) {
        try (InputStream is = archive.getEntry(getXmlFileName())) {
          init(is);
        } catch (Throwable t) {
          String msg =
              localStrings.getLocalString(
                  "web.deployment.exception_parsing_webxml",
                  "Error in parsing {0} for archive [{1}]: {2}",
                  getXmlFileName(),
                  archive.getURI(),
                  t.getMessage());
          throw new RuntimeException(msg);
        }
      }
    }

    protected abstract String getXmlFileName();

    boolean isDelegate() {
      return delegate;
    }

    boolean isIgnoreHiddenJarFiles() {
      return ignoreHiddenJarFiles;
    }

    String getExtraClassPath() {
      return extraClassPath;
    }

    boolean isUseBundledJSF() {
      return useBundledJSF;
    }

    String getVersionIdentifier() {
      return versionIdentifier;
    }
  }

  protected class SunWebXmlParser extends WebXmlParser {
    // XXX need to compute the default delegate depending on the version of dtd
    /*
     * The DOL will *always* return a value: If 'delegate' has not been
     * configured in sun-web.xml, its default value will be returned,
     * which is FALSE in the case of sun-web-app_2_2-0.dtd and
     * sun-web-app_2_3-0.dtd, and TRUE in the case of
     * sun-web-app_2_4-0.dtd.
     */

    SunWebXmlParser(ReadableArchive archive) throws XMLStreamException, IOException {

      super(archive);
    }

    @Override
    protected String getXmlFileName() {
      return SUN_WEB_XML;
    }

    protected String getRootElementName() {
      return "sun-web-app";
    }

    @Override
    protected void read(InputStream input) throws XMLStreamException {
      parser = getXMLInputFactory().createXMLStreamReader(input);

      int event = 0;
      boolean inClassLoader = false;
      skipRoot(getRootElementName());

      while (parser.hasNext() && (event = parser.next()) != END_DOCUMENT) {
        if (event == START_ELEMENT) {
          String name = parser.getLocalName();
          if ("class-loader".equals(name)) {
            int count = parser.getAttributeCount();
            for (int i = 0; i < count; i++) {
              String attrName = parser.getAttributeName(i).getLocalPart();
              if ("delegate".equals(attrName)) {
                delegate = Boolean.valueOf(parser.getAttributeValue(i));
              } else if ("extra-class-path".equals(attrName)) {
                extraClassPath = parser.getAttributeValue(i);
              } else if ("dynamic-reload-interval".equals(attrName)) {
                if (parser.getAttributeValue(i) != null) {
                  // Log warning if dynamic-reload-interval is specified
                  // in sun-web.xml since it is not supported
                  if (logger.isLoggable(Level.WARNING)) {
                    logger.log(Level.WARNING, DYNAMIC_RELOAD_INTERVAL);
                  }
                }
              }
            }
            inClassLoader = true;
          } else if (inClassLoader && "property".equals(name)) {
            int count = parser.getAttributeCount();
            String propName = null;
            String value = null;
            for (int i = 0; i < count; i++) {
              String attrName = parser.getAttributeName(i).getLocalPart();
              if ("name".equals(attrName)) {
                propName = parser.getAttributeValue(i);
              } else if ("value".equals(attrName)) {
                value = parser.getAttributeValue(i);
              }
            }

            if (propName == null || value == null) {
              throw new IllegalArgumentException(rb.getString(NULL_WEB_PROPERTY));
            }

            if ("ignoreHiddenJarFiles".equals(propName)) {
              ignoreHiddenJarFiles = Boolean.valueOf(value);
            } else {
              Object[] params = {propName, value};
              if (logger.isLoggable(Level.WARNING)) {
                logger.log(Level.WARNING, INVALID_PROPERTY, params);
              }
            }
          } else if ("property".equals(name)) {
            int count = parser.getAttributeCount();
            String propName = null;
            String value = null;
            for (int i = 0; i < count; i++) {
              String attrName = parser.getAttributeName(i).getLocalPart();
              if ("name".equals(attrName)) {
                propName = parser.getAttributeValue(i);
              } else if ("value".equals(attrName)) {
                value = parser.getAttributeValue(i);
              }
            }

            if (propName == null || value == null) {
              throw new IllegalArgumentException(rb.getString(NULL_WEB_PROPERTY));
            }

            if ("useMyFaces".equalsIgnoreCase(propName)) {
              useBundledJSF = Boolean.valueOf(value);
            } else if ("useBundledJsf".equalsIgnoreCase(propName)) {
              useBundledJSF = Boolean.valueOf(value);
            }
          } else if ("version-identifier".equals(name)) {
            versionIdentifier = parser.getElementText();
          } else {
            skipSubTree(name);
          }
        } else if (inClassLoader && event == END_ELEMENT) {
          if ("class-loader".equals(parser.getLocalName())) {
            inClassLoader = false;
          }
        }
      }
    }
  }

  protected class GlassFishWebXmlParser extends SunWebXmlParser {

    GlassFishWebXmlParser(ReadableArchive archive) throws XMLStreamException, IOException {

      super(archive);
    }

    @Override
    protected String getXmlFileName() {
      return GLASSFISH_WEB_XML;
    }

    @Override
    protected String getRootElementName() {
      return "glassfish-web-app";
    }
  }

  protected class WeblogicXmlParser extends WebXmlParser {
    WeblogicXmlParser(ReadableArchive archive) throws XMLStreamException, IOException {

      super(archive);
    }

    @Override
    protected String getXmlFileName() {
      return WEBLOGIC_XML;
    }

    @Override
    protected void read(InputStream input) throws XMLStreamException {
      parser = getXMLInputFactory().createXMLStreamReader(input);

      skipRoot("weblogic-web-app");

      int event = 0;
      while (parser.hasNext() && (event = parser.next()) != END_DOCUMENT) {
        if (event == START_ELEMENT) {
          String name = parser.getLocalName();
          if ("prefer-web-inf-classes".equals(name)) {
            // weblogic DD has default "false" for perfer-web-inf-classes
            delegate = !Boolean.parseBoolean(parser.getElementText());
            break;
          } else if (!"container-descriptor".equals(name)) {
            skipSubTree(name);
          }
        }
      }
    }
  }

  protected class ContextXmlParser extends BaseXmlParser {
    protected Boolean clearReferencesStatic = null;

    ContextXmlParser(File contextXmlFile) throws XMLStreamException, IOException {

      if (contextXmlFile.exists()) {
        try (InputStream is = new FileInputStream(contextXmlFile)) {
          init(is);
        }
      }
    }

    /**
     * This method will parse the input stream and set the XMLStreamReader object for latter use.
     *
     * @param input InputStream
     * @exception XMLStreamException;
     */
    @Override
    protected void read(InputStream input) throws XMLStreamException {
      parser = getXMLInputFactory().createXMLStreamReader(input);

      int event = 0;
      while (parser.hasNext() && (event = parser.next()) != END_DOCUMENT) {
        if (event == START_ELEMENT) {
          String name = parser.getLocalName();
          if ("Context".equals(name)) {
            String path = null;
            Boolean crs = null;
            int count = parser.getAttributeCount();
            for (int i = 0; i < count; i++) {
              String attrName = parser.getAttributeName(i).getLocalPart();
              if ("clearReferencesStatic".equals(attrName)) {
                crs = Boolean.valueOf(parser.getAttributeValue(i));
              } else if ("path".equals(attrName)) {
                path = parser.getAttributeValue(i);
              }
            }
            if (path == null) { // make sure no path associated to it
              clearReferencesStatic = crs;
              break;
            }
          } else {
            skipSubTree(name);
          }
        }
      }
    }

    Boolean getClearReferencesStatic() {
      return clearReferencesStatic;
    }
  }

  /**
   * Returns the classpath URIs for this archive.
   *
   * @param archive file
   * @return classpath URIs for this archive
   */
  @Override
  public List<URI> getClassPathURIs(ReadableArchive archive) {
    List<URI> uris = super.getClassPathURIs(archive);
    try {
      File archiveFile = new File(archive.getURI());
      if (archiveFile.exists() && archiveFile.isDirectory()) {
        uris.add(new URI(archive.getURI().toString() + "WEB-INF/classes/"));
        File webInf = new File(archiveFile, "WEB-INF");
        File webInfLib = new File(webInf, "lib");
        if (webInfLib.exists()) {
          uris.addAll(ASClassLoaderUtil.getLibDirectoryJarURIs(webInfLib));
        }
      }
    } catch (Exception e) {
      logger.log(Level.WARNING, e.getMessage(), e);
    }
    return uris;
  }
}
/** @author Karl Bennett */
public class BailsStreamSTAX implements IBailsStream {

  // The new line char for the system.
  private static final String SYSTEM_NEW_LINE = System.getProperty("line.separator");
  // The default unix new line char.
  private static final String UNIX_NEW_LINE = "\n";

  // A regexp to check for a new line(s) surrounded by white space.
  private static final String SYSTEM_NEW_LINE_REGEXP = "(\\s*" + SYSTEM_NEW_LINE + "+\\s*)+";
  private static final String UNIX_NEW_LINE_REGEXP = "(\\s*" + UNIX_NEW_LINE + "+\\s*)+";

  private XMLEventReader parser; // The STAX parser to be used throughout this class.

  private XMLEvent currentEvent; // The xml event that is currently being pointed to by this stream.

  // The white space that should be placed before the char sequence of the current event.
  private String preWhiteSpace = "";
  // The white space that should be placed after the char sequence of the current event.
  private String postWhiteSpace = "";

  // The char sequence of the current xml event including whitespace.
  private StringBuilder currentEventString = new StringBuilder(0);

  private Map<String, Object> attributes; // The attributes for the current xml event.

  private boolean bailsTag = false;

  private ELEMENT_TYPE type;

  public BailsStreamSTAX(InputStream stream) {
    XMLInputFactory factory = XMLInputFactory.newInstance(); // Get a new STAX factory class.

    try {
      this.parser = factory.createXMLEventReader(stream); // Get a new STAX event reader.
    } catch (XMLStreamException e) {
      System.out.println("SORT THIS OUT!: " + e.getMessage());
    }
  }

  /*
     Convenience methods.
  */

  /**
   * Check to see if the next even is just a new line character. If so add it to the end of the last
   * events string.
   */
  private void checkForNewLine() {

    preWhiteSpace = "";
    postWhiteSpace = "";

    if (hasNext()) { // If there is a next event...
      try {
        XMLEvent event = parser.peek(); // ...peek at it and...
        String eventString = event.toString(); // ...get it's character representation.

        // Then if the next event is just a new ling and whitespace...
        if (eventString.matches(UNIX_NEW_LINE_REGEXP)
            || eventString.matches(SYSTEM_NEW_LINE_REGEXP)) {
          // Record the whitespace that needs to be placed before the next real element.
          preWhiteSpace = eventString.substring(eventString.indexOf('\n') + 1);
          // Record the whitespace that needs to be placed after this element.
          postWhiteSpace = eventString.substring(0, eventString.indexOf('\n') + 1);
          parser.nextEvent(); // Lastly move on to the next element what ever it may be.
        }
      } catch (XMLStreamException e) {
        System.out.println("SORT THIS OUT!: " + e.getMessage());
      }
    }
  }

  /**
   * Pars the STAX attributes into the Bails attribute map.
   *
   * @param element a STAX @link javax.xml.stream.events.StartElement that may or may not contain
   *     attributes.
   * @return a map of string object key value pairs that represent the Bails attributes.
   */
  private Map<String, Object> parsAttributes(StartElement element) {
    Map<String, Object> attributes =
        new HashMap<String, Object>(); // Initialise the attributes map.

    // Get the attributes iterator from the STAX start element object.
    Iterator<Attribute> attributeIterator = element.getAttributes();
    Attribute attribute = null; // Place holder for each attribute.

    bailsTag = false; // Prepare the element to be checked for a bails id.

    while (attributeIterator.hasNext()) { // Iterate over the attributes...
      attribute = attributeIterator.next(); // ...recording each one, ...

      QName qName = attribute.getName(); // ...taking the qName then...

      // ...recording the name as a string.
      // A check is done on the attribute prefix. If it exists it is added to the attributes name
      // separated by a
      // colon (:).
      String nameString =
          qName.getPrefix().equals("")
              ? qName.getLocalPart()
              : qName.getPrefix() + ":" + qName.getLocalPart();

      if (!bailsTag) { // If this isn't yet a bails tag keep checking to see if it is.
        bailsTag = TagElement.BAILS_ID_NAME.equals(nameString);
      }

      attributes.put(nameString, attribute.getValue());
    }

    return attributes;
  }

  /*
     Override methods.
  */

  /** @see IBailsStream#hashCode() */
  @Override
  public boolean hasNext() {
    // If the stream is not pointing to the end of the document there must be more to process.
    return parser.hasNext()
        && (currentEvent == null || currentEvent.getEventType() != END_DOCUMENT);
  }

  /** @see IBailsStream#next() */
  @Override
  public void next() {
    try {
      currentEventString.setLength(0); // Clear the xml events char sequence.
      currentEvent = this.parser.nextEvent(); // get the next event.

      ELEMENT_TYPE previousType = type; // Record the previous type to check for openClose tags.

      // Search for the next start/end document/element or a character event.
      while (currentEvent.getEventType() != START_DOCUMENT
          && currentEvent.getEventType() != START_ELEMENT
          && currentEvent.getEventType() != CHARACTERS
          && currentEvent.getEventType() != END_ELEMENT
          && currentEvent.getEventType() != END_DOCUMENT) {
        currentEvent = this.parser.nextEvent();
      }

      switch (currentEvent.getEventType()) {
        case START_DOCUMENT:
          type = ELEMENT_TYPE.DOCUMENT_START;
          break;
        case START_ELEMENT:
          if (this.parser.peek().getEventType() == END_ELEMENT) type = ELEMENT_TYPE.OPENCLOSE;
          else type = ELEMENT_TYPE.OPEN;
          break;
        case END_ELEMENT:
          type = ELEMENT_TYPE.CLOSE;
          break;
        case CHARACTERS:
          type = ELEMENT_TYPE.CHARACTERS;
          break;
        case END_DOCUMENT:
          type = ELEMENT_TYPE.DOCUMENT_END;
          break;
      }

      // If we are at the start or end of the document don't do any more processing.
      if (type != ELEMENT_TYPE.DOCUMENT_START && type != ELEMENT_TYPE.DOCUMENT_END) {

        // Add the white space that should be at the start of the char sequence.
        currentEventString.append(preWhiteSpace);
        // Add char sequence of the current event.
        currentEventString.append(currentEvent);

        // Get the white space that should be at the end of this char sequence as well as the white
        // space that
        // should be before the next.
        checkForNewLine();

        // Add the white space that should be at the end of the char sequence.
        currentEventString.append(postWhiteSpace);

        // If this is an opening tag any attributes will need be recorded.
        if (currentEvent.getEventType() == START_ELEMENT) {

          attributes =
              parsAttributes((StartElement) currentEvent); // Pars the start events attributes.

          if (bailsTag)
            type = ELEMENT_TYPE.BAILS; // If this is a bails tag set it to the correct type.

        } else { // Otherwise there should be no attributes.
          attributes = null;
        }
      }
    } catch (XMLStreamException e) {
      System.out.println("SORT THIS OUT!: " + e.getMessage());
    }
  }

  /** @see IBailsStream#close() */
  @Override
  public void close() {
    try {
      parser.close(); // Close the STAX stream.
    } catch (XMLStreamException e) {
      System.out.println("SORT THIS OUT!: " + e.getMessage());
    }
  }

  @Override
  public ELEMENT_TYPE getType() {
    return type;
  }

  /** @see IBailsStream#getCharSequence() */
  @Override
  public CharSequence getCharSequence() {
    // This should contain the tags char sequence along with any surrounding whitespace.
    return currentEventString.toString();
  }

  /** @see IBailsStream#getName() */
  @Override
  public String getName() {
    String name = null;

    // Need to check what type of element this is because there are two that could contain a name.
    switch (currentEvent.getEventType()) {
      case START_ELEMENT:
        name = ((StartElement) currentEvent).getName().toString();
        break;
      case END_ELEMENT:
        name = ((EndElement) currentEvent).getName().toString();
        break;
    }

    return name;
  }

  /** @see IBailsStream#getAttributes() */
  @Override
  public Map<String, Object> getAttributes() {
    return attributes;
  }

  @Override
  public String getBailsId() {
    return (String) attributes.get(TagElement.BAILS_ID_NAME);
  }
}