/**
* 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.base.Optional;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import org.deephacks.confit.model.Bean;
import org.deephacks.confit.model.BeanId;
import org.deephacks.confit.model.ThreadLocalManager;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.persistence.Table;
import javax.persistence.Transient;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Objects.toStringHelper;
import static org.deephacks.confit.internal.jpa.JpaProperty.deletePropertiesAndMarker;
/**
* JpaBean is a jpa entity that represent a Bean.
*
* @author Kristoffer Sjogren
*/
@Entity
@Table(name = "CONFIG_BEAN")
@NamedQueries({
@NamedQuery(name = JpaBean.FIND_BEAN_FROM_BEANID_NAME,
query = JpaBean.FIND_BEAN_FROM_BEANID),
@NamedQuery(name = JpaBean.FIND_BEANS_FROM_SCHEMA_NAME,
query = JpaBean.FIND_BEANS_FROM_SCHEMA),
@NamedQuery(name = JpaBean.DELETE_BEAN_USING_BEANID_NAME,
query = JpaBean.DELETE_BEAN_USING_BEANID)})
public class JpaBean implements Serializable {
private static final long serialVersionUID = -4097243985344046349L;
@EmbeddedId
private JpaBeanPk pk;
public static List<Bean> findEager(Set<BeanId> ids) {
if (ids.size() == 0) {
return new ArrayList<>();
}
JpaBeanQueryAssembler query = new JpaBeanQueryAssembler(ids);
// collect references recursively
collectRefs(ids, query, 10);
// fetch properties for list beans at once
List<JpaProperty> allProperties = JpaProperty.findProperties(query.getIds());
query.addProperties(allProperties);
return query.assembleBeans();
}
public static Bean findEager(BeanId id) {
List<Bean> beans = findEager(Sets.newHashSet(id));
if (beans == null || beans.size() == 0) {
return null;
}
return beans.get(0);
}
public static List<Bean> findEager(List<BeanId> ids) {
/**
* TODO: Need to take care of BeanId that may apear more than once in 'ids'.
*/
return findEager(Sets.newHashSet(ids));
}
private static void collectRefs(Set<BeanId> predecessors, JpaBeanQueryAssembler query, int level) {
if (--level < 0) {
return;
}
Multimap<BeanId, JpaRef> successors = JpaRef.findReferences(predecessors);
if (successors.size() > 0) {
query.addRefs(predecessors);
}
// only recurse successors we havent already visited to break circular references
Set<BeanId> unvisitedSuccessors = new HashSet<>();
for (JpaRef successor : successors.values()) {
if (!query.contains(successor.getTarget())) {
unvisitedSuccessors.add(successor.getTarget());
}
}
if (unvisitedSuccessors.size() != 0) {
// we have reached the end and found list successors
collectRefs(unvisitedSuccessors, query, level);
}
query.addRefs(successors);
}
/**
* Finds the provided beans and initalize their properties and direct
* references (but no further).
*/
public static List<Bean> findLazy(Set<BeanId> ids) {
if (ids.size() == 0) {
return new ArrayList<>();
}
JpaBeanQueryAssembler query = new JpaBeanQueryAssembler(ids);
List<JpaProperty> allProperties = JpaProperty.findProperties(query.getIds());
query.addProperties(allProperties);
Multimap<BeanId, JpaRef> refs = JpaRef.findReferences(ids);
if (refs.size() > 0) {
query.addRefs(refs);
}
return query.assembleBeans();
}
protected static final String FIND_BEANS_FROM_SCHEMA = "SELECT DISTINCT e FROM JpaBean e WHERE e.pk.schemaName= ?1 ORDER BY e.pk.id";
protected static final String FIND_BEANS_FROM_SCHEMA_NAME = "FIND_BEANS_FROM_SCHEMA_NAME";
@SuppressWarnings("unchecked")
public static List<Bean> findEager(String schemaName) {
Optional<EntityManager> em = getEm();
if (!em.isPresent()) {
return new ArrayList<>();
}
Query query = em.get().createNamedQuery(FIND_BEANS_FROM_SCHEMA_NAME);
query.setParameter(1, schemaName);
List<JpaBean> beans = (List<JpaBean>) query.getResultList();
Set<BeanId> ids = new HashSet<>();
for (JpaBean jpaBean : beans) {
ids.add(jpaBean.getId());
}
return findEager(ids);
}
/**
* Will return the target bean and its direct predecessors for validation
*/
public static Set<Bean> getBeanToValidate(Set<BeanId> ids) {
List<JpaRef> targetPredecessors = JpaRef.getDirectPredecessors(ids);
Set<BeanId> beansToValidate = new HashSet<>();
for (JpaRef ref : targetPredecessors) {
beansToValidate.add(ref.getSource());
}
beansToValidate.addAll(ids);
JpaBeanQueryAssembler query = new JpaBeanQueryAssembler(beansToValidate);
collectRefs(beansToValidate, query, 2);
List<JpaProperty> allProperties = JpaProperty.findProperties(query.getIds());
query.addProperties(allProperties);
return new HashSet<>(query.assembleBeans());
}
protected static final String FIND_BEAN_FROM_BEANID = "SELECT DISTINCT e FROM JpaBean e WHERE e.pk.id = ?1 AND e.pk.schemaName= ?2";
protected static final String FIND_BEAN_FROM_BEANID_NAME = "FIND_BEAN_FROM_BEANID_NAME";
public static boolean exists(BeanId id) {
Query query = getEmOrFail().createNamedQuery(FIND_BEAN_FROM_BEANID_NAME);
query.setParameter(1, id.getInstanceId());
query.setParameter(2, id.getSchemaName());
try {
query.getSingleResult();
} catch (NoResultException e) {
return false;
}
return true;
}
/**
* Need not consult the JpaBean table since the property marker
* in JpaProperties table is an indicator that the JpaBean really
* exist, even if it has no properties.
*/
@SuppressWarnings("unused")
private static JpaBean getJpaBeanAndProperties(BeanId id) {
List<JpaProperty> props = JpaProperty.findProperties(id);
if (props.size() == 0) {
// no marker, bean does not exist
return null;
}
JpaBean bean = new JpaBean(new JpaBeanPk(id));
JpaProperty.filterMarkerProperty(props);
bean.properties.addAll(props);
return bean;
}
protected static final String DELETE_BEAN_USING_BEANID = "DELETE FROM JpaBean e WHERE e.pk.id = ?1 AND e.pk.schemaName= ?2";
protected static final String DELETE_BEAN_USING_BEANID_NAME = "DELETE_BEAN_USING_BEANID_NAME";
public static Bean deleteJpaBean(BeanId id) {
Bean bean = findEager(id);
deletePropertiesAndMarker(id);
JpaRef.deleteReferences(id);
Query query = getEmOrFail().createNamedQuery(DELETE_BEAN_USING_BEANID_NAME);
query.setParameter(1, id.getInstanceId());
query.setParameter(2, id.getSchemaName());
query.executeUpdate();
return bean;
}
private static EntityManager getEmOrFail() {
Optional<EntityManager> em = getEm();
if (!em.isPresent()) {
throw JpaEvents.JPA202_MISSING_THREAD_EM();
}
return em.get();
}
private static Optional<EntityManager> getEm() {
EntityManager em = ThreadLocalManager.peek(EntityManager.class);
if (em == null) {
return Optional.absent();
}
return Optional.of(em);
}
@Transient
private Set<JpaRef> references = new HashSet<>();
@Transient
private Set<JpaProperty> properties = new HashSet<>();
public JpaBean() {
}
JpaBean(Bean b) {
this.pk = new JpaBeanPk(b.getId());
}
JpaBean(JpaBeanPk pk) {
this.pk = pk;
}
public JpaBeanPk getPk() {
return pk;
}
public BeanId getId() {
return BeanId.create(pk.id, pk.schemaName);
}
public Set<JpaRef> getReferences() {
return references;
}
public void addReference(JpaRef ref) {
references.add(ref);
}
public Set<JpaProperty> getProperties() {
return properties;
}
public void addProperty(String name, List<String> values) {
for (String value : values) {
properties.add(new JpaProperty(getId(), name, value));
}
}
public void addProperty(JpaProperty prop) {
properties.add(prop);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof JpaBean)) {
return false;
}
JpaBean o = (JpaBean) obj;
return equal(pk, o.pk);
}
@Override
public int hashCode() {
return Objects.hashCode(pk);
}
@Override
public String toString() {
return toStringHelper(JpaBean.class).add("pk", pk).toString();
}
}