/*
* #%L
* BroadleafCommerce Open Admin Platform
* %%
* Copyright (C) 2009 - 2013 Broadleaf Commerce
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package org.broadleafcommerce.openadmin.server.service.persistence.module.criteria;
import org.apache.commons.lang.StringUtils;
import org.broadleafcommerce.common.util.dao.DynamicDaoHelper;
import org.broadleafcommerce.common.util.dao.DynamicDaoHelperImpl;
import org.hibernate.ejb.EntityManagerFactoryImpl;
import org.hibernate.ejb.criteria.CriteriaBuilderImpl;
import org.hibernate.ejb.criteria.path.PluralAttributePath;
import org.hibernate.ejb.criteria.path.SingularAttributePath;
import org.hibernate.internal.SessionFactoryImpl;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.persistence.Embeddable;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
/**
* @author Jeff Fischer
*/
public class FieldPathBuilder {
protected DynamicDaoHelper dynamicDaoHelper = new DynamicDaoHelperImpl();
protected CriteriaQuery criteria;
protected List<Predicate> restrictions;
public FieldPath getFieldPath(From root, String fullPropertyName) {
String[] pieces = fullPropertyName.split("\\.");
List<String> associationPath = new ArrayList<String>();
List<String> basicProperties = new ArrayList<String>();
int j = 0;
for (String piece : pieces) {
checkPiece: {
if (j == 0) {
Path path = root.get(piece);
if (path instanceof PluralAttributePath) {
associationPath.add(piece);
break checkPiece;
}
}
basicProperties.add(piece);
}
j++;
}
FieldPath fieldPath = new FieldPath()
.withAssociationPath(associationPath)
.withTargetPropertyPieces(basicProperties);
return fieldPath;
}
public Path getPath(From root, String fullPropertyName, CriteriaBuilder builder) {
return getPath(root, getFieldPath(root, fullPropertyName), builder);
}
@SuppressWarnings({"rawtypes", "unchecked", "serial"})
public Path getPath(From root, FieldPath fieldPath, final CriteriaBuilder builder) {
FieldPath myFieldPath = fieldPath;
if (!StringUtils.isEmpty(fieldPath.getTargetProperty())) {
myFieldPath = getFieldPath(root, fieldPath.getTargetProperty());
}
From myRoot = root;
for (String pathElement : myFieldPath.getAssociationPath()) {
myRoot = myRoot.join(pathElement);
}
Path path = myRoot;
for (int i = 0; i < myFieldPath.getTargetPropertyPieces().size(); i++) {
String piece = myFieldPath.getTargetPropertyPieces().get(i);
if (path.getJavaType().isAnnotationPresent(Embeddable.class)) {
String original = ((SingularAttributePath) path).getAttribute().getDeclaringType().getJavaType().getName() + "." + ((SingularAttributePath) path).getAttribute().getName() + "." + piece;
String copy = path.getJavaType().getName() + "." + piece;
copyCollectionPersister(original, copy, ((CriteriaBuilderImpl) builder).getEntityManagerFactory().getSessionFactory());
}
try {
path = path.get(piece);
} catch (IllegalArgumentException e) {
// We weren't able to resolve the requested piece, likely because it's in a polymoprhic version
// of the path we're currently on. Let's see if there's any polymoprhic version of our class to
// use instead.
EntityManagerFactoryImpl em = ((CriteriaBuilderImpl) builder).getEntityManagerFactory();
Metamodel mm = em.getMetamodel();
boolean found = false;
Class<?>[] polyClasses = dynamicDaoHelper.getAllPolymorphicEntitiesFromCeiling(
path.getJavaType(), em.getSessionFactory(), true, true);
for (Class<?> clazz : polyClasses) {
ManagedType mt = mm.managedType(clazz);
try {
Attribute attr = mt.getAttribute(piece);
if (attr != null) {
Root additionalRoot = criteria.from(clazz);
restrictions.add(builder.equal(path, additionalRoot));
path = additionalRoot.get(piece);
found = true;
break;
}
} catch (IllegalArgumentException e2) {
// Do nothing - we'll try the next class and see if it has the attribute
}
}
if (!found) {
throw new IllegalArgumentException("Could not resolve requested attribute against path, including" +
" known polymorphic versions of the root", e);
}
}
if (path.getParentPath() != null && path.getParentPath().getJavaType().isAnnotationPresent(Embeddable.class) && path instanceof PluralAttributePath) {
//We need a workaround for this problem until it is resolved in Hibernate (loosely related to and likely resolved by https://hibernate.atlassian.net/browse/HHH-8802)
//We'll throw a specialized exception (and handle in an alternate flow for calls from BasicPersistenceModule)
throw new CriteriaConversionException(String.format("Unable to create a JPA criteria Path through an @Embeddable object to a collection that resides therein (%s)", fieldPath.getTargetProperty()), fieldPath);
// //TODO this code should work, but there still appear to be bugs in Hibernate's JPA criteria handling for lists
// //inside Embeddables
// Class<?> myClass = ((PluralAttributePath) path).getAttribute().getClass().getInterfaces()[0];
// //we don't know which version of "join" to call, so we'll let reflection figure it out
// try {
// From embeddedJoin = myRoot.join(((SingularAttributePath) path.getParentPath()).getAttribute());
// Method join = embeddedJoin.getClass().getMethod("join", myClass);
// path = (Path) join.invoke(embeddedJoin, ((PluralAttributePath) path).getAttribute());
// } catch (Exception e) {
// throw new RuntimeException(e);
// }
}
}
return path;
}
/**
* This is a workaround for HHH-6562 (https://hibernate.atlassian.net/browse/HHH-6562)
*/
@SuppressWarnings("unchecked")
private void copyCollectionPersister(String originalKey, String copyKey,
SessionFactoryImpl sessionFactory) {
try {
Field collectionPersistersField = SessionFactoryImpl.class
.getDeclaredField("collectionPersisters");
collectionPersistersField.setAccessible(true);
Map collectionPersisters = (Map) collectionPersistersField.get(sessionFactory);
if (collectionPersisters.containsKey(originalKey)) {
Object collectionPersister = collectionPersisters.get(originalKey);
collectionPersisters.put(copyKey, collectionPersister);
}
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public CriteriaQuery getCriteria() {
return criteria;
}
public void setCriteria(CriteriaQuery criteria) {
this.criteria = criteria;
}
public List<Predicate> getRestrictions() {
return restrictions;
}
public void setRestrictions(List<Predicate> restrictions) {
this.restrictions = restrictions;
}
}