public static WebMetaData parse(
      XMLStreamReader reader, DTDInfo info, boolean validation, PropertyReplacer propertyReplacer)
      throws XMLStreamException {
    if (reader == null) throw new IllegalArgumentException("Null reader");
    if (info == null) throw new IllegalArgumentException("Null info");

    reader.require(START_DOCUMENT, null, null);

    // Read until the first start element
    while (reader.hasNext() && reader.next() != START_ELEMENT) ;

    String schemaLocation = readSchemaLocation(reader);

    Version version = null;
    if (info.getPublicID() != null) {
      version = Version.fromPublicID(info.getPublicID());
    }
    if (version == null && info.getSystemID() != null) {
      version = Version.fromSystemID(info.getSystemID());
    }
    if (version == null && schemaLocation != null) {
      version = Version.fromSystemID(schemaLocation);
    }
    if (version == null) {
      // Look at the version attribute
      String versionString = null;
      final int count = reader.getAttributeCount();
      for (int i = 0; i < count; i++) {
        if (attributeHasNamespace(reader, i)) {
          continue;
        }
        final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
        if (attribute == Attribute.VERSION) {
          versionString = reader.getAttributeValue(i);
        }
      }
      if ("2.4".equals(versionString)) {
        version = Version.SERVLET_2_4;
      } else if ("2.5".equals(versionString)) {
        version = Version.SERVLET_2_5;
      } else if ("3.0".equals(versionString)) {
        version = Version.SERVLET_3_0;
      }
    }

    if (version == null) version = Version.SERVLET_3_0;
    // throw new IllegalStateException("Cannot obtain servlet version");

    WebMetaData wmd = null;
    switch (version) {
      case SERVLET_2_2:
        wmd = new Web22MetaData();
        break;
      case SERVLET_2_3:
        wmd = new Web23MetaData();
        break;
      case SERVLET_2_4:
        wmd = new Web24MetaData();
        break;
      case SERVLET_2_5:
        wmd = new Web25MetaData();
        break;
      case SERVLET_3_0:
        wmd = new Web30MetaData();
        break;
    }

    // Set the publicId / systemId
    if (info != null) wmd.setDTD(info.getBaseURI(), info.getPublicID(), info.getSystemID());

    // Set the schema location if we have one
    if (schemaLocation != null) wmd.setSchemaLocation(schemaLocation);

    // Handle attributes
    final int count = reader.getAttributeCount();
    for (int i = 0; i < count; i++) {
      final String value = reader.getAttributeValue(i);
      if (attributeHasNamespace(reader, i)) {
        continue;
      }
      final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
      switch (attribute) {
        case ID:
          {
            wmd.setId(value);
            break;
          }
        case VERSION:
          {
            wmd.setVersion(value);
            break;
          }
        case METADATA_COMPLETE:
          {
            if (wmd instanceof Web25MetaData || wmd instanceof Web30MetaData) {
              if (Boolean.TRUE.equals(Boolean.valueOf(value))) {
                if (wmd instanceof Web25MetaData) {
                  ((Web25MetaData) wmd).setMetadataComplete(true);
                }
                if (wmd instanceof Web30MetaData) {
                  ((Web30MetaData) wmd).setMetadataComplete(true);
                }
              }
            } else {
              throw unexpectedAttribute(reader, i);
            }
            break;
          }
        default:
          throw unexpectedAttribute(reader, i);
      }
    }

    DescriptionGroupMetaData descriptionGroup = new DescriptionGroupMetaData();
    EnvironmentRefsGroupMetaData env = new EnvironmentRefsGroupMetaData();
    // Handle elements
    while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
      if (WebCommonMetaDataParser.parse(reader, wmd, propertyReplacer)) {
        continue;
      }
      if (EnvironmentRefsGroupMetaDataParser.parse(reader, env, propertyReplacer)) {
        if (wmd.getJndiEnvironmentRefsGroup() == null) {
          wmd.setJndiEnvironmentRefsGroup(env);
        }
        continue;
      }
      if (DescriptionGroupMetaDataParser.parse(reader, descriptionGroup)) {
        if (wmd.getDescriptionGroup() == null) {
          wmd.setDescriptionGroup(descriptionGroup);
        }
        continue;
      }
      final Element element = Element.forName(reader.getLocalName());
      switch (element) {
        case ABSOLUTE_ORDERING:
          if (wmd instanceof Web30MetaData) {
            ((Web30MetaData) wmd)
                .setAbsoluteOrdering(
                    AbsoluteOrderingMetaDataParser.parse(reader, propertyReplacer));
          } else {
            throw unexpectedElement(reader);
          }
          break;
        case MODULE_NAME:
          if (wmd instanceof Web30MetaData) {
            ((Web30MetaData) wmd).setModuleName(getElementText(reader, propertyReplacer));
          } else {
            throw unexpectedElement(reader);
          }
          break;
        case TAGLIB:
          if (wmd instanceof Web22MetaData || wmd instanceof Web23MetaData) {
            JspConfigMetaData jspConfig = wmd.getJspConfig();
            if (jspConfig == null) {
              jspConfig = new JspConfigMetaData();
              wmd.setJspConfig(jspConfig);
            }
            List<TaglibMetaData> taglibs = jspConfig.getTaglibs();
            if (taglibs == null) {
              taglibs = new ArrayList<TaglibMetaData>();
              jspConfig.setTaglibs(taglibs);
            }
            taglibs.add(TaglibMetaDataParser.parse(reader, propertyReplacer));
          } else {
            throw unexpectedElement(reader);
          }
          break;
        default:
          throw unexpectedElement(reader);
      }
    }

    return wmd;
  }
  @Override
  public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
    final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
    if (!DeploymentTypeMarker.isType(DeploymentType.WAR, deploymentUnit)) {
      return; // Skip non web deployments
    }
    final ResourceRoot deploymentRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
    final VirtualFile alternateDescriptor =
        deploymentRoot.getAttachment(
            org.jboss.as.ee.structure.Attachments.ALTERNATE_WEB_DEPLOYMENT_DESCRIPTOR);
    // Locate the descriptor
    final VirtualFile webXml;
    if (alternateDescriptor != null) {
      webXml = alternateDescriptor;
    } else {
      webXml = deploymentRoot.getRoot().getChild(WEB_XML);
    }
    final WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
    assert warMetaData != null;
    if (webXml.exists()) {
      InputStream is = null;
      try {
        is = webXml.openStream();
        final XMLInputFactory inputFactory = XMLInputFactory.newInstance();

        MetaDataElementParser.DTDInfo dtdInfo = new MetaDataElementParser.DTDInfo();
        inputFactory.setXMLResolver(dtdInfo);
        final XMLStreamReader xmlReader = inputFactory.createXMLStreamReader(is);

        WebMetaData webMetaData =
            WebMetaDataParser.parse(
                xmlReader,
                dtdInfo,
                SpecDescriptorPropertyReplacement.propertyReplacer(deploymentUnit));

        if (schemaValidation && webMetaData.getSchemaLocation() != null) {
          XMLSchemaValidator validator = new XMLSchemaValidator(new XMLResourceResolver());
          InputStream xmlInput = webXml.openStream();
          try {
            if (webMetaData.is23())
              validator.validate(
                  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN", xmlInput);
            else if (webMetaData.is24())
              validator.validate("http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd", xmlInput);
            else if (webMetaData.is25())
              validator.validate("http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd", xmlInput);
            else if (webMetaData.is30())
              validator.validate("http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd", xmlInput);
            else if (webMetaData.is31())
              validator.validate("http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd", xmlInput);
            else
              validator.validate(
                  "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN", xmlInput);
          } catch (SAXException e) {
            throw new DeploymentUnitProcessingException("Failed to validate " + webXml, e);
          } finally {
            xmlInput.close();
          }
        }
        warMetaData.setWebMetaData(webMetaData);

      } catch (XMLStreamException e) {
        throw new DeploymentUnitProcessingException(
            MESSAGES.failToParseXMLDescriptor(
                webXml, e.getLocation().getLineNumber(), e.getLocation().getColumnNumber()));
      } catch (IOException e) {
        throw new DeploymentUnitProcessingException(MESSAGES.failToParseXMLDescriptor(webXml), e);
      } finally {
        try {
          if (is != null) {
            is.close();
          }
        } catch (IOException e) {
          // Ignore
        }
      }
    }
  }