AbstractParser.java

/*
 * IronJacamar, a Java EE Connector Architecture implementation
 * Copyright 2014, Red Hat Inc, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the Eclipse Public License 1.0 as
 * published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse
 * Public License for more details.
 *
 * You should have received a copy of the Eclipse Public License 
 * along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.ironjacamar.common.metadata.common;

import org.ironjacamar.common.CommonBundle;
import org.ironjacamar.common.CommonLogger;
import org.ironjacamar.common.api.metadata.Defaults;
import org.ironjacamar.common.api.metadata.common.Capacity;
import org.ironjacamar.common.api.metadata.common.Credential;
import org.ironjacamar.common.api.metadata.common.Extension;
import org.ironjacamar.common.api.metadata.common.FlushStrategy;
import org.ironjacamar.common.api.metadata.common.Pool;
import org.ironjacamar.common.api.metadata.common.Recovery;
import org.ironjacamar.common.api.metadata.common.Security;
import org.ironjacamar.common.api.metadata.common.Timeout;
import org.ironjacamar.common.api.metadata.common.Validation;
import org.ironjacamar.common.api.metadata.common.XaPool;
import org.ironjacamar.common.api.validator.ValidateException;
import org.ironjacamar.common.metadata.ParserException;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

import org.jboss.logging.Logger;
import org.jboss.logging.Messages;

import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;

/**
 *
 * A AbstractParser.
 *
 * @author <a href="stefano.maestri@ironjacamar.org">Stefano Maestri</a>
 *
 */
public abstract class AbstractParser
{
   /** The logger */
   protected static CommonLogger log = Logger.getMessageLogger(CommonLogger.class, AbstractParser.class.getName());

   /** The bundle */
   protected static CommonBundle bundle = Messages.getBundle(CommonBundle.class);

   /** Resolve system property */
   private boolean resolveSystemProperties = true;

   /**
    * {@inheritDoc}
    */
   public boolean isSystemPropertiesResolved()
   {
      return resolveSystemProperties;
   }

   /**
    * {@inheritDoc}
    */
   public void setSystemPropertiesResolved(boolean v)
   {
      resolveSystemProperties = v;
   }

   /**
    * convert an xml element in boolean value. Empty elements results with true (tag presence is sufficient condition)
    *
    * @param reader the StAX reader
    * @param key The key
    * @param expressions The expressions
    * @return the boolean representing element
    * @throws XMLStreamException StAX exception
    * @throws ParserException in case of non valid boolean for given element value
    */
   protected Boolean elementAsBoolean(XMLStreamReader reader, String key, Map<String, String> expressions)
      throws XMLStreamException, ParserException
   {
      String elementtext = rawElementText(reader);

      if (key != null && expressions != null && elementtext != null && elementtext.indexOf("${") != -1)
         expressions.put(key, elementtext);
      
      String stringValue = getSubstitutionValue(elementtext);
      if (StringUtils.isEmpty(stringValue) || stringValue.trim().equalsIgnoreCase("true") ||
          stringValue.trim().equalsIgnoreCase("false"))
      {
         return StringUtils.isEmpty(stringValue) ? Boolean.TRUE : Boolean.valueOf(stringValue.trim());
      }
      else
      {
         throw new ParserException(bundle.elementAsBoolean(elementtext, reader.getLocalName()));
      }
   }

   /**
    * convert an xml attribute in boolean value. Empty elements results in default value
    *
    * @param reader the StAX reader
    * @param attributeName the name of the attribute
    * @param defaultValue  defaultValue
    * @param expressions The expressions
    * @return the boolean representing element
    * @throws XMLStreamException StAX exception
    * @throws ParserException in case of not valid boolean for given attribute
    */
   protected Boolean attributeAsBoolean(XMLStreamReader reader, String attributeName, Boolean defaultValue,
                                        Map<String, String> expressions)
      throws XMLStreamException, ParserException
   {
      String attributeString = rawAttributeText(reader, attributeName);

      if (attributeName != null && expressions != null && attributeString != null &&
          attributeString.indexOf("${") != -1)
         expressions.put(attributeName, attributeString);
      
      String stringValue = getSubstitutionValue(attributeString);
      if (StringUtils.isEmpty(stringValue) || stringValue.trim().equalsIgnoreCase("true") ||
          stringValue.trim().equalsIgnoreCase("false"))
      {
         return StringUtils.isEmpty(stringValue) ? defaultValue : Boolean.valueOf(stringValue.trim());
      }
      else
      {
         throw new ParserException(bundle.attributeAsBoolean(attributeString, reader.getLocalName()));
      }
   }

   /**
    * convert an xml element in String value
    *
    * @param reader the StAX reader
    * @param key The key
    * @param expressions The expressions
    * @return the string representing element
    * @throws XMLStreamException StAX exception
    */
   protected String elementAsString(XMLStreamReader reader, String key, Map<String, String> expressions)
      throws XMLStreamException
   {
      String elementtext = rawElementText(reader);

      if (key != null && expressions != null && elementtext != null && elementtext.indexOf("${") != -1)
         expressions.put(key, elementtext);
      
      return getSubstitutionValue(elementtext);
   }

   /**
    * Read the raw element
    *
    * @param reader
    * @return the string representing the raw eleemnt text
    * @throws XMLStreamException
    */
   private String rawElementText(XMLStreamReader reader) throws XMLStreamException
   {
      String elementtext = reader.getElementText();

      if (elementtext == null)
         return null;

      return elementtext.trim();
   }

   /**
    * convert an xml element in String value
    *
    * @param reader the StAX reader
    * @param attributeName the name of the attribute
    * @param expressions The expressions
    * @return the string representing element
    * @throws XMLStreamException StAX exception
    */
   protected String attributeAsString(XMLStreamReader reader, String attributeName, Map<String, String> expressions)
      throws XMLStreamException
   {
      String attributeString = rawAttributeText(reader, attributeName);

      if (attributeName != null && expressions != null && attributeString != null &&
          attributeString.indexOf("${") != -1)
         expressions.put(attributeName, attributeString);

      return getSubstitutionValue(attributeString);
   }

   /**
    * convert an xml element in String value
    *
    * @param reader the StAX reader
    * @param attributeName the name of the attribute
    * @param expressions The expressions
    * @return the string representing element
    * @throws XMLStreamException StAX exception
    */
   protected Integer attributeAsInt(XMLStreamReader reader, String attributeName, Map<String, String> expressions)
      throws XMLStreamException
   {
      String attributeString = rawAttributeText(reader, attributeName);

      if (attributeName != null && expressions != null && attributeString != null &&
          attributeString.indexOf("${") != -1)
         expressions.put(attributeName, attributeString);

      return attributeString != null ? Integer.valueOf(getSubstitutionValue(attributeString)) : null;
   }

   /**
    * Read the raw attribute
    *
    * @param reader
    * @param attributeName
    * @return the string representing raw attribute textx
    */
   private String rawAttributeText(XMLStreamReader reader, String attributeName)
   {
      String attributeString = reader.getAttributeValue("", attributeName);

      if (attributeString == null)
         return null;

      return attributeString.trim();
   }

   /**
    * convert an xml element in Integer value
    *
    * @param reader the StAX reader
    * @param key The key
    * @param expressions The expressions
    * @return the integer representing element
    * @throws XMLStreamException StAX exception
    * @throws ParserException in case it isn't a number
    */
   protected Integer elementAsInteger(XMLStreamReader reader, String key, Map<String, String> expressions)
      throws XMLStreamException, ParserException
   {
      Integer integerValue = null;
      String elementtext = rawElementText(reader);

      if (key != null && expressions != null && elementtext != null && elementtext.indexOf("${") != -1)
         expressions.put(key, elementtext);
      
      try
      {
         integerValue = Integer.valueOf(getSubstitutionValue(elementtext));
      }
      catch (NumberFormatException nfe)
      {
         throw new ParserException(bundle.notValidNumber(elementtext, reader.getLocalName()));
      }
      return integerValue;
   }

   /**
    * convert an xml element in Long value
    *
    * @param reader the StAX reader
    * @param key The key
    * @param expressions The expressions
    * @return the long representing element
    * @throws XMLStreamException StAX exception
    * @throws ParserException in case it isn't a number
    */
   protected Long elementAsLong(XMLStreamReader reader, String key, Map<String, String> expressions)
      throws XMLStreamException, ParserException
   {
      Long longValue = null;
      String elementtext = rawElementText(reader);

      if (key != null && expressions != null && elementtext != null && elementtext.indexOf("${") != -1)
         expressions.put(key, elementtext);
      
      try
      {
         longValue = Long.valueOf(getSubstitutionValue(elementtext));
      }
      catch (NumberFormatException nfe)
      {
         throw new ParserException(bundle.notValidNumber(elementtext, reader.getLocalName()));
      }

      return longValue;
   }

   /**
    * convert an xml element in FlushStrategy value
    *
    * @param reader the StAX reader
    * @param expressions expressions
    * @return the flush strategy represention
    * @throws XMLStreamException StAX exception
    * @throws ParserException in case it isn't a number
    */
   protected FlushStrategy elementAsFlushStrategy(XMLStreamReader reader, Map<String, String> expressions)
      throws XMLStreamException, ParserException
   {
      String elementtext = rawElementText(reader);

      if (expressions != null && elementtext != null && elementtext.indexOf("${") != -1)
         expressions.put(CommonXML.ELEMENT_FLUSH_STRATEGY, elementtext);
      
      FlushStrategy result = FlushStrategy.forName(getSubstitutionValue(elementtext));

      if (result != FlushStrategy.UNKNOWN)
         return result;

      throw new ParserException(bundle.notValidFlushStrategy(elementtext));
   }

