All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tentackle.dbms.DbObjectClassVariables Maven / Gradle / Ivy

/*
 * Tentackle - https://tentackle.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.tentackle.dbms;

import org.tentackle.common.FileHelper;
import org.tentackle.dbms.rmi.AbstractDbObjectRemoteDelegate;
import org.tentackle.log.Logger;
import org.tentackle.session.PersistenceException;
import org.tentackle.session.SessionUtilities;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;

/**
 * Holds static class variables for classes derived from AbstractDbObject.
* This is a "singleton per class". * * @param

the persistence type * @author harald */ public class DbObjectClassVariables

> { private static final Logger LOGGER = Logger.get(DbObjectClassVariables.class); // all classes register here: private static final Map>> CLASSMAP = new HashMap<>(); /** * Creates a classvariable for a db object. * * @param clazz is the class of the derived AbstractDbObject * @param classId the unique class id, 0 if abstract * @param tableName is the SQL tablename * @param

the persistence type */ public static

> DbObjectClassVariables

create(Class

clazz, int classId, String tableName) { return DbClassVariablesFactory.getInstance().dbPoCv(clazz, classId, tableName); } /** * Creates a classvariable for a db object.
* Classid and tablename are derived from mapped services in META-INF. * * @param clazz is the class of the derived AbstractDbObject * @param

the persistence type */ public static

> DbObjectClassVariables

create(Class

clazz) { return DbClassVariablesFactory.getInstance().dbPoCv(clazz); } /** * Gets the classvariables for a given tablename. * * @param

the persistence type * @param tableName is the database tablename * @return the classvariables or null if no such tablename */ @SuppressWarnings("unchecked") public static

> DbObjectClassVariables

getVariables(String tableName) { synchronized(CLASSMAP) { return (DbObjectClassVariables

) CLASSMAP.get(tableName); } } /** * Registers a classvariables instance. * * @param

the persistence type * @param variables the classvariables to register */ public static

> void putVariables(DbObjectClassVariables

variables) { if (variables.tableName != null) { synchronized(CLASSMAP) { @SuppressWarnings("unchecked") DbObjectClassVariables

classVar = (DbObjectClassVariables

) CLASSMAP.get(variables.tableName); if (classVar != null) { /* * Using an extended class with the same tablename is okay. * This is because the class hierarchy will register along the extension path. * In all other cases the tablename must be unique. */ if (!classVar.clazz.isAssignableFrom(variables.clazz)) { throw new PersistenceException( "classvariables for '" + variables.tableName + "' already registered, old=" + classVar.clazz.getName() + ", new=" + variables.clazz.getName()); } } else { CLASSMAP.put(variables.tableName, variables); } } } } protected record ForeignReference(Method[] methods, int priority, String methodPath) implements Comparable { /** * Sorted by priority + method. */ @Override public int compareTo(ForeignReference o) { int rv = priority - o.priority; if (rv == 0) { rv = methodPath.compareTo(o.methodPath); } return rv; } } /** * Referencing classes and their isReferencing-methods. */ protected final Set foreignReferences = new TreeSet<>(); /** * the class. */ public final Class

clazz; /** * The unique class id. */ public final int classId; /** * the base-classname. */ public final String classBaseName; /** * database table name. */ public final String tableName; /** * ID for the remote delegate for this class. */ public int remoteDelegateId; /** * Number of columns in a resultset. */ public int columnCount; /** * true if prepared statements should always be prepared (i.e. if * the statement is changing for some reasons, e.g. the tablename). */ public boolean alwaysPrepare; /** * prepared statement ID for select(). */ public final StatementId selectObjectStatementId = new StatementId(); /** * prepared statement ID for selectAll(). */ public final StatementId selectAllObjectsStatementId = new StatementId(); /** * prepared statement ID for selectAllIdSerial(). */ public final StatementId selectAllIdSerialStatementId = new StatementId(); /** * prepared statement ID for selectForUpdate(). */ public final StatementId selectForUpdateStatementId = new StatementId(); /** * prepared statement ID for selectSerial(). */ public final StatementId selectSerialStatementId = new StatementId(); /** * prepared statement ID for selectMaxId(). */ public final StatementId selectMaxIdStatementId = new StatementId(); /** * prepared statement ID for selectMaxTableSerial(). */ public final StatementId selectMaxTableSerialStatementId = new StatementId(); /** * prepared statement ID for insert(). */ public final StatementId insertStatementId = new StatementId(); /** * prepared statement ID for update(). */ public final StatementId updateStatementId = new StatementId(); /** * prepared statement ID for delete(). */ public final StatementId deleteStatementId = new StatementId(); /** * prepared statement ID for dummyUpdate(). */ public final StatementId dummyUpdateStatementId = new StatementId(); /** * prepared statement ID for updateSerial(). */ public final StatementId updateSerialStatementId = new StatementId(); /** * prepared statement ID for updateSerial(long). */ public final StatementId updateAndSetSerialStatementId = new StatementId(); /** * prepared statement ID for updateTableSerial(). */ public final StatementId updateTableSerialStatementId = new StatementId(); /** * prepared statement ID for updateSerialAndTableSerial(). */ public final StatementId updateSerialAndTableSerialStatementId = new StatementId(); /** * prepared statement ID for selectExpiredTableSerials(). */ public final StatementId selectExpiredTableSerials1StatementId = new StatementId(); public final StatementId selectExpiredTableSerials2StatementId = new StatementId(); /** * prepared statement ID for selectObjectsWithExpiredTableSerials */ public final StatementId selectObjectsWithExpiredTableSerialsStatementId = new StatementId(); /** * table serial expiration backlog. */ public final TableSerialExpirationBacklog expirationBacklog; /** * the source for obtaining a new ID. */ public IdSource idSource; /** * class properties. */ private Properties properties; /** * Constructs a class variable.
* Throws IllegalStateException if already constructed. * * @param clazz is the class of the derived AbstractDbObject * @param classId the unique class id, 0 if abstract * @param tableName is the SQL tablename */ public DbObjectClassVariables(Class

clazz, int classId, String tableName) { this.clazz = clazz; this.classId = classId; this.tableName = tableName; classBaseName = clazz.getSimpleName(); expirationBacklog = createBacklog(); // register and check extension hierarchy putVariables(this); } /** * Constructs a class variable.
* Throws IllegalStateException if already constructed. * Classid and tablename are derived from META-INF. * * @param clazz is the class of the derived AbstractDbObject */ public DbObjectClassVariables(Class

clazz) { this(clazz, SessionUtilities.getInstance().determineClassId(clazz), SessionUtilities.getInstance().determineTablename(clazz)); } /** * Creates the table serial backlog. * * @return the backlog */ protected TableSerialExpirationBacklog createBacklog() { return new TableSerialExpirationBacklog(); } /** * Lazily gets the properties.
* Loads and initially processes the properties. * * @return the properties, never null */ public Properties getProperties() { if (properties == null) { // load properties, if any from file or resource String propertiesName = clazz.getName().replace('.', '/'); try { properties = FileHelper.loadProperties(propertiesName); } catch (FileNotFoundException nf) { // missing props is okay and default } catch (IOException io) { LOGGER.severe("could not load class variable properties from " + propertiesName, io); } finally { if (properties == null) { properties = new Properties(); } } } return properties; } /** * Get the IdSource.
* * If the IdSource is null, the method will look for * a property file which is the classname + ".properties". * The property file must contain an idsource-line * similar to that in Db.properties.
* If there is no such file, the source from the db will be used. * * @param db the session * @return the idSource */ public synchronized IdSource getIdSource(Db db) { if (idSource == null) { // setup IdSource once for this class // check property "idsource = ..." String idConfig = getProperty(Db.IDSOURCE); if (idConfig == null) { // no such property: use default id source from Db idSource = db.getDefaultIdSource(); } else { // create configurator and source from property idSource = IdSourceConfigurator.getInstance().configure(db, idConfig); } } return idSource; } /** * Determine the referencing priority. *

* The default implementation returns 0. * Provided to be overridden. * * @param clazz the referencing class * @return the priority */ protected int determineReferencePriority(Class> clazz) { // all objects get the same priority by default return 0; } /** * Adds a PDO class that is referencing the class of this class variable holder. *

* The referencing class must provide a static method isReferencing<ClassBasename>, * while ClassBaseName is the {@link #classBaseName} of the referenced class. * The method must provide the objectId as its only parameter and is usually generated * by the ???IsReferencing-wurblets.
* The method name can also contain dots to denote a chain of methods to invoke. * * @param clazz the class to add * @param methodPath the method path name, null if default "isReferencing<ClassBaseName>" * @return true if added, false if already in map * @see #removeReferencingClass */ public boolean addReferencingClass(Class> clazz, String methodPath) { ForeignReference ref = createForeignReference(clazz, methodPath); synchronized(foreignReferences) { return foreignReferences.add(ref); } } /** * Removes a PDO class that is referencing the class from this class variable holder. * * @param clazz the class to be removed * @param methodPath the method path name, null if default * @return true if removed, false if not in map * @see #addReferencingClass */ public boolean removeReferencingClass(Class> clazz, String methodPath) { ForeignReference ref = createForeignReference(clazz, methodPath); synchronized(foreignReferences) { return foreignReferences.remove(ref); } } private ForeignReference createForeignReference(Class> clazz, String methodPath) { if (methodPath == null) { methodPath = "isReferencing" + classBaseName; } try { StringTokenizer stok = new StringTokenizer(methodPath, "."); List methods = new ArrayList<>(); Class methodClass = clazz; while (stok.hasMoreTokens()) { String methodName = stok.nextToken(); Method method; if (stok.hasMoreTokens()) { // getter method to another object method = methodClass.getMethod(methodName); // no args methodClass = determineImplementingClassOfReturnType(method.getReturnType()); } else { // isReferencingMethod try { method = methodClass.getMethod(methodName, Long.TYPE); } catch (NoSuchMethodException e1) { // try Long instead of long method = methodClass.getMethod(methodName, Long.class); } } int modifiers = method.getModifiers(); if (Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) { throw new PersistenceException(methodClass.getName() + "." + method.getName() + " must be public and non-static"); } methods.add(method); } return new ForeignReference(methods.toArray(new Method[0]), determineReferencePriority(clazz), methodPath); } catch (NoSuchMethodException ex) { throw new PersistenceException("please add an *IsReferencing-wurblet to generate " + clazz.getName() + "." + methodPath, ex); } } /** * Determines the implementing class for a type returned by a method. * * @param clazz the method's returned class * @return the implementing class */ protected Class determineImplementingClassOfReturnType(Class clazz) { return clazz; // same in low-level persistence layer } /** * Determines whether the class of this variable holder is referenced. * * @param db the session * @param id the object id to test whether it is referenced by any other objects * @return true if referenced */ public boolean isReferenced(Db db, long id) { return isReferencedImpl(db, id, foreignReferences); } /** * Determines whether the class of this variable holder is referenced. * * @param db the session * @param id the object id to test whether it is referenced by any other objects * @param foreignReferences the foreign references * @return true if referenced */ protected boolean isReferencedImpl(Db db, long id, Set foreignReferences) { for (ForeignReference ref : foreignReferences) { try { @SuppressWarnings("unchecked") Object obj = AbstractDbObject.newInstance(db, (Class>) ref.methods[0].getDeclaringClass()); for (int methodIndex = 0; methodIndex < ref.methods.length; methodIndex++) { if (methodIndex < ref.methods.length - 1) { obj = ref.methods[methodIndex].invoke(obj); } else if (Boolean.TRUE.equals(ref.methods[methodIndex].invoke(obj, id))) { return true; } } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new PersistenceException("couldn't invoke " + ref.methodPath, ex); } } return false; } /** * Gets the delegateId of the class, i.e. subclass of AbstractDbObject.
* If the remoteDelegateId is 0, it will be prepared * The delegateId is unique for each class. It is valid only in remote connections and * is the same for all remote sessions. * The RMI-server creates a delegate for each subclass of AbstractDbObject (DbObjectRemoteDelegateImpl resp.) * * @return the delegate id */ public int getRemoteDelegateId() { if (remoteDelegateId == 0) { remoteDelegateId = Db.prepareRemoteDelegate(clazz); } return remoteDelegateId; } /** * Gets the RemoteDelegate for the class and session. * * @param db the session * @return the delegate */ @SuppressWarnings("unchecked") public AbstractDbObjectRemoteDelegate

getRemoteDelegate(Db db) { return (AbstractDbObjectRemoteDelegate

) db.getRemoteDelegate(getRemoteDelegateId()); } /** * Gets a class property. * * @param key the property key * @return the property value */ public String getProperty(String key) { return getProperties().getProperty(key); } /** * Sets a class property. * * @param key the property key * @param value the property value * @return the old property value, null if new */ public String putProperty(String key, String value) { return (String) getProperties().put(key, value); } @Override public String toString() { StringBuilder buf = new StringBuilder(clazz.getName()); if (tableName != null) { buf.append("/'").append(tableName).append("'"); } return buf.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy