/**
* 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.
*/
package org.deephacks.confit.internal.jpa;
import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import org.deephacks.confit.model.BeanId;
import org.deephacks.confit.model.ThreadLocalManager;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Query;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.UUID;
import static com.google.common.base.Objects.equal;
@Entity
@Table(name = "CONFIG_BEAN_REF")
@NamedQueries({
@NamedQuery(name = JpaRef.DELETE_REF_USING_BEANID_NAME,
query = JpaRef.DELETE_REF_USING_BEANID),
@NamedQuery(name = JpaRef.DELETE_REF_USING_PROPNAME_NAME,
query = JpaRef.DELETE_REF_USING_PROPNAME),
@NamedQuery(name = JpaRef.FIND_REFS_FOR_BEANS_HIBERNATE_NAME,
query = JpaRef.FIND_REFS_FOR_BEANS_HIBERNATE),
@NamedQuery(name = JpaRef.FIND_REFS_FOR_BEANS_DEFAULT_NAME,
query = JpaRef.FIND_REFS_FOR_BEANS_DEFAULT),
@NamedQuery(name = JpaRef.FIND_PREDECESSORS_FOR_BEAN_DEFAULT_NAME,
query = JpaRef.FIND_PREDECESSORS_FOR_BEAN_DEFAULT),
@NamedQuery(name = JpaRef.FIND_PREDECESSORS_FOR_BEAN_HIBERNATE_NAME,
query = JpaRef.FIND_PREDECESSORS_FOR_BEAN_HIBERNATE)})
public class JpaRef implements Serializable {
private static final long serialVersionUID = -3528959706883881047L;
@Id
@Column(name = "UUID")
String id;
@Column(name = "FK_SOURCE_BEAN_ID", nullable = false)
protected String sourceId;
@Column(name = "FK_SOURCE_BEAN_SCHEMA_NAME", nullable = false)
protected String sourceSchemaName;
@Column(name = "FK_TARGET_BEAN_ID", nullable = false)
protected String targetId;
@Column(name = "FK_TARGET_BEAN_SCHEMA_NAME", nullable = false)
protected String targetSchemaName;
@Column(name = "PROP_NAME")
private String propertyName;
protected static final String DELETE_REF_USING_BEANID = "DELETE FROM JpaRef e WHERE e.sourceId = ?1 AND e.sourceSchemaName= ?2";
protected static final String DELETE_REF_USING_BEANID_NAME = "DELETE_REF_USING_BEANID_NAME";
@Transient
private JpaBean target;
public static void deleteReferences(BeanId id) {
Query query = getEm().createNamedQuery(DELETE_REF_USING_BEANID_NAME);
query.setParameter(1, id.getInstanceId());
query.setParameter(2, id.getSchemaName());
query.executeUpdate();
}
protected static final String DELETE_REF_USING_PROPNAME = "DELETE FROM JpaRef e WHERE e.sourceId = ?1 AND e.sourceSchemaName= ?2 AND e.propertyName= ?3";
protected static final String DELETE_REF_USING_PROPNAME_NAME = "DELETE_REF_USING_PROPNAME_NAME";
public static void deleteReference(BeanId id, String propName) {
Query query = getEm().createNamedQuery(DELETE_REF_USING_PROPNAME_NAME);
query.setParameter(1, id.getInstanceId());
query.setParameter(2, id.getSchemaName());
query.setParameter(3, propName);
query.executeUpdate();
}
protected static final String FIND_REFS_FOR_BEANS_DEFAULT = "SELECT e FROM JpaRef e WHERE (e.sourceId IN :ids AND e.sourceSchemaName IN :schemaNames)";
protected static final String FIND_REFS_FOR_BEANS_DEFAULT_NAME = "FIND_REFS_FOR_BEANS_DEFAULT_NAME";
protected static final String FIND_REFS_FOR_BEANS_HIBERNATE = "SELECT e FROM JpaRef e WHERE (e.sourceId IN (:ids) AND e.sourceSchemaName IN (:schemaNames))";
protected static final String FIND_REFS_FOR_BEANS_HIBERNATE_NAME = "FIND_REFS_FOR_BEANS_HIBERNATE_NAME";
@SuppressWarnings("unchecked")
public static Multimap<BeanId, JpaRef> findReferences(Set<BeanId> beanIds) {
Multimap<BeanId, JpaRef> refs = ArrayListMultimap.create();
if (beanIds.size() == 0) {
return refs;
}
String namedQuery = FIND_REFS_FOR_BEANS_DEFAULT_NAME;
if (getEm().getClass().getName().contains("hibernate")) {
/**
* Hibernate and EclipseLink treat IN queries differently.
* EclipseLink mandates NO brackets, while hibernate mandates WITH brackets.
* In order to support both, this ugly hack is needed.
*/
namedQuery = FIND_REFS_FOR_BEANS_HIBERNATE_NAME;
}
Query query = getEm().createNamedQuery(namedQuery);
Collection<String> ids = new ArrayList<String>();
Collection<String> schemaNames = new ArrayList<String>();
for (BeanId id : beanIds) {
ids.add(id.getInstanceId());
schemaNames.add(id.getSchemaName());
}
query.setParameter("ids", ids);
query.setParameter("schemaNames", schemaNames);
List<JpaRef> result = (List<JpaRef>) query.getResultList();
filterUnwantedReferences(result, beanIds);
for (JpaRef jpaRef : result) {
refs.put(jpaRef.getSource(), jpaRef);
}
return refs;
}
/**
* Beans with different schemaName may have same instance id.
*
* The IN search query is greedy, finding instances that
* match any combination of instance id and schemaName. Hence,
* the query may find references belonging to wrong schema
* so filter those out.
*/
static void filterUnwantedReferences(List<JpaRef> result, Collection<BeanId> query) {
ListIterator<JpaRef> it = result.listIterator();
while (it.hasNext()) {
// remove reference from result that was not part of the query
BeanId found = it.next().getSource();
if (!query.contains(found)) {
it.remove();
}
}
}
protected static final String FIND_PREDECESSORS_FOR_BEAN_DEFAULT = "SELECT e FROM JpaRef e WHERE (e.targetId IN :ids AND e.targetSchemaName IN :schemaNames)";
protected static final String FIND_PREDECESSORS_FOR_BEAN_DEFAULT_NAME = "FIND_PREDECESSORS_FOR_BEAN_DEFAULT_NAME";
protected static final String FIND_PREDECESSORS_FOR_BEAN_HIBERNATE = "SELECT e FROM JpaRef e WHERE (e.targetId IN (:ids) AND e.targetSchemaName IN (:schemaNames))";
protected static final String FIND_PREDECESSORS_FOR_BEAN_HIBERNATE_NAME = "FIND_PREDECESSORS_FOR_BEAN_HIBERNATE_NAME";
@SuppressWarnings("unchecked")
public static List<JpaRef> getDirectPredecessors(Set<BeanId> beanIds) {
if (beanIds.size() == 0) {
return new ArrayList<JpaRef>();
}
String namedQuery = FIND_PREDECESSORS_FOR_BEAN_DEFAULT_NAME;
if (getEm().getClass().getName().contains("hibernate")) {
/**
* Hibernate and EclipseLink treat IN queries differently.
* EclipseLink mandates NO brackets, while hibernate mandates WITH brackets.
* In order to support both, this ugly hack is needed.
*/
namedQuery = FIND_PREDECESSORS_FOR_BEAN_HIBERNATE_NAME;
}
Query query = getEm().createNamedQuery(namedQuery);
Collection<String> ids = new ArrayList<String>();
Collection<String> schemaNames = new ArrayList<String>();
for (BeanId id : beanIds) {
ids.add(id.getInstanceId());
schemaNames.add(id.getSchemaName());
}
query.setParameter("ids", ids);
query.setParameter("schemaNames", schemaNames);
List<JpaRef> result = (List<JpaRef>) query.getResultList();
return result;
}
private static EntityManager getEm() {
EntityManager em = ThreadLocalManager.peek(EntityManager.class);
if (em == null) {
throw JpaEvents.JPA202_MISSING_THREAD_EM();
}
return em;
}
public JpaRef() {
}
public JpaRef(JpaBean source, JpaBean target, String propName) {
this.sourceId = source.getId().getInstanceId();
this.sourceSchemaName = source.getId().getSchemaName();
this.targetId = target.getId().getInstanceId();
this.targetSchemaName = target.getId().getSchemaName();
this.propertyName = propName;
this.id = UUID.randomUUID().toString();
}
public JpaRef(BeanId source, BeanId target, String propName) {
this.sourceId = source.getInstanceId();
this.sourceSchemaName = source.getSchemaName();
this.targetId = target.getInstanceId();
this.targetSchemaName = target.getSchemaName();
this.propertyName = propName;
this.id = UUID.randomUUID().toString();
}
public String getPropertyName() {
return propertyName;
}
public BeanId getSource() {
return BeanId.create(sourceId, sourceSchemaName);
}
public BeanId getTarget() {
return BeanId.create(targetId, targetSchemaName);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof JpaRef)) {
return false;
}
JpaRef o = (JpaRef) obj;
return equal(id, o.id);
}
@Override
public int hashCode() {
return Objects.hashCode(id);
}
@Override
public String toString() {
return Objects.toStringHelper(JpaRef.class).add("propertyName", propertyName)
.add("source", sourceId + "@" + sourceSchemaName)
.add("target", targetId + "@" + targetSchemaName).toString();
}
public void setTargetBean(JpaBean target) {
this.target = target;
}
public JpaBean getTargetBean() {
return target;
}
}