   /**
    *
    * parse a {@link Pool} object
    *
    * @param reader reader
    * @return the parsed {@link Pool} object
    * @throws XMLStreamException XMLStreamException
    * @throws ParserException ParserException
    * @throws ValidateException ValidateException
    */
   protected Pool parsePool(XMLStreamReader reader) throws XMLStreamException, ParserException,
      ValidateException
   {
      String type = Defaults.TYPE;
      String janitor = Defaults.JANITOR;
      Integer minPoolSize = Defaults.MIN_POOL_SIZE;;
      Integer initialPoolSize = Defaults.INITIAL_POOL_SIZE;;
      Integer maxPoolSize = Defaults.MAX_POOL_SIZE;
      Boolean prefill = Defaults.PREFILL;
      FlushStrategy flushStrategy = Defaults.FLUSH_STRATEGY;
      Capacity capacity = null;

      Map<String, String> expressions = new HashMap<String, String>();

      for (int i = 0; i < reader.getAttributeCount(); i++)
      {
         switch (reader.getAttributeLocalName(i))
         {
            case CommonXML.ATTRIBUTE_TYPE : {
               type = attributeAsString(reader, CommonXML.ATTRIBUTE_TYPE, expressions);
               break;
            }
            case CommonXML.ATTRIBUTE_JANITOR : {
               janitor = attributeAsString(reader, CommonXML.ATTRIBUTE_JANITOR, expressions);
               break;
            }
            default :
               break;
         }
      }

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_POOL :
                     return new PoolImpl(type, janitor, minPoolSize, initialPoolSize, maxPoolSize, prefill,
                           flushStrategy, capacity,
                             !expressions.isEmpty() ? expressions : null);
                  case CommonXML.ELEMENT_MAX_POOL_SIZE :
                  case CommonXML.ELEMENT_MIN_POOL_SIZE :
                  case CommonXML.ELEMENT_INITIAL_POOL_SIZE :
                  case CommonXML.ELEMENT_PREFILL :
                  case CommonXML.ELEMENT_FLUSH_STRATEGY :
                  case CommonXML.ELEMENT_CAPACITY :
                     break;
                  default :
                     throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
               }
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_MAX_POOL_SIZE : {
                     maxPoolSize = elementAsInteger(reader, CommonXML.ELEMENT_MAX_POOL_SIZE, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_MIN_POOL_SIZE : {
                     minPoolSize = elementAsInteger(reader, CommonXML.ELEMENT_MIN_POOL_SIZE, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_INITIAL_POOL_SIZE : {
                     initialPoolSize = elementAsInteger(reader, CommonXML.ELEMENT_INITIAL_POOL_SIZE, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_PREFILL : {
                     prefill = elementAsBoolean(reader, CommonXML.ELEMENT_PREFILL, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_FLUSH_STRATEGY : {
                     flushStrategy = elementAsFlushStrategy(reader, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_CAPACITY : {
                     capacity = parseCapacity(reader);
                     break;
                  }
                  default :
                     throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
               }
               break;
            }
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }

   /**
   *
   * parse a {@link XaPool} object
   *
   * @param reader reader
   * @return the parsed {@link XaPool} object
   * @throws XMLStreamException XMLStreamException
   * @throws ParserException ParserException
    * @throws ValidateException ValidateException
   */
   protected XaPool parseXaPool(XMLStreamReader reader) throws XMLStreamException, ParserException,
      ValidateException
   {
      String type = Defaults.TYPE;
      String janitor = Defaults.JANITOR;
      Integer minPoolSize = Defaults.MIN_POOL_SIZE;
      Integer initialPoolSize = Defaults.INITIAL_POOL_SIZE;
      Integer maxPoolSize = Defaults.MAX_POOL_SIZE;
      Boolean prefill = Defaults.PREFILL;
      FlushStrategy flushStrategy = Defaults.FLUSH_STRATEGY;
      Capacity capacity = null;
      Boolean isSameRmOverride = Defaults.IS_SAME_RM_OVERRIDE;
      Boolean padXid = Defaults.PAD_XID;
      Boolean wrapXaDataSource = Defaults.WRAP_XA_RESOURCE;

      Map<String, String> expressions = new HashMap<String, String>();

      for (int i = 0; i < reader.getAttributeCount(); i++)
      {
         switch (reader.getAttributeLocalName(i))
         {
            case CommonXML.ATTRIBUTE_TYPE : {
               type = attributeAsString(reader, CommonXML.ATTRIBUTE_TYPE, expressions);
               break;
            }
            case CommonXML.ATTRIBUTE_JANITOR : {
               janitor = attributeAsString(reader, CommonXML.ATTRIBUTE_JANITOR, expressions);
               break;
            }
            default :
               break;
         }
      }

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_XA_POOL :
                     return new XaPoolImpl(type, janitor, minPoolSize, initialPoolSize, maxPoolSize, prefill,
                           flushStrategy, capacity,
                           isSameRmOverride, padXid,
                           wrapXaDataSource,
                             !expressions.isEmpty() ? expressions : null);
                  case CommonXML.ELEMENT_MAX_POOL_SIZE :
                  case CommonXML.ELEMENT_INITIAL_POOL_SIZE :
                  case CommonXML.ELEMENT_MIN_POOL_SIZE :
                  case CommonXML.ELEMENT_IS_SAME_RM_OVERRIDE :
                  case CommonXML.ELEMENT_PAD_XID :
                  case CommonXML.ELEMENT_WRAP_XA_RESOURCE :
                  case CommonXML.ELEMENT_PREFILL :
                  case CommonXML.ELEMENT_FLUSH_STRATEGY :
                  case CommonXML.ELEMENT_CAPACITY :
                     break;
                  default :
                     throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
               }
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_MAX_POOL_SIZE : {
                     maxPoolSize = elementAsInteger(reader, CommonXML.ELEMENT_MAX_POOL_SIZE, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_INITIAL_POOL_SIZE : {
                     initialPoolSize = elementAsInteger(reader, CommonXML.ELEMENT_INITIAL_POOL_SIZE, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_MIN_POOL_SIZE : {
                     minPoolSize = elementAsInteger(reader, CommonXML.ELEMENT_MIN_POOL_SIZE, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_IS_SAME_RM_OVERRIDE : {
                     isSameRmOverride = elementAsBoolean(reader, CommonXML.ELEMENT_IS_SAME_RM_OVERRIDE, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_PAD_XID : {
                     padXid = elementAsBoolean(reader, CommonXML.ELEMENT_PAD_XID, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_WRAP_XA_RESOURCE : {
                     wrapXaDataSource = elementAsBoolean(reader, CommonXML.ELEMENT_WRAP_XA_RESOURCE, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_PREFILL : {
                     prefill = elementAsBoolean(reader, CommonXML.ELEMENT_PREFILL, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_FLUSH_STRATEGY : {
                     flushStrategy = elementAsFlushStrategy(reader, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_CAPACITY : {
                     capacity = parseCapacity(reader);
                     break;
                  }
                  default :
                     throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
               }
               break;
            }
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }

   /**
    *
    * parse a {@link Security} element
    *
    * @param reader reader
    * @return a {@link Security} object
    * @throws XMLStreamException XMLStreamException
    * @throws ParserException ParserException
    * @throws ValidateException ValidateException
    */
   protected Security parseSecuritySettings(XMLStreamReader reader) throws XMLStreamException, ParserException,
      ValidateException
   {

      String securityDomain = null;

      Map<String, String> expressions = new HashMap<String, String>();

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_SECURITY :
                     return new SecurityImpl(securityDomain,
                             !expressions.isEmpty() ? expressions : null);
                  case CommonXML.ELEMENT_SECURITY_DOMAIN :
                     break;
                  default :
                     throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
               }
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {

                  case CommonXML.ELEMENT_SECURITY_DOMAIN : {
                     securityDomain = elementAsString(reader, CommonXML.ELEMENT_SECURITY_DOMAIN, expressions);
                     break;
                  }
                  default :
                     throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
               }
               break;
            }
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }

   /**
    * System property substitution
    * @param input The input string
    * @return The output
    */
   private String getSubstitutionValue(String input)
   {
      if (!resolveSystemProperties)
         return input;
      return StringUtils.transformExpression(input);
   }

   /**
    *
    * parse credential tag
    *
    * @param reader reader
    * @return the parse Object
    * @throws XMLStreamException in case of error
    * @throws ParserException in case of error
    * @throws ValidateException in case of error
    */
   protected Credential parseCredential(XMLStreamReader reader) throws XMLStreamException, ParserException,
      ValidateException
   {
      String securityDomain = null;

      Map<String, String> expressions = new HashMap<String, String>();

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_SECURITY :
                  case CommonXML.ELEMENT_RECOVERY_CREDENTIAL :
                     return new CredentialImpl(securityDomain,
                             !expressions.isEmpty() ? expressions : null);
                  case CommonXML.ELEMENT_SECURITY_DOMAIN :
                     break;
                  default :
                     throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
               }
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_SECURITY_DOMAIN : {
                     securityDomain = elementAsString(reader, CommonXML.ELEMENT_SECURITY_DOMAIN, expressions);
                     break;
                  }
                  default :
                     throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
               }
               break;
            }
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }


   /**
    *
    * Parse recovery tag
    *
    * @param reader reader
    * @return the parsed recovery object
    * @throws XMLStreamException in case of error
    * @throws ParserException in case of error
    * @throws ValidateException in case of error
    */
   protected Recovery parseRecovery(XMLStreamReader reader) throws XMLStreamException, ParserException,
      ValidateException
   {
      Boolean noRecovery = Defaults.NO_RECOVERY;
      Credential security = null;
      Extension plugin = null;

      Map<String, String> expressions = new HashMap<String, String>();

      for (int i = 0; i < reader.getAttributeCount(); i++)
      {
         switch (reader.getAttributeLocalName(i))
         {
            case CommonXML.ATTRIBUTE_NO_RECOVERY : {
               noRecovery = attributeAsBoolean(reader, CommonXML.ATTRIBUTE_NO_RECOVERY, Boolean.FALSE, expressions);
               break;
            }
            default :
               break;
         }
      }

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_RECOVERY :
                     return new RecoveryImpl(security, plugin, noRecovery,
                             !expressions.isEmpty() ? expressions : null);
                  case CommonXML.ELEMENT_RECOVERY_CREDENTIAL :
                  case CommonXML.ELEMENT_RECOVERY_PLUGIN :
                     break;
                  default :
                     throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
               }
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_RECOVERY_CREDENTIAL : {
                     security = parseCredential(reader);
                     break;
                  }
                  case CommonXML.ELEMENT_RECOVERY_PLUGIN : {
                     plugin = parseExtension(reader, CommonXML.ELEMENT_RECOVERY_PLUGIN);
                     break;
                  }
                  default :
                     throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
               }
               break;
            }
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }

   /**
    *
    * parse the Extension tag
    *
    * @param reader reader
    * @param enclosingTag enclosingTag
    * @return the parsed extension object
    * @throws XMLStreamException in case of error
    * @throws ParserException in case of error
    * @throws ValidateException in case of error
    */
   protected Extension parseExtension(XMLStreamReader reader, String enclosingTag) throws XMLStreamException,
      ParserException,
      ValidateException
   {

      String className = null;
      String moduleName = null;
      String moduleSlot = null;
      Map<String, String> properties = null;
      Map<String, String> expressions = new HashMap<String, String>();

      for (int i = 0; i < reader.getAttributeCount(); i++)
      {
         switch (reader.getAttributeLocalName(i))
         {
            case CommonXML.ATTRIBUTE_CLASS_NAME : {
               className = attributeAsString(reader, CommonXML.ATTRIBUTE_CLASS_NAME, expressions);
               break;
            }
            case CommonXML.ATTRIBUTE_MODULE_NAME : {
               moduleName = attributeAsString(reader, CommonXML.ATTRIBUTE_MODULE_NAME, expressions);
               break;
            }
            case CommonXML.ATTRIBUTE_MODULE_SLOT: {
               moduleSlot = attributeAsString(reader, CommonXML.ATTRIBUTE_MODULE_SLOT, expressions);
               break;
            }
            default :
               break;
         }
      }

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               if (reader.getLocalName().equals(enclosingTag))
               {
                  if (className == null)
                  {
                     throw new ParserException(bundle.missingClassName(enclosingTag));
                  }

                  return new ExtensionImpl(className, moduleName, moduleSlot, properties,
                          !expressions.isEmpty() ? expressions : null);
               }
               else
               {
                  switch (reader.getLocalName())
                  {
                     case CommonXML.ELEMENT_CONFIG_PROPERTY :
                        break;
                     default :
                        throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
                  }
               }
               break;
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_CONFIG_PROPERTY : {
                     if (properties == null)
                        properties = new HashMap<String, String>();
                     parseConfigProperty(properties, reader, CommonXML.ELEMENT_CONFIG_PROPERTY, expressions);
                     break;
                  }
                  default :
                     throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
               }
               break;
            }
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }

   /**
    * Parse capacity tag
    *
    * @param reader reader
    * @return the parsed capacity object
    * @throws XMLStreamException in case of error
    * @throws ParserException in case of error
    * @throws ValidateException in case of error
    */
   protected Capacity parseCapacity(XMLStreamReader reader) throws XMLStreamException, ParserException,
      ValidateException
   {
      Extension incrementer = null;
      Extension decrementer = null;

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_CAPACITY :
                     return new CapacityImpl(incrementer, decrementer);
                  default :
                     break;
               }
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_INCREMENTER : {
                     incrementer = parseExtension(reader, CommonXML.ELEMENT_INCREMENTER);
                     break;
                  }
                  case CommonXML.ELEMENT_DECREMENTER : {
                     decrementer = parseExtension(reader, CommonXML.ELEMENT_DECREMENTER);
                     break;
                  }
                  default :
                     // Nothing
               }
               break;
            }
            default :
               throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }


   /**
    * Parse validation
    * @param reader The reader
    * @return The result
    * @throws XMLStreamException XMLStreamException
    * @throws ParserException ParserException
    * @throws ValidateException ValidateException
    */
   protected Validation parseValidation(XMLStreamReader reader) throws XMLStreamException, 
      ParserException, 
      ValidateException
   {
      Boolean validateOnMatch = Defaults.VALIDATE_ON_MATCH;
      Boolean useFastFail = Defaults.USE_FAST_FAIL;
      Boolean backgroundValidation = Defaults.BACKGROUND_VALIDATION;
      Long backgroundValidationMillis = null;

      Map<String, String> expressions = new HashMap<String, String>();

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_VALIDATION :
                     return new ValidationImpl(validateOnMatch, backgroundValidation, backgroundValidationMillis,
                        useFastFail,
                             !expressions.isEmpty() ? expressions : null);
                  case CommonXML.ELEMENT_VALIDATE_ON_MATCH :
                  case CommonXML.ELEMENT_BACKGROUND_VALIDATION_MILLIS :
                  case CommonXML.ELEMENT_BACKGROUND_VALIDATION :
                  case CommonXML.ELEMENT_USE_FAST_FAIL :
                     break;
                  default :
                     throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
               }
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_VALIDATE_ON_MATCH : {
                     validateOnMatch = elementAsBoolean(reader, CommonXML.ELEMENT_VALIDATE_ON_MATCH, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_BACKGROUND_VALIDATION_MILLIS : {
                     backgroundValidationMillis = elementAsLong(reader, CommonXML.ELEMENT_BACKGROUND_VALIDATION_MILLIS,
                                                                expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_BACKGROUND_VALIDATION : {
                     backgroundValidation = elementAsBoolean(reader, CommonXML.ELEMENT_BACKGROUND_VALIDATION,
                                                             expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_USE_FAST_FAIL : {
                     useFastFail = elementAsBoolean(reader, CommonXML.ELEMENT_USE_FAST_FAIL, expressions);
                     break;
                  }
                  default :
                     throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
               }
               break;
            }
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }

   /**
    * Parse timeout
    * @param reader The reader
    * @param isXa XA flag
    * @return The result
    * @exception XMLStreamException XMLStreamException
    * @exception ParserException ParserException
    * @exception ValidateException ValidateException
    */
   protected Timeout parseTimeout(XMLStreamReader reader, Boolean isXa) throws XMLStreamException,
      ParserException, ValidateException
   {
      Long blockingTimeoutMillis = null;
      Long allocationRetryWaitMillis = null;
      Integer idleTimeoutMinutes = null;
      Integer allocationRetry = null;
      Integer xaResourceTimeout = null;

      Map<String, String> expressions = new HashMap<String, String>();

      while (reader.hasNext())
      {
         switch (reader.nextTag())
         {
            case END_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_TIMEOUT :
                     return new TimeoutImpl(blockingTimeoutMillis, idleTimeoutMinutes, allocationRetry,
                           allocationRetryWaitMillis, xaResourceTimeout,
                             !expressions.isEmpty() ? expressions : null);
                  case CommonXML.ELEMENT_ALLOCATION_RETRY_WAIT_MILLIS :
                  case CommonXML.ELEMENT_ALLOCATION_RETRY :
                  case CommonXML.ELEMENT_BLOCKING_TIMEOUT_MILLIS :
                  case CommonXML.ELEMENT_IDLE_TIMEOUT_MINUTES :
                  case CommonXML.ELEMENT_XA_RESOURCE_TIMEOUT :
                     break;
                  default :
                     throw new ParserException(bundle.unexpectedEndTag(reader.getLocalName()));
               }
            }
            case START_ELEMENT : {
               switch (reader.getLocalName())
               {
                  case CommonXML.ELEMENT_ALLOCATION_RETRY_WAIT_MILLIS : {
                     allocationRetryWaitMillis = elementAsLong(reader, CommonXML.ELEMENT_ALLOCATION_RETRY_WAIT_MILLIS,
                                                               expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_ALLOCATION_RETRY : {
                     allocationRetry = elementAsInteger(reader, CommonXML.ELEMENT_ALLOCATION_RETRY, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_BLOCKING_TIMEOUT_MILLIS : {
                     blockingTimeoutMillis = elementAsLong(reader, CommonXML.ELEMENT_BLOCKING_TIMEOUT_MILLIS,
                                                           expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_IDLE_TIMEOUT_MINUTES : {
                     idleTimeoutMinutes = elementAsInteger(reader, CommonXML.ELEMENT_IDLE_TIMEOUT_MINUTES, expressions);
                     break;
                  }
                  case CommonXML.ELEMENT_XA_RESOURCE_TIMEOUT : {
                     if (isXa != null && Boolean.FALSE.equals(isXa))
                        throw new ParserException(bundle.unsupportedElement(reader.getLocalName()));
                     xaResourceTimeout = elementAsInteger(reader, CommonXML.ELEMENT_XA_RESOURCE_TIMEOUT, expressions);
                     break;
                  }
                  default :
                     throw new ParserException(bundle.unexpectedElement(reader.getLocalName()));
               }
               break;
            }
         }
      }
      throw new ParserException(bundle.unexpectedEndOfDocument());
   }

   /**
    * Store capacity
    * @param c The capacity
    * @param writer The writer
    * @exception Exception Thrown if an error occurs
    */
   protected void storeCapacity(Capacity c, XMLStreamWriter writer) throws Exception
   {
      writer.writeStartElement(CommonXML.ELEMENT_CAPACITY);

      if (c.getIncrementer() != null)
      {
         storeExtension(c.getIncrementer(), writer, CommonXML.ELEMENT_INCREMENTER);
      }

      if (c.getDecrementer() != null)
      {
         storeExtension(c.getDecrementer(), writer, CommonXML.ELEMENT_DECREMENTER);
      }

      writer.writeEndElement();
   }
   /**
    * Store capacity
    * @param e The extension
    * @param writer The writer
    * @param elementName the element name
    * @exception XMLStreamException Thrown if an error occurs
    */
   protected void storeExtension(Extension e, XMLStreamWriter writer, String elementName) throws XMLStreamException
   {
      writer.writeStartElement(elementName);
      writer.writeAttribute(CommonXML.ATTRIBUTE_CLASS_NAME,
                            e.getValue(CommonXML.ATTRIBUTE_CLASS_NAME,
                                                        e.getClassName()));
      if (e.getModuleName() != null)
         writer.writeAttribute(CommonXML.ATTRIBUTE_MODULE_NAME,
               e.getValue(CommonXML.ATTRIBUTE_MODULE_NAME, e.getModuleName()));
      if (e.getModuleSlot() != null)
         writer.writeAttribute(CommonXML.ATTRIBUTE_MODULE_SLOT,
               e.getValue(CommonXML.ATTRIBUTE_MODULE_SLOT, e.getModuleSlot()));

      if (!e.getConfigPropertiesMap().isEmpty())
      {
         Iterator<Map.Entry<String, String>> it =
            e.getConfigPropertiesMap().entrySet().iterator();

         while (it.hasNext())
         {
            Map.Entry<String, String> entry = it.next();

            writer.writeStartElement(CommonXML.ELEMENT_CONFIG_PROPERTY);
            writer.writeAttribute(CommonXML.ATTRIBUTE_NAME, entry.getKey());
            writer.writeCharacters(e.getValue(CommonXML.ELEMENT_CONFIG_PROPERTY,
                                                               entry.getKey(), entry.getValue()));
            writer.writeEndElement();
         }
      }

      writer.writeEndElement();
   }

   /**
    * Store recovery
    * @param r The recovery
    * @param writer The writer
    * @exception Exception Thrown if an error occurs
    */
   protected void storeRecovery(Recovery r, XMLStreamWriter writer) throws Exception
   {
      writer.writeStartElement(CommonXML.ELEMENT_RECOVERY);

      if (r.isNoRecovery() != null)
         writer.writeAttribute(CommonXML.ATTRIBUTE_NO_RECOVERY,
                               r.getValue(CommonXML.ATTRIBUTE_NO_RECOVERY, r.isNoRecovery().toString()));

      if (r.getCredential() != null)
      {
         writer.writeStartElement(CommonXML.ELEMENT_RECOVERY_CREDENTIAL);

         writer.writeStartElement(CommonXML.ELEMENT_SECURITY_DOMAIN);
         writer.writeCharacters(r.getCredential().getValue(CommonXML.ELEMENT_SECURITY_DOMAIN,
                                                              r.getCredential().getSecurityDomain()));
         writer.writeEndElement();

         writer.writeEndElement();
      }

      if (r.getPlugin() != null)
      {
         storeExtension(r.getPlugin(), writer, CommonXML.ELEMENT_RECOVERY_PLUGIN);
      }

      writer.writeEndElement();
   }

   /**
    * 
    * Adds config property to the map
    * 
    * @param configProperties map
    * @param reader XMLStream reader
    * @param key The key
    * @param expressions expressions
    * @throws XMLStreamException in case of error
    * @throws ParserException in case of error
    */
   protected void parseConfigProperty(Map<String, String> configProperties, XMLStreamReader reader,
                                      String key, Map<String, String> expressions)
      throws XMLStreamException, ParserException
   {
      String n = attributeAsString(reader, "name", null);
      if (StringUtils.isEmptyTrimmed(n))
         throw new ParserException(bundle.requiredAttributeMissing("name", reader.getLocalName()));
      else
         configProperties.put(n, elementAsString(reader, getExpressionKey(key, n), expressions));
   }

   /**
    * Get expression key
    * @param k The key
    * @param s The subkey
    * @return The value
    */
   protected String getExpressionKey(String k, String s)
   {
      return k + "|" + s;
   }
}