de.tsl2.nano.replication.EntityReplication Maven / Gradle / Ivy
package de.tsl2.nano.replication;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import de.tsl2.nano.replication.serializer.SerializeBytes;
import de.tsl2.nano.replication.serializer.SerializeXML;
import de.tsl2.nano.replication.serializer.Serializer;
import de.tsl2.nano.replication.util.H2Util;
import de.tsl2.nano.replication.util.ULog;
import de.tsl2.nano.replication.util.Util;
/**
* Replicates entities through hibernatesession.replicate(). EntityManagers will be load through their persistence unit names.
* To use it,
* - create a META-INF/persistence.xml in your classpath
* - describe the two persistence-units (source and destination)
* - call: new EntityReplication(mySourceUnit, myDestUnit).replicate(new HibReplication(myDestUnit)::strategyHibReplicate, myBeans)
* or through serlialization:
* - EntityReplication.setPersistableID(e -> (IPersistable)e).getId)
* - new EntityReplication().replicate(myBeans)
* - ...
* - new EntityReplication().load(myID, myBeanType.class)
*
* @author Tom, Thomas Schneider
* @version $Revision$
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class EntityReplication {
public static final String CONFIG_DIR = "REPL-INF/";
private static final String PERS_JNDI = "JNDI";
static final String DEFAULT_PERSISTENCE_XML = "META-INF/persistence.xml";
private EntityManager src;
private EntityManager dest;
AtomicReference tmp = new AtomicReference();
private static String persistenceXmlPath = DEFAULT_PERSISTENCE_XML;
private static Function persistableID;
private JndiLookup.FindByIdAccess jndiEJBSession;
public EntityReplication(String srcPersistenceUnit, String destPersistenceUnit) {
if (srcPersistenceUnit.equals(PERS_JNDI))
jndiEJBSession = JndiLookup.createSessionBeanFromJndi();
if (!isKeywordOrNull(srcPersistenceUnit)) {
src = createEntityManager(srcPersistenceUnit, Boolean.getBoolean("entityreplication.load.src.extrathread"));
}
if (!isKeywordOrNull(destPersistenceUnit)) {
dest = createEntityManager(destPersistenceUnit, false);
}
Util.assert_(src != null || dest != null, "at least one persistence-unit-name must be given!");
}
private ClassLoader definePersitenceXmlPath() {
if (System.getProperty("persistencexml.path") != null)
persistenceXmlPath = System.getProperty("persistencexml.path");
if (persistenceXmlPath != null) {
ClassLoader orgin = Util.linkResourcePath(DEFAULT_PERSISTENCE_XML, persistenceXmlPath);
return orgin;
}
return null;
}
protected EntityManager createEntityManager(String punit, boolean threadScope) {
long start = System.currentTimeMillis();
if (threadScope) {
ULog.log("creating EntityManager for '" + punit + "' in new thread scope...", false);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
ClassLoader cl = definePersitenceXmlPath();
Properties pers = new Properties();
// pers.setProperty("hibernate.current_session_context_class", "thread");
// pers.setProperty("hibernate.connection.pool_size", "10");
tmp = createEntityManager(punit, cl, pers);
}
});
thread.start();
try {
thread.join();
} catch (Exception e) {
log("ERROR on creating entitymanager: ", e);
throw new IllegalStateException(e);
}
} else {
createEntityManager(punit, null, new Properties());
}
ULog.log((System.currentTimeMillis() - start) + " ms");
return tmp.get();
}
private AtomicReference createEntityManager(String punit, ClassLoader cl, Properties pers) {
ULog.log("creating EntityManager for '" + punit + "...", false);
EntityManagerFactory entityManagerFactory = null;
try {
entityManagerFactory = Persistence.createEntityManagerFactory(punit);
tmp.set(entityManagerFactory.createEntityManager(pers));
if (cl != null)
Thread.currentThread().setContextClassLoader(cl);
return tmp;
} finally {
//seams to close the entitymanagers, too
// if (entityManagerFactory != null)
// entityManagerFactory.close();
}
}
public EntityReplication(EntityManager src, EntityManager dest) {
this.src = src;
this.dest = dest;
}
public EntityReplication() {
}
protected boolean isKeywordOrNull(String name) {
return name == null || Serializer.getByKey(name) != null || name.equals(PERS_JNDI);
}
public void replicateFromIDs(Class entityClass, Serializer ser, Object...ids) {
T[] entities = fromIDs(entityClass, ids);
replicate((Consumer)null, s -> strategySerialize(s, ser), entities);
}
protected T[] fromIDs(Class entityClass, Object... ids) {
EntityManager em = src != null ? src : dest;
Util.assert_(em != null, "to find entities through their ids, source or destination entitymanager must not be null!");
long start = System.currentTimeMillis();
ULog.log("loading entities of type " + entityClass.getName() + " for " + ids.length + " ids");
ids = getWellTypedIds(em, entityClass, ids);
T[] entities = Arrays.stream(ids).
map(id -> src.find(entityClass, id, readOnlyHints())).filter(e -> e != null).
toArray(s -> (T[])java.lang.reflect.Array.newInstance(entityClass, s));
if (ids.length > entities.length) {
if (entities.length == 0)
throw new IllegalStateException(em + " could not find any entity for ids: " + Arrays.toString(ids));
else
ULog.log("WARNING: entitymanager " + em + " couldn't find all ids (" + ids.length + ")! found only: " + entities.length);
}
ULog.log("loading entities finshed " + (System.currentTimeMillis() - start) + " ms");
return entities;
}
private Object[] getWellTypedIds(EntityManager em, Class> entityClass, Object[] ids) {
Class> idType = em.getMetamodel().entity(entityClass).getIdType().getJavaType();
if (idType != null && !String.class.isAssignableFrom(idType)) {
Object[] ids_ = new Object[ids.length];
for (int i = 0; i < ids.length; i++) {
ids_[i] = Util.decode(idType, ids[i]);
}
return ids_;
}
return ids;
}
protected T[] fromIDs(JndiLookup.FindByIdAccess ejbSession, Class entityClass, Object... ids) {
long start = System.currentTimeMillis();
ULog.log("loading entities of type " + entityClass.getName() + " for " + ids.length + " ids");
T[] entities = Arrays.stream(ids).
map(id -> ejbSession.call(entityClass, (Serializable)id)).filter(o -> o != null).
toArray(s -> (T[])java.lang.reflect.Array.newInstance(entityClass, s));
if (ids.length > entities.length)
ULog.log("WARNING: not all ids (" + ids.length + ") were found: " + entities.length);
ULog.log("loading entities finshed " + (System.currentTimeMillis() - start) + " ms");
return entities;
}
public void replicate(T...entities) {
replicate(EntityReplication::strategySerializeXML, entities);
}
public void replicate(Consumer strategy, T...entities) {
replicate((Consumer)null, strategy, entities);
}
public void replicate(Consumer transformer, Consumer strategy, T...entities) {
long start = System.currentTimeMillis();
Integer transactionBlock = Util.getProperty("transaction.block", Integer.class);
log("replicating with transformer " + transformer + ", strategy " + strategy + " on " + entities.length + " entities");
if (transactionBlock == null)
beginTransation();
int i[] = {0}, f[] = {0};
Arrays.stream(entities).forEach(e -> {
try {
if (transformer != null)
transformer.accept(e);
if (strategy != null) {
boolean doTransaction = transactionBlock != null && (i[0] %transactionBlock) == 0;
if (doTransaction)
beginTransation();
ULog.log("\b" + i[0], false);
strategy.accept(e);
if (doTransaction)
commitTransaction();
i[0]++;
} else {
ULog.log("WARN: no strategy defined --> nothing to do");
}
} catch (Exception ex) {
f[0]++;
if (dest != null && dest.getTransaction().isActive())
dest.getTransaction().rollback();
Util.handleException(ex);
}
});
if (transactionBlock == null) {
commitTransaction();
ULog.log("");
}
log("replication finished " + (f[0] == 0 ? "successfull" : "with " + f[0] + " errors") + "! " + "(Entities: " + i[0] + ", "+ (System.currentTimeMillis() - start) + " ms)");
}
private void beginTransation() {
if (dest != null) {
ULog.log("--> begin transaction...", false);
dest.getTransaction().begin();
}
}
private void commitTransaction() {
if (dest != null) {
ULog.log("--> commit transaction...", false);
H2Util.disableReferentialIntegrity(dest);
dest.flush();
dest.getTransaction().commit();
}
}
public static void strategySerializeXML(Object entity) {
strategySerialize(entity, Serializer.getByKey(SerializeXML.KEY));
}
public static void strategySerialize(Object entity, Serializer serializer) {
File file = getFile(entity.getClass(), getID(entity), serializer.getExtension());
log("serializing (" + serializer.getKey() + ") " + entity.getClass() + " to " + file);
try {
Files.write(Paths.get(file.getAbsolutePath()), serializer.serialize(entity).toByteArray());
} catch (IOException e) {
Util.handleException(e);
}
}
public void strategyPersist(Object entity) {
log("persisting " + entity.getClass() + " to " + dest);
dest.merge(entity);
}
public static void setPersistenceXmlPath(String persistenceXmlPath) {
EntityReplication.persistenceXmlPath = persistenceXmlPath;
}
public static void setPersistableID(Function persistableID) {
log("setting persistableID=" + persistableID);
EntityReplication.persistableID = persistableID;
}
private static Object getID(Object entity) {
Object id;
if (persistableID != null) {
id = persistableID.apply(entity);
} else {
String idMethod = Util.getProperty("peristableid.access", "getId", "");
try {
id = entity.getClass().getMethod(idMethod, new Class[0]).invoke(entity);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalArgumentException("ERROR: Please define a peristableid either through calling setPersistableID() or system-property 'peristableid.access' (default: getId <- reflaction!)", e);
}
}
log("handling " + entity.getClass() + ":" + id);
return id;
}
public static List load(Class entityClass, Serializer serializer) {
ArrayList entities = new ArrayList();
String type = entityClass.getSimpleName().toLowerCase();
Arrays.stream(new File(".").listFiles()).forEach(f -> {
if (f.isFile() && f.getName().startsWith(type) && f.getName().endsWith(serializer.getExtension())) {
entities.add(load(f, entityClass, serializer));
}
});
return entities;
}
public static T load(File file, Class entityClass, Serializer serializer) {
try (InputStream in = Files.newInputStream(Paths.get(file.getPath()))) {
return serializer.deserialize(in, entityClass);
} catch (IOException | ClassNotFoundException e) {
Util.handleException(e);
return null;
}
}
private static File getFile(Class> entityClass, Object id, String extension) {
return new File(entityClass.getSimpleName().toLowerCase() + (id != null ? "-" + id : "") + "." + extension);
}
private Map readOnlyHints() {
HashMap hints = new HashMap<>();
hints.put("org.hibernate.readOnly", true);
log("setting entity-manager hints: " + hints);
return hints;
}
public static void checkContent(String srcPersistenceUnit, String destPersistenceUnit, Class> entityClass, Object...ids) throws IOException {
EntityReplication repl = new EntityReplication(srcPersistenceUnit, destPersistenceUnit);
SerializeBytes s = (SerializeBytes) Serializer.getByKey(SerializeBytes.KEY);
Object srcObj, dstObj;
for (int i = 0; i < ids.length; i++) {
srcObj = repl.src.find(entityClass, ids[i]);
dstObj = repl.dest.find(entityClass, ids[i]);
Util.assert_(srcObj != null, ids[i] + " not found in " + repl.src);
Util.assert_(dstObj != null, ids[i] + " not found in " + repl.dest);
Util.assert_(Arrays.equals(s.serialize(srcObj).toByteArray(), s.serialize(dstObj).toByteArray()), "objects for " + ids[i] + " differ between both persistences!");
}
}
private static void log(String txt, Object...args) {
ULog.log(EntityReplication.class.getSimpleName() + ": " + txt, true, args);
}
public static void main(String[] args) throws ClassNotFoundException {
log("===============================================================================");
Util.printLogo("repl-logo.txt");
final int MINARGS = 3;
Properties props = Util.loadPropertiesToSystem(CONFIG_DIR + EntityReplication.class.getSimpleName().toLowerCase() + ".properties");
args = Util.mergeArgsAndProps(args, args.length, props);
if (args.length < MINARGS || args[0].matches("[-/]+[?h].*")) {
log("usage : {|XML|JAXB|BYTES|YAML|JSON|SIMPLE_XML} {||XML|JAXB|BYTES|YAML|JSON|SIMPLE_XML} {} {, ...}" );
log(" e.g. : mypersistentunit1 XML my.pack.MyClass 1 2 3");
log(" e.g. : XML mypersistentunit2 my.pack.MyClass 1");
log(" e.g. : mypersistentunit1 mypersistentunit2 my.pack.MyClass 1 2 3");
log(" e.g. : -Dperistableid.access=getMyID mypersistentunit1 XML my.pack.MyClass 1 2 3");
log(" e.g. : -Dpersistencexml.path=REPL-INF/persistence.xml mypersistentunit1 xml my.pack.MyClass 1 2 3");
log(" e.g. : -Duse.hibernate.replication=true -Djndi.prefix=ejb:/myapp/ -Djndi.sessionbean=MySessionBean -Djndi.sessioninterface=MySessionInterface -Djndi.find.method=myFindByID JNDI mypersistentunit2 my.pack.MyClass 1 2 3");
log(" e.g. : -Dreplication.transformer=de.tsl2.nano.replication.util.SimpleTransformer -Dreplication.transform.regex=my-class+field-find-regex ...");
log("you can provide properties through system call or through file 'REPL-INF/entityreplication.properties");
log("the properties may contain the main args instead: syntax: args0={||XML|JAXB|BYTES}, args1={||XML|JAXB|BYTES}, args2={}, args3={, ...}");
return;
}
String pers1 = args[0];
String pers2 = args[1];
String call;
EntityReplication repl = null;
try {
if ((call = Util.getProperty("on.start.call", null, null)) != null)
Util.invoke(call);
repl = new EntityReplication(pers1, pers2);
Class cls = Thread.currentThread().getContextClassLoader().loadClass(args[2]);
boolean useHibernateReplication = Util.getProperty("use.hibernate.replication", Boolean.class);
Util.assert_(pers2 != PERS_JNDI, "persistence-unit-2 must not be " + PERS_JNDI);
Object[] ids = Arrays.copyOfRange(args, 3, args.length);
if (ids.length == 1 && ids[0].getClass().equals(String.class))
ids = ((String)ids[0]).split("[,;| ]");
log("starting replication with:"
+ "\n\tpersistence-unit-1: " + pers1
+ "\n\tpersistence-unit-2: " + pers2
+ "\n\tentity-class : " + cls
+ "\n\tentity-ids : " + Arrays.toString(ids));
Consumer transformer = evalTransformer();
Serializer ser;
if ((ser = Serializer.getByKey(pers1)) != null) {
repl.replicate(transformer, repl::strategyPersist, load(cls, ser).toArray());
} else {
if ((ser = Serializer.getByKey(pers2)) != null) {
repl.replicateFromIDs(cls, ser, ids);
} else if (pers1.equals(PERS_JNDI)){
if (useHibernateReplication)
repl.replicate(transformer, (Consumer)new HibReplication<>(repl.dest)::strategyHibReplicate, repl.fromIDs(repl.jndiEJBSession, cls, ids));
else
repl.replicate(transformer, (Consumer)repl::strategyPersist, repl.fromIDs(repl.jndiEJBSession, cls, ids));
} else {
repl.replicate((Consumer)transformer, (Consumer)repl::strategyPersist, repl.fromIDs(cls, ids));
}
}
} catch (Throwable e) {
ULog.log("STOPPED WITH ERROR: " + Util.toString(e));
throw new RuntimeException(e);
} finally {
if (repl != null) {
if (repl.src != null && repl.src.isOpen())
repl.src.close();
if (repl.dest != null && repl.dest.isOpen())
repl.dest.close();
}
ULog.log("\nconsumed properties: " + Util.getConsumedProperties());
//TODO: store consumed properties
log("===============================================================================");
if (Util.getProperty("wait.on.finish", Boolean.class)) {
if (System.console() != null)
System.console().readLine("Please press ENTER to shutdown Java VM: ");
}
}
}
private static Consumer> evalTransformer() {
String cls = System.getProperty("replication.transformer");
if (cls != null) {
try {
Class> clazz = Thread.currentThread().getContextClassLoader().loadClass(cls);
log("using transformer: " + clazz);
return (Consumer>) clazz.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new RuntimeException();
}
}
return null;
}
static final void log(Object o) {
System.out.println(o);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy