You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2020/07/09 14:25:52 UTC

[isis] branch master updated: ISIS-2340: smoketests: monkey patch for EnhancementHelper

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new f647719  ISIS-2340: smoketests: monkey patch for EnhancementHelper
f647719 is described below

commit f6477199d58748cb734e4c91d4dab2ff40ef3a6a
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Jul 9 16:25:03 2020 +0200

    ISIS-2340: smoketests: monkey patch for EnhancementHelper
    
    fixes glitches with consecutive test runs
---
 .../isis/testdomain/jdo/entities/JdoBook.java      |   2 +-
 .../datanucleus/enhancer/EnhancementHelper.java    | 308 +++++++++++++++++++++
 2 files changed, 309 insertions(+), 1 deletion(-)

diff --git a/examples/smoketests/stable/src/main/java/org/apache/isis/testdomain/jdo/entities/JdoBook.java b/examples/smoketests/stable/src/main/java/org/apache/isis/testdomain/jdo/entities/JdoBook.java
index 533c94f..d6681dc 100644
--- a/examples/smoketests/stable/src/main/java/org/apache/isis/testdomain/jdo/entities/JdoBook.java
+++ b/examples/smoketests/stable/src/main/java/org/apache/isis/testdomain/jdo/entities/JdoBook.java
@@ -47,7 +47,7 @@ import lombok.extern.log4j.Log4j2;
         objectType = "testdomain.jdo.Book",
         publishing=Publishing.ENABLED, 
         auditing = Auditing.ENABLED)
-@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@NoArgsConstructor(access = AccessLevel.PUBLIC)
 @ToString(callSuper = true)
 @Log4j2
 public class JdoBook extends JdoProduct {
diff --git a/examples/smoketests/stable/src/test/java/org/datanucleus/enhancer/EnhancementHelper.java b/examples/smoketests/stable/src/test/java/org/datanucleus/enhancer/EnhancementHelper.java
new file mode 100644
index 0000000..2f521ee
--- /dev/null
+++ b/examples/smoketests/stable/src/test/java/org/datanucleus/enhancer/EnhancementHelper.java
@@ -0,0 +1,308 @@
+/**********************************************************************
+Copyright (c) 2014 Andy Jefferson and others. All rights reserved.
+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.
+
+Contributors:
+    ...
+**********************************************************************/
+package org.datanucleus.enhancer;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventListener;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.datanucleus.enhancement.Persistable;
+import org.datanucleus.enhancement.StateManager;
+import org.datanucleus.exceptions.NucleusUserException;
+
+/**
+ * Helper class for the DN bytecode enhancement contract. 
+ * It contains methods to register metadata for persistable classes and to perform common operations needed by implementations, not by end users.
+ * <P>
+ * It allows construction of instances of persistable classes without using reflection.
+ * <P>
+ * Persistable classes register themselves via a static method at class load time. There is no security restriction on this access.
+ * 
+ * TODO Provide a mechanism to automatically deregister classes when their class loader exits? or register against the NucleusContext?
+ */
+public class EnhancementHelper extends java.lang.Object
+{
+    private static EnhancementHelper singletonHelper = new EnhancementHelper();
+
+    /**
+     * Static mapping of <code>Persistable</code> class to an instance of that class.
+     * New entries are added by the static method in each <code>Persistable</code> class initialisation. Entries are never removed.
+     */
+    private static Map<Class, Meta> registeredClasses = new ConcurrentHashMap<>();
+
+    /**
+     * This list contains the registered listeners for <code>RegisterClassEvent</code>s.
+     */
+    private static final List<RegisterClassListener> listeners = new ArrayList<RegisterClassListener>();
+
+    private EnhancementHelper()
+    {
+    }
+
+    public static EnhancementHelper getInstance()/* throws SecurityException*/
+    {
+        /*SecurityManager sec = System.getSecurityManager();
+        if (sec != null)
+        {
+            // throws exception if caller is not authorized
+            sec.checkPermission(JDOPermission.GET_METADATA);
+        }*/
+        return singletonHelper;
+    }
+
+    /**
+     * Create a new instance of the class and assign its StateManager.
+     * @see Persistable#dnNewInstance(StateManager sm)
+     * @param pcClass the <code>Persistable</code> class.
+     * @param sm the <code>StateManager</code> which will own the new instance.
+     * @return the new instance, or <code>null</code> if the class is not registered.
+     */
+    public Persistable newInstance(Class pcClass, StateManager sm)
+    {
+        Persistable pc = getPersistableForClass(pcClass);
+        return pc == null ? null : pc.dnNewInstance(sm);
+    }
+
+    /**
+     * Create a new instance of the class and assign its StateManager and key values from the ObjectId. If the oid parameter is <code>null</code>, no key values are copied. 
+     * The new instance has its <code>dnFlags</code> set to <code>LOAD_REQUIRED</code>.
+     * @see Persistable#dnNewInstance(StateManager sm, Object oid)
+     * @param pcClass the <code>Persistable</code> class.
+     * @param sm the <code>StateManager</code> which will own the new instance.
+     * @return the new instance, or <code>null</code> if the class is not registered.
+     * @param oid the ObjectId instance from which to copy key field values.
+     */
+    public Persistable newInstance(Class pcClass, StateManager sm, Object oid)
+    {
+        Persistable pc = getPersistableForClass(pcClass);
+        return pc == null ? null : pc.dnNewInstance(sm, oid);
+    }
+
+    /**
+     * Create a new instance of the ObjectId class of this <code>Persistable</code> class. 
+     * It is intended only for application identity. 
+     * This method should not be called for classes that use single field identity; newObjectIdInstance(Class, Object) should be used instead.
+     * If the class has been enhanced for datastore identity, or if the class is abstract, null is returned.
+     * @param pcClass the <code>Persistable</code> class.
+     * @return the new ObjectId instance, or <code>null</code> if the class is not registered.
+     */
+    public Object newObjectIdInstance(Class pcClass)
+    {
+        Persistable pc = getPersistableForClass(pcClass);
+        return pc == null ? null : pc.dnNewObjectIdInstance();
+    }
+
+    /**
+     * Create a new instance of the class used by the parameter Class for JDO identity, using the key constructor of the object id class. 
+     * It is intended for single field identity. 
+     * The identity instance returned has no relationship with the values of the primary key fields of the persistence-capable instance on which the method is called. 
+     * If the key is the wrong class for the object id class, null is returned.
+     * <P>
+     * For classes that use single field identity, if the parameter is of one of the following types, the behavior must be as specified:
+     * <ul>
+     * <li><code>Number</code> or <code>Character</code>: the parameter must be the single field type or the
+     * wrapper class of the primitive field type; the parameter is passed to the single field identity constructor</li>
+     * <li><code>ObjectIdFieldSupplier</code>: the field value is fetched from the
+     * <code>ObjectIdFieldSupplier</code> and passed to the single field identity constructor</li>
+     * <li><code>String</code>: the String is passed to the single field identity constructor</li>
+     * </ul>
+     * @return the new ObjectId instance, or <code>null</code> if the class is not registered.
+     * @param obj the <code>Object</code> form of the object id
+     * @param pcClass the <code>Persistable</code> class.
+     */
+    public Object newObjectIdInstance(Class pcClass, Object obj)
+    {
+        Persistable pc = getPersistableForClass(pcClass);
+        return (pc == null) ? null : pc.dnNewObjectIdInstance(obj);
+    }
+
+    public static interface RegisterClassListener extends EventListener
+    {
+        /**
+         * This method gets called when a Persistable class is registered.
+         * @param registeredClass The class that has just been initialised/registered
+         */
+        public void registerClass(Class registeredClass);
+    }
+
+    /**
+     * Register metadata by class.
+     * This is called by the enhanced constructor of the <code>Persistable</code> class.
+     * @param pcClass the <code>Persistable</code> class used as the key for lookup.
+     * @param pc an instance of the <code>Persistable</code> class. TODO We can just do pcClass.newInstance() to get one!
+     */
+    public static void registerClass(Class pcClass, Persistable pc)
+    {
+        if (pcClass == null)
+        {
+            throw new NullPointerException("Attempt to register class with null class type");
+        }
+
+        registeredClasses.put(pcClass, new Meta(pc));
+
+        // Notify all listeners
+        synchronized (listeners)
+        {
+            if (!listeners.isEmpty())
+            {
+                for (Iterator i = listeners.iterator(); i.hasNext();)
+                {
+                    RegisterClassListener crl = (RegisterClassListener) i.next();
+                    if (crl != null)
+                    {
+                        crl.registerClass(pcClass);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Unregister all classes for the specified class loader.
+     * @param cl ClassLoader
+     */
+    public void unregisterClasses (ClassLoader cl)
+    {
+        synchronized(registeredClasses) 
+        {
+            for (Iterator i = registeredClasses.keySet().iterator(); i.hasNext();)
+            {
+                Class pcClass = (Class)i.next();
+                // Note, the pc class was registered by calling the static method EnhancementHelper.registerClass. 
+                // This means the EnhancementHelper class loader is the same as or an ancestor of the class loader of the pc class.
+                if ((pcClass != null) && (pcClass.getClassLoader() == cl)) 
+                {
+                    // unregister pc class, if its class loader is the specified one.
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Add the specified <code>RegisterClassListener</code> to the listener list.
+     * @param crl the listener to be added
+     */
+    public void addRegisterClassListener(RegisterClassListener crl)
+    {
+        Set<Class> alreadyRegisteredClasses = null;
+        synchronized (listeners)
+        {
+            listeners.add(crl);
+
+            // Make a copy of the existing set of registered classes.
+            // Between these two lines of code, any number of new class registrations might occur, and will then all wait until this synchronized block completes. 
+            // Some of the class registrations might be delivered twice to the newly registered listener.
+            alreadyRegisteredClasses = new HashSet<Class>(registeredClasses.keySet());
+        }
+
+        // new registrations will call the new listener while the following occurs notify the new listener about already-registered classes
+        for (Class pcClass : alreadyRegisteredClasses)
+        {
+            crl.registerClass(pcClass);
+        }
+    }
+
+    /**
+     * Remove the specified <code>RegisterClassListener</code> from the listener list.
+     * @param crl the listener to be removed
+     */
+    public void removeRegisterClassListener(RegisterClassListener crl)
+    {
+        synchronized (listeners)
+        {
+            listeners.remove(crl);
+        }
+    }
+
+    /**
+     * Returns a collection of class objects of the registered persistable classes.
+     * @return registered persistable classes
+     */
+    public Collection<Class> getRegisteredClasses()
+    {
+        return Collections.unmodifiableCollection(registeredClasses.keySet());
+    }
+
+    /**
+     * Look up the instance for a Persistable class.
+     * @param pcClass the <code>Class</code>.
+     * @return the Persistable instance for the <code>Class</code>.
+     */
+    private static Persistable getPersistableForClass(Class pcClass)
+    {
+        Meta ret = registeredClasses.get(pcClass);
+        if (ret == null)
+        {
+            // Commented out code that we could enable, particularly if we update registerClass to not pass an instance
+            if (Persistable.class.isAssignableFrom(pcClass))
+            {
+                System.err.println(String.format("MONKEY PATCH INTERCEPTING %s", pcClass));
+                // Not yet registered but this is Persistable, so create an instance
+                Persistable pc;
+                try
+                {
+                    pc = (Persistable) pcClass.newInstance();
+                    registeredClasses.put(pcClass, new Meta(pc));
+                    return pc;
+                }
+                catch (InstantiationException | IllegalAccessException e)
+                {
+                    System.err.println(e.getMessage());
+                }
+            }
+            throw new NucleusUserException("Cannot lookup meta info for " + pcClass + " - nothing found").setFatal();
+        }
+        return ret.getPC();
+    }
+
+    /**
+     * Helper class to manage persistable classes. 
+     */
+    static class Meta
+    {
+        /** Instance of <code>Persistable</code>, used at runtime to create new instances. */
+        Persistable pc;
+
+        /**
+         * Construct an instance of <code>Meta</code>.
+         * @param pc An instance of the <code>Persistable</code> class
+         */
+        Meta(Persistable pc)
+        {
+            this.pc = pc;
+        }
+
+        Persistable getPC()
+        {
+            return pc;
+        }
+
+        public String toString()
+        {
+            return "Meta-" + pc.getClass().getName();
+        }
+    }
+}
\ No newline at end of file