package com.tesora.dve.sql.schema;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.tesora.dve.common.catalog.CatalogEntity;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.expression.Traversable;
import com.tesora.dve.sql.schema.cache.Cacheable;
import com.tesora.dve.sql.schema.cache.PersistentCacheKey;
import com.tesora.dve.sql.schema.cache.SchemaCacheKey;
import com.tesora.dve.sql.schema.validate.ValidateResult;
import com.tesora.dve.sql.transform.CopyContext;
import com.tesora.dve.sql.util.Accessor;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.ListSet;
@SuppressWarnings({ "rawtypes", "unused" })
public abstract class Persistable<Transient, Persistent> extends Traversable implements Cacheable {
protected Name name;
protected Integer persistentID;
protected final SchemaCacheKey<?> sck;
public Persistable(SchemaCacheKey<?> sck) {
this.sck = sck;
}
protected abstract Class<? extends CatalogEntity> getPersistentClass();
protected abstract int getID(Persistent p);
protected PersistentCacheKey getPersistentCacheKey(int id) {
return new PersistentCacheKey(getPersistentClass(), id);
}
public PersistentCacheKey getPersistentCacheKey() {
if (persistentID == null) return null;
return new PersistentCacheKey(getPersistentClass(), persistentID.intValue());
}
@Override
public SchemaCacheKey<?> getCacheKey() {
return sck;
}
protected abstract Persistent lookup(SchemaContext sc) throws PEException;
protected abstract Persistent createEmptyNew(SchemaContext sc) throws PEException;
protected abstract void populateNew(SchemaContext sc, Persistent p) throws PEException;
protected abstract Persistable<Transient, Persistent> load(SchemaContext sc, Persistent p) throws PEException;
protected void updateExisting(SchemaContext sc, Persistent p) throws PEException {
}
protected boolean updating = false;
protected void update(SchemaContext sc, Persistent p) throws PEException {
if (persistentID == null) return;
if (updating)
return;
try {
updating = true;
updateExisting(sc,p);
} finally {
updating = false;
}
}
protected boolean isTemporary() {
return false;
}
public Persistent persistTree(SchemaContext sc) throws PEException {
return persistTree(sc,false);
}
@SuppressWarnings("unchecked")
protected Persistent lookupForRefresh(SchemaContext sc, int id) throws PEException {
PersistentCacheKey pck = getPersistentCacheKey(id);
if (pck == null) return null;
return (Persistent)sc.getCatalog().findByKey(pck);
}
@SuppressWarnings({ "unchecked" })
public Persistent persistTree(SchemaContext sc, boolean forRefresh) throws PEException {
Persistent already = getPersistent(sc,false);
if (already == null) {
if (forRefresh) {
if (persistentID != null) {
already = lookupForRefresh(sc, persistentID);
if (already != null) setPersistent(sc,already, getID(already));
}
} else {
already = lookup(sc);
if (already != null) {
Persistable t = load(sc,already);
if (t != null)
verifySame(sc,t);
setPersistent(sc,already,getID(already));
update(sc,already);
}
}
if (already == null) {
if (!sc.isMutableSource()) {
throw new IllegalStateException("Creating an instance of type " + getPersistentClass().getName() + " on a nonmutable source");
}
already = createEmptyNew(sc);
setPersistent(sc,already, null);
populateNew(sc,already);
}
} else {
update(sc,already);
}
if (!isTemporary() && already instanceof CatalogEntity)
sc.getSaveContext().add(this,(CatalogEntity)already);
return already;
}
public Persistent getPersistent(SchemaContext pc) {
return getPersistent(pc, true);
}
// get the persistent version, or null if none.
@SuppressWarnings("unchecked")
public Persistent getPersistent(SchemaContext pc, boolean create) {
Persistent p = (Persistent)pc.getBacking(this);
if (p instanceof CatalogEntity && persistentID != null) {
CatalogEntity ce = (CatalogEntity) p;
if (ce.getId() != persistentID.intValue())
throw new SchemaException(Pass.PLANNER, "wrong persistent rep. have " + persistentID + "@" + System.identityHashCode(this) + " but found " + ce.getId() + "@" + System.identityHashCode(ce));
}
if (!create)
return p;
if (p == null) {
pc.beginSaveContext();
try {
persistTree(pc,true);
} catch (PEException pe) {
pe.printStackTrace();
throw new SchemaException(Pass.SECOND,"Persistent refresh failed",pe);
} finally {
pc.endSaveContext();
}
return (Persistent)pc.getBacking(this);
}
return p;
}
protected void setPersistent(SchemaContext sc, Persistent p, Integer pid) {
if (p == null) return;
persistentID = pid;
if (sc != null) sc.setBacking(this, (Serializable)p);
}
public Persistable<Transient, Persistent> reload(SchemaContext toContext) throws PEException {
throw new PEException("Cannot reload " + this.getClass().getSimpleName());
}
public Name getName() {
return name;
}
public void setName(Name n) {
name = n;
}
@SuppressWarnings("unchecked")
public Transient get() {
return (Transient)this;
}
public static final Accessor<Name, Persistable> getName = new Accessor<Name, Persistable>() {
@Override
public Name evaluate(Persistable object) {
return object.getName();
}
};
// the differs api, for checking equivalence between two schema objects which may be created different ways
public String differs(SchemaContext sc, Persistable<Transient, Persistent> other, boolean first) {
ArrayList<String> diffs = new ArrayList<String>();
Set<Persistable> visited = new HashSet<Persistable>();
collectDifferences(sc, diffs, other, first, visited);
if (diffs.isEmpty())
return null;
StringBuilder buf = new StringBuilder();
Functional.join(diffs, buf, "; ");
return buf.toString();
}
// return true if any differences were found.
public boolean collectDifferences(SchemaContext sc, List<String> messages, Persistable<Transient, Persistent> other, boolean first, Set<Persistable> visited) {
return false;
}
protected abstract String getDiffTag();
@SuppressWarnings({ "unchecked" })
protected boolean maybeBuildDiffMessage(SchemaContext sc, List<String> messages, String what, Object current, Object other, boolean first, Set<Persistable> visited) {
if (current == null && other == null)
return false;
if (current != null && other == null) {
messages.add(getDiffTag() + " is missing " + what + ". Did not find " + current);
if (first)
return true;
}
if (current == null && other != null) {
messages.add(getDiffTag() + " has extra " + what + ". Found extra " + other);
if (first)
return true;
}
if (current instanceof Persistable && other instanceof Persistable)
return ((Persistable)current).collectDifferences(sc, messages, (Persistable) other, first, visited);
if (!current.equals(other)) {
messages.add(getDiffTag() + " has different " + what + ". Expected: " + current + ", but found: " + other);
if (first)
return true;
}
return false;
}
@SuppressWarnings({ "unchecked" })
protected boolean maybeBuildDiffMessage(SchemaContext sc, List<String> messages, String what,
Map<Name, ? extends Persistable<?,?>> current,
Map<Name, ? extends Persistable<?,?>> other,
boolean first, Set<Persistable> visited) {
if (current.size() != other.size()) {
messages.add(getDiffTag() + " has different numbers of " + what + "s. Expected: " + current.size() + ", but found: " + other.size());
if (first)
return true;
}
Map matching = new HashMap();
for(Name n : current.keySet())
matching.put(current.get(n), other.get(n));
// purposefully misses items new in other - skipping for now
for(Iterator iter = matching.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry me = (Map.Entry)iter.next();
Persistable l = (Persistable)me.getKey();
Persistable r = (Persistable)me.getValue();
if (r == null) {
messages.add(what + " with name " + l.getName().getSQL() + " is missing.");
if (first)
return true;
} else if (l.collectDifferences(sc, messages, r, first, visited)) {
return true;
}
}
return false;
}
public void verifySame(SchemaContext sc, Persistable<Transient, Persistent> other) throws PEException {
String diffs = differs(sc, other, true);
if (diffs != null)
throw new PEException(diffs);
}
public Integer getPersistentID() {
return persistentID;
}
// make sure we never make a copy without knowing the schema context
@Override
public final Traversable copy(CopyContext cc) {
throw new IllegalArgumentException("No copy support for " + this.getClass().getName());
}
public Traversable copy(SchemaContext sc, CopyContext cc) {
throw new IllegalArgumentException("No copy support for " + this.getClass().getName());
}
// the validation api - checks self consistency
public void checkValid(SchemaContext sc, List<ValidateResult> acc) {
}
public List<ValidateResult> validate(SchemaContext sc, boolean complain) {
ArrayList<ValidateResult> out = new ArrayList<ValidateResult>();
checkValid(sc,out);
if (complain) {
String message = ValidateResult.buildMessage(sc, out, true);
throw new SchemaException(Pass.NORMALIZE, message);
}
return out;
}
@Override
public boolean equals(Object o) {
if (sck == null) return super.equals(o);
if (o instanceof Persistable) {
Persistable<?,?> op = (Persistable<?, ?>) o;
if (op.sck == null) return super.equals(o);
return sck.equals(op.sck);
}
return super.equals(o);
}
@Override
public int hashCode() {
if (sck == null) return super.hashCode();
return sck.hashCode();
}
}