/**
  * Build a normal attribute.
  *
  * @param ownerType The descriptor of the attribute owner (aka declarer).
  * @param property The Hibernate property descriptor for the attribute
  * @param <X> The type of the owner
  * @param <Y> The attribute type
  * @return The built attribute descriptor or null if the attribute is not part of the JPA 2 model
  *     (eg backrefs)
  */
 @SuppressWarnings({"unchecked"})
 public <X, Y> AttributeImplementor<X, Y> buildAttribute(
     AbstractManagedType<X> ownerType, Property property) {
   if (property.isSynthetic()) {
     // hide synthetic/virtual properties (fabricated by Hibernate) from the JPA metamodel.
     LOG.tracef(
         "Skipping synthetic property %s(%s)",
         ownerType.getJavaType().getName(), property.getName());
     return null;
   }
   LOG.trace(
       "Building attribute ["
           + ownerType.getJavaType().getName()
           + "."
           + property.getName()
           + "]");
   final AttributeContext<X> attributeContext = wrap(ownerType, property);
   final AttributeMetadata<X, Y> attributeMetadata =
       determineAttributeMetadata(attributeContext, NORMAL_MEMBER_RESOLVER);
   if (attributeMetadata == null) {
     return null;
   }
   if (attributeMetadata.isPlural()) {
     return buildPluralAttribute((PluralAttributeMetadata) attributeMetadata);
   }
   final SingularAttributeMetadata<X, Y> singularAttributeMetadata =
       (SingularAttributeMetadata<X, Y>) attributeMetadata;
   final Type<Y> metaModelType = getMetaModelType(singularAttributeMetadata.getValueContext());
   return new SingularAttributeImpl<X, Y>(
       attributeMetadata.getName(),
       attributeMetadata.getJavaType(),
       ownerType,
       attributeMetadata.getMember(),
       false,
       false,
       property.isOptional(),
       metaModelType,
       attributeMetadata.getPersistentAttributeType());
 }
 /**
  * Build the version attribute descriptor
  *
  * @param ownerType The descriptor of the attribute owner (aka declarer).
  * @param property The Hibernate property descriptor for the version attribute
  * @param <X> The type of the owner
  * @param <Y> The attribute type
  * @return The built attribute descriptor
  */
 @SuppressWarnings({"unchecked"})
 public <X, Y> SingularAttributeImpl<X, Y> buildVersionAttribute(
     AbstractIdentifiableType<X> ownerType, Property property) {
   LOG.trace(
       "Building version attribute [ownerType.getJavaType().getName()"
           + "."
           + "property.getName()]");
   final AttributeContext<X> attributeContext = wrap(ownerType, property);
   final SingularAttributeMetadata<X, Y> attributeMetadata =
       (SingularAttributeMetadata<X, Y>)
           determineAttributeMetadata(attributeContext, VERSION_MEMBER_RESOLVER);
   final Type<Y> metaModelType = getMetaModelType(attributeMetadata.getValueContext());
   return new SingularAttributeImpl.Version(
       property.getName(),
       attributeMetadata.getJavaType(),
       ownerType,
       attributeMetadata.getMember(),
       metaModelType,
       attributeMetadata.getPersistentAttributeType());
 }
  private static Document loadURL(URL configURL, EntityResolver resolver) throws Exception {
    /*
     * try and parse the document:
     *  - try and validate the document with persistence_2_0.xsd
     * - if it fails because of the version attribute mismatch, try and validate the document with persistence_1_0.xsd
     */
    InputStream is = null;
    if (configURL != null) {
      URLConnection conn = configURL.openConnection();
      conn.setUseCaches(false); // avoid JAR locking on Windows and Tomcat
      is = conn.getInputStream();
    }
    if (is == null) {
      throw new IOException(
          "Failed to obtain InputStream while reading persistence.xml file: " + configURL);
    }

    DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
    docBuilderFactory.setNamespaceAware(true);
    final Schema v2Schema =
        SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)
            .newSchema(new StreamSource(getStreamFromClasspath("persistence_2_0.xsd")));
    final Validator v2Validator = v2Schema.newValidator();
    final Schema v1Schema =
        SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)
            .newSchema(new StreamSource(getStreamFromClasspath("persistence_1_0.xsd")));
    final Validator v1Validator = v1Schema.newValidator();

    InputSource source = new InputSource(is);
    DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
    docBuilder.setEntityResolver(resolver);
    List<SAXParseException> errors = new ArrayList<SAXParseException>();
    Document doc = null;

    // first sparse document and collect syntaxic errors
    try {
      doc = docBuilder.parse(source);
    } catch (SAXParseException e) {
      errors.add(e);
    }

    if (errors.size() == 0) {
      v2Validator.setErrorHandler(new ErrorLogger(errors));
      LOG.trace("Validate with persistence_2_0.xsd schema on file " + configURL);
      v2Validator.validate(new DOMSource(doc));
      boolean isV1Schema = false;
      if (errors.size() != 0) {
        // v2 fails, it could be because the file is v1.
        LOG.trace("Found error with persistence_2_0.xsd schema on file " + configURL);
        SAXParseException exception = errors.get(0);
        final String errorMessage = exception.getMessage();
        // is it a validation error due to a v1 schema validated by a v2
        isV1Schema =
            errorMessage.contains("1.0")
                && errorMessage.contains("2.0")
                && errorMessage.contains("version");
      }
      if (isV1Schema) {
        LOG.trace("Validate with persistence_1_0.xsd schema on file " + configURL);
        errors.clear();
        v1Validator.setErrorHandler(new ErrorLogger(errors));
        v1Validator.validate(new DOMSource(doc));
      }
    }
    if (errors.size() != 0) {
      // report all errors in the exception
      StringBuilder errorMessage = new StringBuilder();
      for (SAXParseException error : errors) {
        errorMessage
            .append("Error parsing XML (line")
            .append(error.getLineNumber())
            .append(" : column ")
            .append(error.getColumnNumber())
            .append("): ")
            .append(error.getMessage())
            .append("\n");
      }
      throw new PersistenceException("Invalid persistence.xml.\n" + errorMessage.toString());
    }
    return doc;
  }
  private static PersistenceMetadata parsePersistenceUnit(Element top) throws Exception {
    PersistenceMetadata metadata = new PersistenceMetadata();
    String puName = top.getAttribute("name");
    if (StringHelper.isNotEmpty(puName)) {
      LOG.trace("Persistent Unit name from persistence.xml: " + puName);
      metadata.setName(puName);
    }
    NodeList children = top.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
        Element element = (Element) children.item(i);
        String tag = element.getTagName();
        //				if ( tag.equals( "name" ) ) {
        //					String puName = XmlHelper.getElementContent( element );
        //					log.trace( "FOUND PU NAME: " + puName );
        //					metadata.setName( puName );
        //				}
        //				else
        if (tag.equals("non-jta-data-source")) {
          metadata.setNonJtaDatasource(XmlHelper.getElementContent(element));
        } else if (tag.equals("jta-data-source")) {
          metadata.setJtaDatasource(XmlHelper.getElementContent(element));
        } else if (tag.equals("provider")) {
          metadata.setProvider(XmlHelper.getElementContent(element));
        } else if (tag.equals("class")) {
          metadata.getClasses().add(XmlHelper.getElementContent(element));
        } else if (tag.equals("mapping-file")) {
          metadata.getMappingFiles().add(XmlHelper.getElementContent(element));
        } else if (tag.equals("jar-file")) {
          metadata.getJarFiles().add(XmlHelper.getElementContent(element));
        } else if (tag.equals("exclude-unlisted-classes")) {
          metadata.setExcludeUnlistedClasses(true);
        } else if (tag.equals("delimited-identifiers")) {
          metadata.setUseQuotedIdentifiers(true);
        } else if (tag.equals("validation-mode")) {
          metadata.setValidationMode(XmlHelper.getElementContent(element));
        } else if (tag.equals("shared-cache-mode")) {
          metadata.setSharedCacheMode(XmlHelper.getElementContent(element));
        } else if (tag.equals("properties")) {
          NodeList props = element.getChildNodes();
          for (int j = 0; j < props.getLength(); j++) {
            if (props.item(j).getNodeType() == Node.ELEMENT_NODE) {
              Element propElement = (Element) props.item(j);
              if (!"property".equals(propElement.getTagName())) continue;
              String propName = propElement.getAttribute("name").trim();
              String propValue = propElement.getAttribute("value").trim();
              if (StringHelper.isEmpty(propValue)) {
                // fall back to the natural (Hibernate) way of description
                propValue = XmlHelper.getElementContent(propElement, "");
              }
              metadata.getProps().put(propName, propValue);
            }
          }
        }
      }
    }
    PersistenceUnitTransactionType transactionType =
        getTransactionType(top.getAttribute("transaction-type"));
    if (transactionType != null) metadata.setTransactionType(transactionType);

    return metadata;
  }
  /**
   * Here is most of the nuts and bolts of this factory, where we interpret the known JPA metadata
   * against the known Hibernate metadata and build a descriptor for the attribute.
   *
   * @param attributeContext The attribute to be described
   * @param memberResolver Strategy for how to resolve the member defining the attribute.
   * @param <X> The owner type
   * @param <Y> The attribute type
   * @return The attribute description
   */
  @SuppressWarnings({"unchecked"})
  private <X, Y> AttributeMetadata<X, Y> determineAttributeMetadata(
      AttributeContext<X> attributeContext, MemberResolver memberResolver) {
    LOG.trace(
        "Starting attribute metadata determination ["
            + attributeContext.getPropertyMapping().getName()
            + "]");
    final Member member = memberResolver.resolveMember(attributeContext);
    LOG.trace("    Determined member [" + member + "]");

    final Value value = attributeContext.getPropertyMapping().getValue();
    final org.hibernate.type.Type type = value.getType();
    LOG.trace(
        "    Determined type [name="
            + type.getName()
            + ", class="
            + type.getClass().getName()
            + "]");

    if (type.isAnyType()) {
      // ANY mappings are currently not supported in the JPA metamodel; see HHH-6589
      if (context.isIgnoreUnsupported()) {
        return null;
      } else {
        throw new UnsupportedOperationException("ANY not supported");
      }
    } else if (type.isAssociationType()) {
      // collection or entity
      if (type.isEntityType()) {
        // entity
        return new SingularAttributeMetadataImpl<X, Y>(
            attributeContext.getPropertyMapping(),
            attributeContext.getOwnerType(),
            member,
            determineSingularAssociationAttributeType(member));
      }
      // collection
      if (value instanceof Collection) {
        final Collection collValue = (Collection) value;
        final Value elementValue = collValue.getElement();
        final org.hibernate.type.Type elementType = elementValue.getType();

        // First, determine the type of the elements and use that to help determine the
        // collection type)
        final Attribute.PersistentAttributeType elementPersistentAttributeType;
        final Attribute.PersistentAttributeType persistentAttributeType;
        if (elementType.isAnyType()) {
          throw new UnsupportedOperationException("collection of any not supported yet");
        }
        final boolean isManyToMany = isManyToMany(member);
        if (elementValue instanceof Component) {
          elementPersistentAttributeType = Attribute.PersistentAttributeType.EMBEDDED;
          persistentAttributeType = Attribute.PersistentAttributeType.ELEMENT_COLLECTION;
        } else if (elementType.isAssociationType()) {
          elementPersistentAttributeType =
              isManyToMany
                  ? Attribute.PersistentAttributeType.MANY_TO_MANY
                  : Attribute.PersistentAttributeType.ONE_TO_MANY;
          persistentAttributeType = elementPersistentAttributeType;
        } else {
          elementPersistentAttributeType = Attribute.PersistentAttributeType.BASIC;
          persistentAttributeType = Attribute.PersistentAttributeType.ELEMENT_COLLECTION;
        }

        final Attribute.PersistentAttributeType keyPersistentAttributeType;

        // Finally, we determine the type of the map key (if needed)
        if (value instanceof Map) {
          final Value keyValue = ((Map) value).getIndex();
          final org.hibernate.type.Type keyType = keyValue.getType();

          if (keyType.isAnyType())
            throw new UnsupportedOperationException("collection of any not supported yet");
          if (keyValue instanceof Component)
            keyPersistentAttributeType = Attribute.PersistentAttributeType.EMBEDDED;
          else if (keyType.isAssociationType())
            keyPersistentAttributeType = Attribute.PersistentAttributeType.MANY_TO_ONE;
          else keyPersistentAttributeType = Attribute.PersistentAttributeType.BASIC;
        } else keyPersistentAttributeType = null;
        return new PluralAttributeMetadataImpl(
            attributeContext.getPropertyMapping(),
            attributeContext.getOwnerType(),
            member,
            persistentAttributeType,
            elementPersistentAttributeType,
            keyPersistentAttributeType);
      } else if (value instanceof OneToMany) {
        // TODO : is this even possible??? Really OneToMany should be describing the
        // element value within a o.h.mapping.Collection (see logic branch above)
        throw new IllegalArgumentException("HUH???");
        //					final boolean isManyToMany = isManyToMany( member );
        //					//one to many with FK => entity
        //					return new PluralAttributeMetadataImpl(
        //							attributeContext.getPropertyMapping(),
        //							attributeContext.getOwnerType(),
        //							member,
        //							isManyToMany
        //									? Attribute.PersistentAttributeType.MANY_TO_MANY
        //									: Attribute.PersistentAttributeType.ONE_TO_MANY
        //							value,
        //							AttributeContext.TypeStatus.ENTITY,
        //							Attribute.PersistentAttributeType.ONE_TO_MANY,
        //							null, null, null
        //					);
      }
    } else if (attributeContext.getPropertyMapping().isComposite()) {
      // component
      return new SingularAttributeMetadataImpl<X, Y>(
          attributeContext.getPropertyMapping(),
          attributeContext.getOwnerType(),
          member,
          Attribute.PersistentAttributeType.EMBEDDED);
    } else {
      // basic type
      return new SingularAttributeMetadataImpl<X, Y>(
          attributeContext.getPropertyMapping(),
          attributeContext.getOwnerType(),
          member,
          Attribute.PersistentAttributeType.BASIC);
    }
    throw new UnsupportedOperationException(
        "oops, we are missing something: " + attributeContext.getPropertyMapping());
  }
  @Override
  @SuppressWarnings({"deprecation"})
  public TypedQuery<X> setHint(String hintName, Object value) {
    boolean skipped = false;
    try {
      if (HINT_TIMEOUT.equals(hintName)) {
        applyTimeout(ConfigurationHelper.getInteger(value));
      } else if (SPEC_HINT_TIMEOUT.equals(hintName)) {
        // convert milliseconds to seconds
        int timeout =
            (int) Math.round(ConfigurationHelper.getInteger(value).doubleValue() / 1000.0);
        applyTimeout(timeout);
      } else if (AvailableSettings.LOCK_TIMEOUT.equals(hintName)) {
        applyLockTimeout(ConfigurationHelper.getInteger(value));
      } else if (HINT_COMMENT.equals(hintName)) {
        applyComment((String) value);
      } else if (HINT_FETCH_SIZE.equals(hintName)) {
        applyFetchSize(ConfigurationHelper.getInteger(value));
      } else if (HINT_CACHEABLE.equals(hintName)) {
        applyCacheable(ConfigurationHelper.getBoolean(value));
      } else if (HINT_CACHE_REGION.equals(hintName)) {
        applyCacheRegion((String) value);
      } else if (HINT_READONLY.equals(hintName)) {
        applyReadOnly(ConfigurationHelper.getBoolean(value));
      } else if (HINT_CACHE_MODE.equals(hintName)) {
        applyCacheMode(ConfigurationHelper.getCacheMode(value));
      } else if (HINT_FLUSH_MODE.equals(hintName)) {
        applyFlushMode(ConfigurationHelper.getFlushMode(value));
      } else if (AvailableSettings.SHARED_CACHE_RETRIEVE_MODE.equals(hintName)) {
        final CacheRetrieveMode retrieveMode = (CacheRetrieveMode) value;

        CacheStoreMode storeMode =
            hints != null
                ? (CacheStoreMode) hints.get(AvailableSettings.SHARED_CACHE_STORE_MODE)
                : null;
        if (storeMode == null) {
          storeMode =
              (CacheStoreMode)
                  entityManager.getProperties().get(AvailableSettings.SHARED_CACHE_STORE_MODE);
        }
        applyCacheMode(CacheModeHelper.interpretCacheMode(storeMode, retrieveMode));
      } else if (AvailableSettings.SHARED_CACHE_STORE_MODE.equals(hintName)) {
        final CacheStoreMode storeMode = (CacheStoreMode) value;

        CacheRetrieveMode retrieveMode =
            hints != null
                ? (CacheRetrieveMode) hints.get(AvailableSettings.SHARED_CACHE_RETRIEVE_MODE)
                : null;
        if (retrieveMode == null) {
          retrieveMode =
              (CacheRetrieveMode)
                  entityManager.getProperties().get(AvailableSettings.SHARED_CACHE_RETRIEVE_MODE);
        }
        applyCacheMode(CacheModeHelper.interpretCacheMode(storeMode, retrieveMode));
      } else if (hintName.startsWith(AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE)) {
        if (!canApplyLockModes()) {
          skipped = true;
        } else {
          // extract the alias
          final String alias =
              hintName.substring(AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE.length() + 1);
          // determine the LockMode
          try {
            final LockMode lockMode = LockModeTypeHelper.interpretLockMode(value);
            applyAliasSpecificLockMode(alias, lockMode);
          } catch (Exception e) {
            LOG.unableToDetermineLockModeValue(hintName, value);
            skipped = true;
          }
        }
      } else {
        skipped = true;
        LOG.ignoringUnrecognizedQueryHint(hintName);
      }
    } catch (ClassCastException e) {
      throw new IllegalArgumentException("Value for hint");
    }

    if (!skipped) {
      if (hints == null) {
        hints = new HashMap<String, Object>();
      }
      hints.put(hintName, value);
    }

    return this;
  }