/**
* 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 org.deephacks.confit.model.Bean;
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 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;
import static com.google.common.base.Objects.toStringHelper;
/**
*
* @author Kristoffer Sjogren
*/
@Entity
@Table(name = "CONFIG_PROPERTY")
@NamedQueries({
@NamedQuery(name = JpaProperty.DELETE_ALL_PROPERTIES_FOR_BEANID_NAME,
query = JpaProperty.DELETE_ALL_PROPERTIES_FOR_BEANID),
@NamedQuery(name = JpaProperty.DELETE_PROPERTY_FOR_BEANID_NAME,
query = JpaProperty.DELETE_PROPERTY_FOR_BEANID),
@NamedQuery(name = JpaProperty.FIND_PROPERTIES_FOR_BEANS_DEFAULT_NAME,
query = JpaProperty.FIND_PROPERTIES_FOR_BEANS_DEFAULT),
@NamedQuery(name = JpaProperty.FIND_PROPERTIES_FOR_BEANS_HIBERNATE_NAME,
query = JpaProperty.FIND_PROPERTIES_FOR_BEANS_HIBERNATE),
@NamedQuery(name = JpaProperty.FIND_PROPERTIES_FOR_SCHEMA_DEFAULT_NAME,
query = JpaProperty.FIND_PROPERTIES_FOR_SCHEMA_DEFAULT),
@NamedQuery(name = JpaProperty.FIND_PROPERTIES_FOR_SCHEMA_HIBERNATE_NAME,
query = JpaProperty.FIND_PROPERTIES_FOR_SCHEMA_HIBERNATE),
@NamedQuery(name = JpaProperty.FIND_PROPERTIES_FOR_BEAN_NAME,
query = JpaProperty.FIND_PROPERTIES_FOR_BEAN) })
public class JpaProperty implements Serializable {
private static final long serialVersionUID = -8467786505761160478L;
/**
* This property ensure that there will be one row in the
* properties table for every bean.
*
* The purpose is to reduce number of SQL calls and increase database
* performance. With this special property, fetching beans and properties
* need only consult one table and no JOINS are needed.
*/
public static final String BEAN_MARKER_PROPERTY_NAME = "#BEAN_MARKER_PROPERTY#";
@Id
@Column(name = "UUID")
private String uuid;
@Column(name = "FK_BEAN_ID", nullable = false)
private String id;
@Column(name = "FK_BEAN_SCHEMA_NAME", nullable = false)
private String schemaName;
@Column(name = "PROP_NAME", nullable = false)
private String propName;
@Column(name = "PROP_VALUE")
private String value;
protected static final String DELETE_ALL_PROPERTIES_FOR_BEANID = "DELETE FROM JpaProperty e WHERE e.id = ?1 AND e.schemaName= ?2 AND NOT (e.propName = ?3)";
protected static final String DELETE_ALL_PROPERTIES_FOR_BEANID_NAME = "DELETE_ALL_PROPERTIES_FOR_BEANID";
/**
* Delete list properties EXCEPT the marker. This is useful for 'set' operations
* that need to clear existing properties.
*/
public static void deleteProperties(BeanId id) {
Query query = getEmOrFail().createNamedQuery(DELETE_ALL_PROPERTIES_FOR_BEANID_NAME);
query.setParameter(1, id.getInstanceId());
query.setParameter(2, id.getSchemaName());
query.setParameter(3, BEAN_MARKER_PROPERTY_NAME);
query.executeUpdate();
}
/**
* Deletes the JpaBean and list its properties and the marker.
*/
public static void deletePropertiesAndMarker(BeanId id) {
Query query = getEmOrFail().createNamedQuery(DELETE_ALL_PROPERTIES_FOR_BEANID_NAME);
query.setParameter(1, id.getInstanceId());
query.setParameter(2, id.getSchemaName());
// empty will marker will delete the marker aswell
query.setParameter(3, "");
query.executeUpdate();
}
protected static final String DELETE_PROPERTY_FOR_BEANID = "DELETE FROM JpaProperty e WHERE e.id = ?1 AND e.schemaName= ?2 AND e.propName = ?3";
protected static final String DELETE_PROPERTY_FOR_BEANID_NAME = "DELETE_PROPERTY_FOR_BEANID_NAME";
public static void deleteProperty(BeanId id, String propName) {
Query query = getEmOrFail().createNamedQuery(DELETE_PROPERTY_FOR_BEANID_NAME);
query.setParameter(1, id.getInstanceId());
query.setParameter(2, id.getSchemaName());
query.setParameter(3, propName);
query.executeUpdate();
}
protected static final String FIND_PROPERTIES_FOR_BEAN = "SELECT e FROM JpaProperty e WHERE e.id= ?1 AND e.schemaName= ?2";
protected static final String FIND_PROPERTIES_FOR_BEAN_NAME = "FIND_PROPERTIES_FOR_BEAN_NAME";
@SuppressWarnings("unchecked")
public static List<JpaProperty> findProperties(BeanId id) {
Optional<EntityManager> optional = getEm();
if (!optional.isPresent()) {
return new ArrayList<>();
}
EntityManager em = optional.get();
Query query = em.createNamedQuery(FIND_PROPERTIES_FOR_BEAN_NAME);
query.setParameter(1, id.getInstanceId());
query.setParameter(2, id.getSchemaName());
List<JpaProperty> properties = (List<JpaProperty>) query.getResultList();
return properties;
}
protected static final String FIND_PROPERTIES_FOR_BEANS_DEFAULT = "SELECT e FROM JpaProperty e WHERE (e.id IN :ids AND e.schemaName IN :schemaNames)";
protected static final String FIND_PROPERTIES_FOR_BEANS_DEFAULT_NAME = "FIND_PROPERTIES_FOR_BEANS_DEFAULT_NAME";
protected static final String FIND_PROPERTIES_FOR_BEANS_HIBERNATE = "SELECT e FROM JpaProperty e WHERE (e.id IN (:ids) AND e.schemaName IN (:schemaNames))";
protected static final String FIND_PROPERTIES_FOR_BEANS_HIBERNATE_NAME = "FIND_PROPERTIES_FOR_BEANS_HIBERNATE_NAME";
@SuppressWarnings("unchecked")
public static List<JpaProperty> findProperties(Set<BeanId> beanIds) {
Optional<EntityManager> optional = getEm();
if (!optional.isPresent() || beanIds.size() == 0) {
return new ArrayList<>();
}
EntityManager em = optional.get();
String namedQuery = FIND_PROPERTIES_FOR_BEANS_DEFAULT_NAME;
if (em.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_PROPERTIES_FOR_BEANS_HIBERNATE_NAME;
}
Query query = em.createNamedQuery(namedQuery);
Collection<String> ids = new ArrayList<>();
Collection<String> schemaNames = new ArrayList<>();
for (BeanId id : beanIds) {
ids.add(id.getInstanceId());
schemaNames.add(id.getSchemaName());
}
query.setParameter("ids", ids);
query.setParameter("schemaNames", schemaNames);
List<JpaProperty> properties = (List<JpaProperty>) query.getResultList();
filterUnwantedReferences(properties, beanIds);
return properties;
}
/**
* 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<JpaProperty> result, Set<BeanId> query) {
ListIterator<JpaProperty> it = result.listIterator();
while (it.hasNext()) {
// remove property from result that was not part of the query
JpaProperty found = it.next();
if (!query.contains(found.getId())) {
it.remove();
}
}
}
protected static final String FIND_PROPERTIES_FOR_SCHEMA_DEFAULT = "SELECT e FROM JpaProperty e WHERE (e.schemaName IN :schemaNames)";
protected static final String FIND_PROPERTIES_FOR_SCHEMA_DEFAULT_NAME = "FIND_PROPERTIES_FOR_SCHEMA_DEFAULT_NAME";
protected static final String FIND_PROPERTIES_FOR_SCHEMA_HIBERNATE = "SELECT e FROM JpaProperty e WHERE (e.schemaName IN (:schemaNames))";
protected static final String FIND_PROPERTIES_FOR_SCHEMA_HIBERNATE_NAME = "FIND_PROPERTIES_FOR_SCHEMA_HIBERNATE_NAME";
@SuppressWarnings("unchecked")
public static List<JpaProperty> findProperties(String schemaName) {
Optional<EntityManager> optional = getEm();
if (!optional.isPresent()) {
return new ArrayList<>();
}
String namedQuery = FIND_PROPERTIES_FOR_SCHEMA_DEFAULT_NAME;
EntityManager em = optional.get();
if (em.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_PROPERTIES_FOR_SCHEMA_HIBERNATE_NAME;
}
Query query = em.createNamedQuery(namedQuery);
Collection<String> schemaNames = new ArrayList<>();
schemaNames.add(schemaName);
query.setParameter("schemaNames", schemaNames);
List<JpaProperty> properties = (List<JpaProperty>) query.getResultList();
return properties;
}
private static EntityManager getEmOrFail() {
Optional<EntityManager> em = getEm();
if (em.isPresent()) {
return em.get();
}
throw JpaEvents.JPA202_MISSING_THREAD_EM();
}
private static Optional<EntityManager> getEm() {
EntityManager em = ThreadLocalManager.peek(EntityManager.class);
if (em == null) {
return Optional.absent();
}
return Optional.of(em);
}
public JpaProperty() {
}
JpaProperty(BeanId owner, String name, String value) {
this.uuid = UUID.randomUUID().toString();
this.id = owner.getInstanceId();
this.schemaName = owner.getSchemaName();
this.propName = name;
this.value = value;
}
public BeanId getId() {
return BeanId.create(id, schemaName);
}
public String getPropertyName() {
return propName;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
/**
* This property ensure that there will be one row in the
* properties table for every bean.
*
* The purpose of creating a special property for a JpaBean is to
* reduce number of SQL calls and increase database performance.
* With the marker, we can fetch beans directly from the properties
* table immediatly without first consulting the beans table.
*/
public static void markBeanWithProperty(Bean bean) {
bean.addProperty(BEAN_MARKER_PROPERTY_NAME, "");
}
public static void unmarkBeanWithProperty(Bean bean) {
bean.remove(BEAN_MARKER_PROPERTY_NAME);
}
/**
* This property has no other meaning than knowing that a bean
* exits only by looking at the properties table.
*
* So remove the marker property from result of a fetch operation.
*/
public static void filterMarkerProperty(List<JpaProperty> properties) {
ListIterator<JpaProperty> propIt = properties.listIterator();
while (propIt.hasNext()) {
if (BEAN_MARKER_PROPERTY_NAME.equals(propIt.next().getPropertyName())) {
propIt.remove();
}
}
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof JpaProperty)) {
return false;
}
JpaProperty o = (JpaProperty) obj;
return equal(uuid, o.uuid);
}
@Override
public int hashCode() {
return Objects.hashCode(uuid);
}
@Override
public String toString() {
return toStringHelper(JpaProperty.class).add("propertyName", getPropertyName())
.add("value", getValue()).toString();
}
}