You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by jg...@apache.org on 2020/06/19 10:22:39 UTC

[tomee] 02/03: Revert "Using new repository"

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

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

commit 9b49d25b3c4e671161386294be33645f852ddbe3
Author: Jonathan Gallimore <jo...@jrg.me.uk>
AuthorDate: Fri Jun 19 11:21:38 2020 +0100

    Revert "Using new repository"
    
    This reverts commit e48dc4a20d33595dc1ceb7cc7399d012c658a4dd.
---
 jakarta/pom.xml                                    |  336 +++
 jakarta/src/main/assembly/tomee-microprofile.xml   |   38 +
 jakarta/src/main/assembly/tomee-plume.xml          |   38 +
 jakarta/src/main/assembly/tomee-plus.xml           |   38 +
 jakarta/src/main/assembly/tomee-webprofile.xml     |   38 +
 .../catalina/loader/WebappClassLoaderBase.java     | 2725 ++++++++++++++++++++
 .../apache/cxf/jaxb/JAXBContextInitializer.java    |  611 +++++
 .../openjpa/enhance/PCClassFileTransformer.java    |  254 ++
 .../openjpa/lib/meta/ClassMetaDataIterator.java    |  203 ++
 .../openjpa/lib/util/TemporaryClassLoader.java     |  123 +
 .../openjpa/meta/AbstractMetaDataDefaults.java     |  422 +++
 .../MbeansDescriptorsIntrospectionSource.java      |  392 +++
 .../webbeans/proxy/AbstractProxyFactory.java       |  737 ++++++
 .../jpa/deployment/JavaSECMPInitializer.java       |  372 +++
 .../accessors/objects/MetadataAsmFactory.java      |  712 +++++
 .../metadata/accessors/objects/MetadataClass.java  |  628 +++++
 .../eclipse/persistence/jaxb/javamodel/Helper.java |  478 ++++
 .../jaxb/javamodel/reflection/JavaClassImpl.java   |  576 +++++
 .../persistence/jaxb/rs/MOXyJsonProvider.java      |  986 +++++++
 .../persistence/jpa/rs/PersistenceFactoryBase.java |  266 ++
 .../org/quartz/core/QuartzSchedulerMBeanImpl.java  | 1011 ++++++++
 pom.xml                                            |    1 +
 22 files changed, 10985 insertions(+)

diff --git a/jakarta/pom.xml b/jakarta/pom.xml
new file mode 100644
index 0000000..0044d1d
--- /dev/null
+++ b/jakarta/pom.xml
@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+
+<!-- $Rev: 600338 $ $Date: 2007-12-02 09:08:04 -0800 (Sun, 02 Dec 2007) $ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+  <parent>
+    <artifactId>tomee-project</artifactId>
+    <groupId>org.apache.tomee</groupId>
+    <version>8.0.3-SNAPSHOT</version>
+  </parent>
+
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>apache-tomee</artifactId>
+  <version>9.0.0-M1-SNAPSHOT</version>
+  <packaging>jar</packaging>
+  <name>TomEE :: TomEE :: Apache TomEE 9</name>
+
+  <properties>
+    <tomee.version>8.0.3</tomee.version>
+    <tomee.build.name>${project.groupId}.tomee.tomee</tomee.build.name>
+    <webprofile.work-dir>${project.build.directory}/webprofile-work-dir</webprofile.work-dir>
+    <plus.work-dir>${project.build.directory}/plus-work-dir</plus.work-dir>
+    <plume.work-dir>${project.build.directory}/plume-work-dir</plume.work-dir>
+    <microprofile.work-dir>${project.build.directory}/microprofile-work-dir</microprofile.work-dir>
+  </properties>
+
+  <dependencies>
+    <dependency> <!-- to be sure to have it -->
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apache-tomee</artifactId>
+      <version>${tomee.version}</version>
+      <classifier>webprofile</classifier>
+      <type>zip</type>
+      <scope>provided</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency> <!-- to be sure to have it -->
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apache-tomee</artifactId>
+      <version>${tomee.version}</version>
+      <classifier>microprofile</classifier>
+      <type>zip</type>
+      <scope>provided</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency> <!-- to be sure to have it -->
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apache-tomee</artifactId>
+      <version>${tomee.version}</version>
+      <classifier>plume</classifier>
+      <type>zip</type>
+      <scope>provided</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency> <!-- to be sure to have it -->
+      <groupId>${project.groupId}</groupId>
+      <artifactId>apache-tomee</artifactId>
+      <version>${tomee.version}</version>
+      <classifier>plus</classifier>
+      <type>zip</type>
+      <scope>provided</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>false</filtering>
+      </resource>
+    </resources>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>unpack-webprofile</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.apache.tomee</groupId>
+                  <artifactId>apache-tomee</artifactId>
+                  <version>${tomee.version}</version>
+                  <classifier>webprofile</classifier>
+                  <type>zip</type>
+                  <outputDirectory>${webprofile.work-dir}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+          <execution>
+            <id>unpack-microprofile</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.apache.tomee</groupId>
+                  <artifactId>apache-tomee</artifactId>
+                  <version>${tomee.version}</version>
+                  <classifier>microprofile</classifier>
+                  <type>zip</type>
+                  <outputDirectory>${microprofile.work-dir}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+          <execution>
+            <id>unpack-plume</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.apache.tomee</groupId>
+                  <artifactId>apache-tomee</artifactId>
+                  <version>${tomee.version}</version>
+                  <classifier>plume</classifier>
+                  <type>zip</type>
+                  <outputDirectory>${plume.work-dir}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+          <execution>
+            <id>unpack-plus</id>
+            <phase>process-resources</phase>
+            <goals>
+              <goal>unpack</goal>
+            </goals>
+            <configuration>
+              <artifactItems>
+                <artifactItem>
+                  <groupId>org.apache.tomee</groupId>
+                  <artifactId>apache-tomee</artifactId>
+                  <version>${tomee.version}</version>
+                  <classifier>plus</classifier>
+                  <type>zip</type>
+                  <outputDirectory>${plus.work-dir}</outputDirectory>
+                </artifactItem>
+              </artifactItems>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>webprofile</id>
+            <phase>package</phase>
+            <configuration>
+              <descriptors>
+                <descriptor>src/main/assembly/tomee-webprofile.xml</descriptor>
+              </descriptors>
+              <attach>false</attach>
+              <appendAssemblyId>false</appendAssemblyId>
+              <finalName>apache-tomee-webprofile-${project.version}</finalName>
+            </configuration>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>plus</id>
+            <phase>package</phase>
+            <configuration>
+              <descriptors>
+                <descriptor>src/main/assembly/tomee-plus.xml</descriptor>
+              </descriptors>
+              <attach>false</attach>
+              <appendAssemblyId>false</appendAssemblyId>
+              <finalName>apache-tomee-plus-${project.version}</finalName>
+            </configuration>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>plume</id>
+            <phase>package</phase>
+            <configuration>
+              <descriptors>
+                <descriptor>src/main/assembly/tomee-plume.xml</descriptor>
+              </descriptors>
+              <attach>false</attach>
+              <appendAssemblyId>false</appendAssemblyId>
+              <finalName>apache-tomee-plume-${project.version}</finalName>
+            </configuration>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+          <execution>
+            <id>microprofile</id>
+            <phase>package</phase>
+            <configuration>
+              <descriptors>
+                <descriptor>src/main/assembly/tomee-microprofile.xml</descriptor>
+              </descriptors>
+              <attach>false</attach>
+              <appendAssemblyId>false</appendAssemblyId>
+              <finalName>apache-tomee-microprofile-${project.version}</finalName>
+            </configuration>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-artifacts</id>
+            <phase>package</phase>
+            <goals>
+              <goal>attach-artifact</goal>
+            </goals>
+            <configuration>
+              <artifacts>
+                <artifact>
+                  <file>${project.build.directory}/apache-tomee-webprofile-${project.version}.zip</file>
+                  <type>zip</type>
+                  <classifier>webprofile</classifier>
+                </artifact>
+                <artifact>
+                  <file>${project.build.directory}/apache-tomee-plus-${project.version}.zip</file>
+                  <type>zip</type>
+                  <classifier>plus</classifier>
+                </artifact>
+                <artifact>
+                  <file>${project.build.directory}/apache-tomee-plume-${project.version}.zip</file>
+                  <type>zip</type>
+                  <classifier>plume</classifier>
+                </artifact>
+                <artifact>
+                  <file>${project.build.directory}/apache-tomee-microprofile-${project.version}.zip</file>
+                  <type>zip</type>
+                  <classifier>microprofile</classifier>
+                </artifact>
+              </artifacts>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.tomitribe.transformer</groupId>
+        <artifactId>org.eclipse.transformer.maven</artifactId>
+        <version>0.1.1a</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <phase>package</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.tomee.patch</groupId>
+        <artifactId>tomee-patch-plugin</artifactId>
+        <version>0.1</version>
+        <configuration>
+          <select>apache-tomee.*\.zip</select>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>run</goal>
+            </goals>
+            <phase>package</phase>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/jakarta/src/main/assembly/tomee-microprofile.xml b/jakarta/src/main/assembly/tomee-microprofile.xml
new file mode 100644
index 0000000..6a1e49b
--- /dev/null
+++ b/jakarta/src/main/assembly/tomee-microprofile.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+
+<!-- $Rev: 576067 $ $Date: 2007-09-16 03:17:08 -0700 (Sun, 16 Sep 2007) $ -->
+
+<assembly>
+  <id>tomee-microprofile</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <directory>${microprofile.work-dir}/apache-tomee-microprofile-${tomee.version}</directory>
+      <outputDirectory>/apache-tomee-microprofile-${project.version}</outputDirectory>
+      <includes>
+        <include>**/*</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
+
diff --git a/jakarta/src/main/assembly/tomee-plume.xml b/jakarta/src/main/assembly/tomee-plume.xml
new file mode 100644
index 0000000..bb71e05
--- /dev/null
+++ b/jakarta/src/main/assembly/tomee-plume.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+
+<!-- $Rev: 576067 $ $Date: 2007-09-16 03:17:08 -0700 (Sun, 16 Sep 2007) $ -->
+
+<assembly>
+  <id>tomee-plume</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <directory>${plume.work-dir}/apache-tomee-plume-${tomee.version}</directory>
+      <outputDirectory>/apache-tomee-plume-${project.version}</outputDirectory>
+      <includes>
+        <include>**/*</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
+
diff --git a/jakarta/src/main/assembly/tomee-plus.xml b/jakarta/src/main/assembly/tomee-plus.xml
new file mode 100644
index 0000000..e0c5708
--- /dev/null
+++ b/jakarta/src/main/assembly/tomee-plus.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+
+<!-- $Rev: 576067 $ $Date: 2007-09-16 03:17:08 -0700 (Sun, 16 Sep 2007) $ -->
+
+<assembly>
+  <id>tomee-plus</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <directory>${plus.work-dir}/apache-tomee-plus-${tomee.version}</directory>
+      <outputDirectory>/apache-tomee-plus-${project.version}</outputDirectory>
+      <includes>
+        <include>**/*</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
+
diff --git a/jakarta/src/main/assembly/tomee-webprofile.xml b/jakarta/src/main/assembly/tomee-webprofile.xml
new file mode 100644
index 0000000..1777653
--- /dev/null
+++ b/jakarta/src/main/assembly/tomee-webprofile.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You 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.
+-->
+
+<!-- $Rev: 576067 $ $Date: 2007-09-16 03:17:08 -0700 (Sun, 16 Sep 2007) $ -->
+
+<assembly>
+  <id>tomee-webprofile</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <fileSets>
+    <fileSet>
+      <directory>${webprofile.work-dir}/apache-tomee-webprofile-${tomee.version}</directory>
+      <outputDirectory>/apache-tomee-webprofile-${project.version}</outputDirectory>
+      <includes>
+        <include>**/*</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
+
diff --git a/jakarta/src/patch/java/org/apache/catalina/loader/WebappClassLoaderBase.java b/jakarta/src/patch/java/org/apache/catalina/loader/WebappClassLoaderBase.java
new file mode 100644
index 0000000..2e75d4c
--- /dev/null
+++ b/jakarta/src/patch/java/org/apache/catalina/loader/WebappClassLoaderBase.java
@@ -0,0 +1,2725 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.catalina.loader;
+
+import java.io.File;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.ref.Reference;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessControlException;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.jar.Attributes;
+import java.util.jar.Attributes.Name;
+import java.util.jar.Manifest;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.WebResource;
+import org.apache.catalina.WebResourceRoot;
+import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
+import org.apache.juli.WebappProperties;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.InstrumentableClassLoader;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.apache.tomcat.util.compat.JreCompat;
+import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.util.security.PermissionCheck;
+
+/**
+ * Specialized web application class loader.
+ * <p>
+ * This class loader is a full reimplementation of the
+ * <code>URLClassLoader</code> from the JDK. It is designed to be fully
+ * compatible with a normal <code>URLClassLoader</code>, although its internal
+ * behavior may be completely different.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - By default, this class loader follows
+ * the delegation model required by the specification. The system class
+ * loader will be queried first, then the local repositories, and only then
+ * delegation to the parent class loader will occur. This allows the web
+ * application to override any shared class except the classes from J2SE.
+ * Special handling is provided from the JAXP XML parser interfaces, the JNDI
+ * interfaces, and the classes from the servlet API, which are never loaded
+ * from the webapp repositories. The <code>delegate</code> property
+ * allows an application to modify this behavior to move the parent class loader
+ * ahead of the local repositories.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Due to limitations in Jasper
+ * compilation technology, any repository which contains classes from
+ * the servlet API will be ignored by the class loader.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - The class loader generates source
+ * URLs which include the full JAR URL when a class is loaded from a JAR file,
+ * which allows setting security permission at the class level, even when a
+ * class is contained inside a JAR.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - Local repositories are searched in
+ * the order they are added via the initial constructor.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - No check for sealing violations or
+ * security is made unless a security manager is present.
+ * <p>
+ * <strong>IMPLEMENTATION NOTE</strong> - As of 8.0, this class
+ * loader implements {@link InstrumentableClassLoader}, permitting web
+ * application classes to instrument other classes in the same web
+ * application. It does not permit instrumentation of system or container
+ * classes or classes in other web apps.
+ *
+ * @author Remy Maucherat
+ * @author Craig R. McClanahan
+ */
+public abstract class WebappClassLoaderBase extends URLClassLoader
+        implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
+
+    private static final Log log = LogFactory.getLog(WebappClassLoaderBase.class);
+
+    /**
+     * List of ThreadGroup names to ignore when scanning for web application
+     * started threads that need to be shut down.
+     */
+    private static final List<String> JVM_THREAD_GROUP_NAMES = new ArrayList<>();
+
+    private static final String JVM_THREAD_GROUP_SYSTEM = "system";
+
+    private static final String CLASS_FILE_SUFFIX = ".class";
+
+    static {
+        if (!JreCompat.isGraalAvailable()) {
+            ClassLoader.registerAsParallelCapable();
+        }
+        JVM_THREAD_GROUP_NAMES.add(JVM_THREAD_GROUP_SYSTEM);
+        JVM_THREAD_GROUP_NAMES.add("RMI Runtime");
+    }
+
+    protected class PrivilegedFindClassByName implements PrivilegedAction<Class<?>> {
+
+        private final String name;
+
+        PrivilegedFindClassByName(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public Class<?> run() {
+            return findClassInternal(name);
+        }
+    }
+
+
+    protected static final class PrivilegedGetClassLoader implements PrivilegedAction<ClassLoader> {
+
+        private final Class<?> clazz;
+
+        public PrivilegedGetClassLoader(Class<?> clazz){
+            this.clazz = clazz;
+        }
+
+        @Override
+        public ClassLoader run() {
+            return clazz.getClassLoader();
+        }
+    }
+
+
+    protected final class PrivilegedJavaseGetResource implements PrivilegedAction<URL> {
+
+        private final String name;
+
+        public PrivilegedJavaseGetResource(String name) {
+            this.name = name;
+        }
+
+        @Override
+        public URL run() {
+            return javaseClassLoader.getResource(name);
+        }
+    }
+
+
+    // ------------------------------------------------------- Static Variables
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a new ClassLoader with no defined repositories and no
+     * parent ClassLoader.
+     */
+    protected WebappClassLoaderBase() {
+
+        super(new URL[0]);
+
+        ClassLoader p = getParent();
+        if (p == null) {
+            p = getSystemClassLoader();
+        }
+        this.parent = p;
+
+        ClassLoader j = String.class.getClassLoader();
+        if (j == null) {
+            j = getSystemClassLoader();
+            while (j.getParent() != null) {
+                j = j.getParent();
+            }
+        }
+        this.javaseClassLoader = j;
+
+        securityManager = System.getSecurityManager();
+        if (securityManager != null) {
+            refreshPolicy();
+        }
+    }
+
+
+    /**
+     * Construct a new ClassLoader with no defined repositories and the given
+     * parent ClassLoader.
+     * <p>
+     * Method is used via reflection -
+     * see {@link WebappLoader#createClassLoader()}
+     *
+     * @param parent Our parent class loader
+     */
+    protected WebappClassLoaderBase(ClassLoader parent) {
+
+        super(new URL[0], parent);
+
+        ClassLoader p = getParent();
+        if (p == null) {
+            p = getSystemClassLoader();
+        }
+        this.parent = p;
+
+        ClassLoader j = String.class.getClassLoader();
+        if (j == null) {
+            j = getSystemClassLoader();
+            while (j.getParent() != null) {
+                j = j.getParent();
+            }
+        }
+        this.javaseClassLoader = j;
+
+        securityManager = System.getSecurityManager();
+        if (securityManager != null) {
+            refreshPolicy();
+        }
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * Associated web resources for this webapp.
+     */
+    protected WebResourceRoot resources = null;
+
+
+    /**
+     * The cache of ResourceEntry for classes and resources we have loaded,
+     * keyed by resource path, not binary name. Path is used as the key since
+     * resources may be requested by binary name (classes) or path (other
+     * resources such as property files) and the mapping from binary name to
+     * path is unambiguous but the reverse mapping is ambiguous.
+     */
+    protected final Map<String, ResourceEntry> resourceEntries =
+            new ConcurrentHashMap<>();
+
+
+    /**
+     * Should this class loader delegate to the parent class loader
+     * <strong>before</strong> searching its own repositories (i.e. the
+     * usual Java2 delegation model)?  If set to <code>false</code>,
+     * this class loader will search its own repositories first, and
+     * delegate to the parent only if the class or resource is not
+     * found locally. Note that the default, <code>false</code>, is
+     * the behavior called for by the servlet specification.
+     */
+    protected boolean delegate = false;
+
+
+    private final Map<String,Long> jarModificationTimes = new HashMap<>();
+
+
+    /**
+     * A list of read File Permission's required if this loader is for a web
+     * application context.
+     */
+    protected final ArrayList<Permission> permissionList = new ArrayList<>();
+
+
+    /**
+     * The PermissionCollection for each CodeSource for a web
+     * application context.
+     */
+    protected final HashMap<String, PermissionCollection> loaderPC = new HashMap<>();
+
+
+    /**
+     * Instance of the SecurityManager installed.
+     */
+    protected final SecurityManager securityManager;
+
+
+    /**
+     * The parent class loader.
+     */
+    protected final ClassLoader parent;
+
+
+    /**
+     * The bootstrap class loader used to load the JavaSE classes. In some
+     * implementations this class loader is always <code>null</code> and in
+     * those cases {@link ClassLoader#getParent()} will be called recursively on
+     * the system class loader and the last non-null result used.
+     */
+    private ClassLoader javaseClassLoader;
+
+
+    /**
+     * Enables the RMI Target memory leak detection to be controlled. This is
+     * necessary since the detection can only work on Java 9 if some of the
+     * modularity checks are disabled.
+     */
+    private boolean clearReferencesRmiTargets = true;
+
+    /**
+     * Should Tomcat attempt to terminate threads that have been started by the
+     * web application? Stopping threads is performed via the deprecated (for
+     * good reason) <code>Thread.stop()</code> method and is likely to result in
+     * instability. As such, enabling this should be viewed as an option of last
+     * resort in a development environment and is not recommended in a
+     * production environment. If not specified, the default value of
+     * <code>false</code> will be used.
+     */
+    private boolean clearReferencesStopThreads = false;
+
+    /**
+     * Should Tomcat attempt to terminate any {@link java.util.TimerThread}s
+     * that have been started by the web application? If not specified, the
+     * default value of <code>false</code> will be used.
+     */
+    private boolean clearReferencesStopTimerThreads = false;
+
+    /**
+     * Should Tomcat call
+     * {@link org.apache.juli.logging.LogFactory#release(ClassLoader)}
+     * when the class loader is stopped? If not specified, the default value
+     * of <code>true</code> is used. Changing the default setting is likely to
+     * lead to memory leaks and other issues.
+     */
+    private boolean clearReferencesLogFactoryRelease = true;
+
+    /**
+     * If an HttpClient keep-alive timer thread has been started by this web
+     * application and is still running, should Tomcat change the context class
+     * loader from the current {@link ClassLoader} to
+     * {@link ClassLoader#getParent()} to prevent a memory leak? Note that the
+     * keep-alive timer thread will stop on its own once the keep-alives all
+     * expire however, on a busy system that might not happen for some time.
+     */
+    private boolean clearReferencesHttpClientKeepAliveThread = true;
+
+    /**
+     * Should Tomcat attempt to clear references to classes loaded by this class
+     * loader from the ObjectStreamClass caches?
+     */
+    private boolean clearReferencesObjectStreamClassCaches = true;
+
+    /**
+     * Should Tomcat attempt to clear references to classes loaded by this class
+     * loader from ThreadLocals?
+     */
+    private boolean clearReferencesThreadLocals = true;
+
+    /**
+     * Should Tomcat skip the memory leak checks when the web application is
+     * stopped as part of the process of shutting down the JVM?
+     */
+    private boolean skipMemoryLeakChecksOnJvmShutdown = false;
+
+    /**
+     * Holds the class file transformers decorating this class loader. The
+     * CopyOnWriteArrayList is thread safe. It is expensive on writes, but
+     * those should be rare. It is very fast on reads, since synchronization
+     * is not actually used. Importantly, the ClassLoader will never block
+     * iterating over the transformers while loading a class.
+     */
+    private final List<ClassFileTransformer> transformers = new CopyOnWriteArrayList<>();
+
+
+    /**
+     * Flag that indicates that {@link #addURL(URL)} has been called which
+     * creates a requirement to check the super class when searching for
+     * resources.
+     */
+    private boolean hasExternalRepositories = false;
+
+
+    /**
+     * Repositories managed by this class rather than the super class.
+     */
+    private List<URL> localRepositories = new ArrayList<>();
+
+
+    private volatile LifecycleState state = LifecycleState.NEW;
+
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * @return associated resources.
+     */
+    public WebResourceRoot getResources() {
+        return this.resources;
+    }
+
+
+    /**
+     * Set associated resources.
+     * @param resources the resources from which the classloader will
+     *     load the classes
+     */
+    public void setResources(WebResourceRoot resources) {
+        this.resources = resources;
+    }
+
+
+    /**
+     * @return the context name for this class loader.
+     */
+    public String getContextName() {
+        if (resources == null) {
+            return "Unknown";
+        } else {
+            return resources.getContext().getBaseName();
+        }
+    }
+
+
+    /**
+     * Return the "delegate first" flag for this class loader.
+     * @return <code>true</code> if the class lookup will delegate to
+     *   the parent first. The default in Tomcat is <code>false</code>.
+     */
+    public boolean getDelegate() {
+        return this.delegate;
+    }
+
+
+    /**
+     * Set the "delegate first" flag for this class loader.
+     * If this flag is true, this class loader delegates
+     * to the parent class loader
+     * <strong>before</strong> searching its own repositories, as
+     * in an ordinary (non-servlet) chain of Java class loaders.
+     * If set to <code>false</code> (the default),
+     * this class loader will search its own repositories first, and
+     * delegate to the parent only if the class or resource is not
+     * found locally, as per the servlet specification.
+     *
+     * @param delegate The new "delegate first" flag
+     */
+    public void setDelegate(boolean delegate) {
+        this.delegate = delegate;
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a read permission for the
+     * target of the given URL as appropriate.
+     *
+     * @param url URL for a file or directory on local system
+     */
+    void addPermission(URL url) {
+        if (url == null) {
+            return;
+        }
+        if (securityManager != null) {
+            String protocol = url.getProtocol();
+            if ("file".equalsIgnoreCase(protocol)) {
+                URI uri;
+                File f;
+                String path;
+                try {
+                    uri = url.toURI();
+                    f = new File(uri);
+                    path = f.getCanonicalPath();
+                } catch (IOException | URISyntaxException e) {
+                    log.warn(sm.getString(
+                            "webappClassLoader.addPermisionNoCanonicalFile",
+                            url.toExternalForm()));
+                    return;
+                }
+                if (f.isFile()) {
+                    // Allow the file to be read
+                    addPermission(new FilePermission(path, "read"));
+                } else if (f.isDirectory()) {
+                    addPermission(new FilePermission(path, "read"));
+                    addPermission(new FilePermission(
+                            path + File.separator + "-", "read"));
+                } else {
+                    // File does not exist - ignore (shouldn't happen)
+                }
+            } else {
+                // Unsupported URL protocol
+                log.warn(sm.getString(
+                        "webappClassLoader.addPermisionNoProtocol",
+                        protocol, url.toExternalForm()));
+            }
+        }
+    }
+
+
+    /**
+     * If there is a Java SecurityManager create a Permission.
+     *
+     * @param permission The permission
+     */
+    void addPermission(Permission permission) {
+        if ((securityManager != null) && (permission != null)) {
+            permissionList.add(permission);
+        }
+    }
+
+
+    public boolean getClearReferencesRmiTargets() {
+        return this.clearReferencesRmiTargets;
+    }
+
+
+    public void setClearReferencesRmiTargets(boolean clearReferencesRmiTargets) {
+        this.clearReferencesRmiTargets = clearReferencesRmiTargets;
+    }
+
+
+    /**
+     * @return the clearReferencesStopThreads flag for this Context.
+     */
+    public boolean getClearReferencesStopThreads() {
+        return this.clearReferencesStopThreads;
+    }
+
+
+    /**
+     * Set the clearReferencesStopThreads feature for this Context.
+     *
+     * @param clearReferencesStopThreads The new flag value
+     */
+    public void setClearReferencesStopThreads(
+            boolean clearReferencesStopThreads) {
+        this.clearReferencesStopThreads = clearReferencesStopThreads;
+    }
+
+
+    /**
+     * @return the clearReferencesStopTimerThreads flag for this Context.
+     */
+    public boolean getClearReferencesStopTimerThreads() {
+        return this.clearReferencesStopTimerThreads;
+    }
+
+
+    /**
+     * Set the clearReferencesStopTimerThreads feature for this Context.
+     *
+     * @param clearReferencesStopTimerThreads The new flag value
+     */
+    public void setClearReferencesStopTimerThreads(
+            boolean clearReferencesStopTimerThreads) {
+        this.clearReferencesStopTimerThreads = clearReferencesStopTimerThreads;
+    }
+
+
+    /**
+     * @return the clearReferencesLogFactoryRelease flag for this Context.
+     */
+    public boolean getClearReferencesLogFactoryRelease() {
+        return this.clearReferencesLogFactoryRelease;
+    }
+
+
+    /**
+     * Set the clearReferencesLogFactoryRelease feature for this Context.
+     *
+     * @param clearReferencesLogFactoryRelease The new flag value
+     */
+    public void setClearReferencesLogFactoryRelease(
+            boolean clearReferencesLogFactoryRelease) {
+        this.clearReferencesLogFactoryRelease =
+            clearReferencesLogFactoryRelease;
+    }
+
+
+    /**
+     * @return the clearReferencesHttpClientKeepAliveThread flag for this
+     * Context.
+     */
+    public boolean getClearReferencesHttpClientKeepAliveThread() {
+        return this.clearReferencesHttpClientKeepAliveThread;
+    }
+
+
+    /**
+     * Set the clearReferencesHttpClientKeepAliveThread feature for this
+     * Context.
+     *
+     * @param clearReferencesHttpClientKeepAliveThread The new flag value
+     */
+    public void setClearReferencesHttpClientKeepAliveThread(
+            boolean clearReferencesHttpClientKeepAliveThread) {
+        this.clearReferencesHttpClientKeepAliveThread =
+            clearReferencesHttpClientKeepAliveThread;
+    }
+
+
+    public boolean getClearReferencesObjectStreamClassCaches() {
+        return clearReferencesObjectStreamClassCaches;
+    }
+
+
+    public void setClearReferencesObjectStreamClassCaches(
+            boolean clearReferencesObjectStreamClassCaches) {
+        this.clearReferencesObjectStreamClassCaches = clearReferencesObjectStreamClassCaches;
+    }
+
+
+    public boolean getClearReferencesThreadLocals() {
+        return clearReferencesThreadLocals;
+    }
+
+
+    public void setClearReferencesThreadLocals(boolean clearReferencesThreadLocals) {
+        this.clearReferencesThreadLocals = clearReferencesThreadLocals;
+    }
+
+
+    public boolean getSkipMemoryLeakChecksOnJvmShutdown() {
+        return skipMemoryLeakChecksOnJvmShutdown;
+    }
+
+
+    public void setSkipMemoryLeakChecksOnJvmShutdown(boolean skipMemoryLeakChecksOnJvmShutdown) {
+        this.skipMemoryLeakChecksOnJvmShutdown = skipMemoryLeakChecksOnJvmShutdown;
+    }
+
+
+    // ------------------------------------------------------- Reloader Methods
+
+    /**
+     * Adds the specified class file transformer to this class loader. The
+     * transformer will then be able to modify the bytecode of any classes
+     * loaded by this class loader after the invocation of this method.
+     *
+     * @param transformer The transformer to add to the class loader
+     */
+    @Override
+    public void addTransformer(ClassFileTransformer transformer) {
+
+        if (transformer == null) {
+            throw new IllegalArgumentException(sm.getString(
+                    "webappClassLoader.addTransformer.illegalArgument", getContextName()));
+        }
+
+        if (this.transformers.contains(transformer)) {
+            // if the same instance of this transformer was already added, bail out
+            log.warn(sm.getString("webappClassLoader.addTransformer.duplicate",
+                    transformer, getContextName()));
+            return;
+        }
+        this.transformers.add(transformer);
+
+        log.info(sm.getString("webappClassLoader.addTransformer", transformer, getContextName()));
+    }
+
+    /**
+     * Removes the specified class file transformer from this class loader.
+     * It will no longer be able to modify the byte code of any classes
+     * loaded by the class loader after the invocation of this method.
+     * However, any classes already modified by this transformer will
+     * remain transformed.
+     *
+     * @param transformer The transformer to remove
+     */
+    @Override
+    public void removeTransformer(ClassFileTransformer transformer) {
+
+        if (transformer == null) {
+            return;
+        }
+
+        if (this.transformers.remove(transformer)) {
+            log.info(sm.getString("webappClassLoader.removeTransformer",
+                    transformer, getContextName()));
+        }
+    }
+
+    protected void copyStateWithoutTransformers(WebappClassLoaderBase base) {
+        base.resources = this.resources;
+        base.delegate = this.delegate;
+        base.state = LifecycleState.NEW;
+        base.clearReferencesStopThreads = this.clearReferencesStopThreads;
+        base.clearReferencesStopTimerThreads = this.clearReferencesStopTimerThreads;
+        base.clearReferencesLogFactoryRelease = this.clearReferencesLogFactoryRelease;
+        base.clearReferencesHttpClientKeepAliveThread = this.clearReferencesHttpClientKeepAliveThread;
+        base.jarModificationTimes.putAll(this.jarModificationTimes);
+        base.permissionList.addAll(this.permissionList);
+        base.loaderPC.putAll(this.loaderPC);
+    }
+
+    /**
+     * Have one or more classes or resources been modified so that a reload
+     * is appropriate?
+     * @return <code>true</code> if there's been a modification
+     */
+    public boolean modified() {
+
+        if (log.isDebugEnabled())
+            log.debug("modified()");
+
+        for (Entry<String,ResourceEntry> entry : resourceEntries.entrySet()) {
+            long cachedLastModified = entry.getValue().lastModified;
+            long lastModified = resources.getClassLoaderResource(
+                    entry.getKey()).getLastModified();
+            if (lastModified != cachedLastModified) {
+                if( log.isDebugEnabled() )
+                    log.debug(sm.getString("webappClassLoader.resourceModified",
+                            entry.getKey(),
+                            new Date(cachedLastModified),
+                            new Date(lastModified)));
+                return true;
+            }
+        }
+
+        // Check if JARs have been added or removed
+        WebResource[] jars = resources.listResources("/WEB-INF/lib");
+        // Filter out non-JAR resources
+
+        int jarCount = 0;
+        for (WebResource jar : jars) {
+            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
+                jarCount++;
+                Long recordedLastModified = jarModificationTimes.get(jar.getName());
+                if (recordedLastModified == null) {
+                    // Jar has been added
+                    log.info(sm.getString("webappClassLoader.jarsAdded",
+                            resources.getContext().getName()));
+                    return true;
+                }
+                if (recordedLastModified.longValue() != jar.getLastModified()) {
+                    // Jar has been changed
+                    log.info(sm.getString("webappClassLoader.jarsModified",
+                            resources.getContext().getName()));
+                    return true;
+                }
+            }
+        }
+
+        if (jarCount < jarModificationTimes.size()){
+            log.info(sm.getString("webappClassLoader.jarsRemoved",
+                    resources.getContext().getName()));
+            return true;
+        }
+
+
+        // No classes have been modified
+        return false;
+    }
+
+
+    @Override
+    public String toString() {
+
+        StringBuilder sb = new StringBuilder(this.getClass().getSimpleName());
+        sb.append("\r\n  context: ");
+        sb.append(getContextName());
+        sb.append("\r\n  delegate: ");
+        sb.append(delegate);
+        sb.append("\r\n");
+        if (this.parent != null) {
+            sb.append("----------> Parent Classloader:\r\n");
+            sb.append(this.parent.toString());
+            sb.append("\r\n");
+        }
+        if (this.transformers.size() > 0) {
+            sb.append("----------> Class file transformers:\r\n");
+            for (ClassFileTransformer transformer : this.transformers) {
+                sb.append(transformer).append("\r\n");
+            }
+        }
+        return sb.toString();
+    }
+
+
+    // ---------------------------------------------------- ClassLoader Methods
+
+
+    // Note: exposed for use by tests
+    protected final Class<?> doDefineClass(String name, byte[] b, int off, int len,
+            ProtectionDomain protectionDomain) {
+        return super.defineClass(name, b, off, len, protectionDomain);
+    }
+
+    /**
+     * Find the specified class in our local repositories, if possible.  If
+     * not found, throw <code>ClassNotFoundException</code>.
+     *
+     * @param name The binary name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    @Override
+    public Class<?> findClass(String name) throws ClassNotFoundException {
+
+        if (log.isDebugEnabled())
+            log.debug("    findClass(" + name + ")");
+
+        checkStateForClassLoading(name);
+
+        // (1) Permission to define this class when using a SecurityManager
+        if (securityManager != null) {
+            int i = name.lastIndexOf('.');
+            if (i >= 0) {
+                try {
+                    if (log.isTraceEnabled())
+                        log.trace("      securityManager.checkPackageDefinition");
+                    securityManager.checkPackageDefinition(name.substring(0,i));
+                } catch (Exception se) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->Exception-->ClassNotFoundException", se);
+                    throw new ClassNotFoundException(name, se);
+                }
+            }
+        }
+
+        // Ask our superclass to locate this class, if possible
+        // (throws ClassNotFoundException if it is not found)
+        Class<?> clazz = null;
+        try {
+            if (log.isTraceEnabled())
+                log.trace("      findClassInternal(" + name + ")");
+            try {
+                if (securityManager != null) {
+                    PrivilegedAction<Class<?>> dp =
+                        new PrivilegedFindClassByName(name);
+                    clazz = AccessController.doPrivileged(dp);
+                } else {
+                    clazz = findClassInternal(name);
+                }
+            } catch(AccessControlException ace) {
+                log.warn(sm.getString("webappClassLoader.securityException", name,
+                        ace.getMessage()), ace);
+                throw new ClassNotFoundException(name, ace);
+            } catch (RuntimeException e) {
+                if (log.isTraceEnabled())
+                    log.trace("      -->RuntimeException Rethrown", e);
+                throw e;
+            }
+            if ((clazz == null) && hasExternalRepositories) {
+                try {
+                    clazz = super.findClass(name);
+                } catch(AccessControlException ace) {
+                    log.warn(sm.getString("webappClassLoader.securityException", name,
+                            ace.getMessage()), ace);
+                    throw new ClassNotFoundException(name, ace);
+                } catch (RuntimeException e) {
+                    if (log.isTraceEnabled())
+                        log.trace("      -->RuntimeException Rethrown", e);
+                    throw e;
+                }
+            }
+            if (clazz == null) {
+                if (log.isDebugEnabled())
+                    log.debug("    --> Returning ClassNotFoundException");
+                throw new ClassNotFoundException(name);
+            }
+        } catch (ClassNotFoundException e) {
+            if (log.isTraceEnabled())
+                log.trace("    --> Passing on ClassNotFoundException");
+            throw e;
+        }
+
+        // Return the class we have located
+        if (log.isTraceEnabled())
+            log.debug("      Returning class " + clazz);
+
+        if (log.isTraceEnabled()) {
+            ClassLoader cl;
+            if (Globals.IS_SECURITY_ENABLED){
+                cl = AccessController.doPrivileged(
+                    new PrivilegedGetClassLoader(clazz));
+            } else {
+                cl = clazz.getClassLoader();
+            }
+            log.debug("      Loaded by " + cl.toString());
+        }
+        return clazz;
+
+    }
+
+
+    /**
+     * Find the specified resource in our local repository, and return a
+     * <code>URL</code> referring to it, or <code>null</code> if this resource
+     * cannot be found.
+     *
+     * @param name Name of the resource to be found
+     */
+    @Override
+    public URL findResource(final String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("    findResource(" + name + ")");
+
+        checkStateForResourceLoading(name);
+
+        URL url = null;
+
+        String path = nameToPath(name);
+
+        WebResource resource = resources.getClassLoaderResource(path);
+        if (resource.exists()) {
+            url = resource.getURL();
+            trackLastModified(path, resource);
+        }
+
+        if ((url == null) && hasExternalRepositories) {
+            url = super.findResource(name);
+        }
+
+        if (log.isDebugEnabled()) {
+            if (url != null)
+                log.debug("    --> Returning '" + url.toString() + "'");
+            else
+                log.debug("    --> Resource not found, returning null");
+        }
+        return url;
+    }
+
+
+    private void trackLastModified(String path, WebResource resource) {
+        if (resourceEntries.containsKey(path)) {
+            return;
+        }
+        ResourceEntry entry = new ResourceEntry();
+        entry.lastModified = resource.getLastModified();
+        synchronized(resourceEntries) {
+            resourceEntries.putIfAbsent(path, entry);
+        }
+    }
+
+
+    /**
+     * Return an enumeration of <code>URLs</code> representing all of the
+     * resources with the given name.  If no resources with this name are
+     * found, return an empty enumeration.
+     *
+     * @param name Name of the resources to be found
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    @Override
+    public Enumeration<URL> findResources(String name) throws IOException {
+
+        if (log.isDebugEnabled())
+            log.debug("    findResources(" + name + ")");
+
+        checkStateForResourceLoading(name);
+
+        LinkedHashSet<URL> result = new LinkedHashSet<>();
+
+        String path = nameToPath(name);
+
+        WebResource[] webResources = resources.getClassLoaderResources(path);
+        for (WebResource webResource : webResources) {
+            if (webResource.exists()) {
+                result.add(webResource.getURL());
+            }
+        }
+
+        // Adding the results of a call to the superclass
+        if (hasExternalRepositories) {
+            Enumeration<URL> otherResourcePaths = super.findResources(name);
+            while (otherResourcePaths.hasMoreElements()) {
+                result.add(otherResourcePaths.nextElement());
+            }
+        }
+
+        return Collections.enumeration(result);
+    }
+
+
+    /**
+     * Find the resource with the given name.  A resource is some data
+     * (images, audio, text, etc.) that can be accessed by class code in a
+     * way that is independent of the location of the code.  The name of a
+     * resource is a "/"-separated path name that identifies the resource.
+     * If the resource cannot be found, return <code>null</code>.
+     * <p>
+     * This method searches according to the following algorithm, returning
+     * as soon as it finds the appropriate URL.  If the resource cannot be
+     * found, returns <code>null</code>.
+     * <ul>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>getResource()</code> method of the parent class
+     *     loader, if any.</li>
+     * <li>Call <code>findResource()</code> to find this resource in our
+     *     locally defined repositories.</li>
+     * <li>Call the <code>getResource()</code> method of the parent class
+     *     loader, if any.</li>
+     * </ul>
+     *
+     * @param name Name of the resource to return a URL for
+     */
+    @Override
+    public URL getResource(String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("getResource(" + name + ")");
+
+        checkStateForResourceLoading(name);
+
+        URL url = null;
+
+        boolean delegateFirst = delegate || filter(name, false);
+
+        // (1) Delegate to parent if requested
+        if (delegateFirst) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader " + parent);
+            url = parent.getResource(name);
+            if (url != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning '" + url.toString() + "'");
+                return url;
+            }
+        }
+
+        // (2) Search local repositories
+        url = findResource(name);
+        if (url != null) {
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning '" + url.toString() + "'");
+            return url;
+        }
+
+        // (3) Delegate to parent unconditionally if not already attempted
+        if (!delegateFirst) {
+            url = parent.getResource(name);
+            if (url != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning '" + url.toString() + "'");
+                return url;
+            }
+        }
+
+        // (4) Resource was not found
+        if (log.isDebugEnabled())
+            log.debug("  --> Resource not found, returning null");
+        return null;
+
+    }
+
+
+    @Override
+    public Enumeration<URL> getResources(String name) throws IOException {
+
+        Enumeration<URL> parentResources = getParent().getResources(name);
+        Enumeration<URL> localResources = findResources(name);
+
+        // Need to combine these enumerations. The order in which the
+        // Enumerations are combined depends on how delegation is configured
+        boolean delegateFirst = delegate || filter(name, false);
+
+        if (delegateFirst) {
+            return new CombinedEnumeration(parentResources, localResources);
+        } else {
+            return new CombinedEnumeration(localResources, parentResources);
+        }
+    }
+
+
+    /**
+     * Find the resource with the given name, and return an input stream
+     * that can be used for reading it.  The search order is as described
+     * for <code>getResource()</code>, after checking to see if the resource
+     * data has been previously cached.  If the resource cannot be found,
+     * return <code>null</code>.
+     *
+     * @param name Name of the resource to return an input stream for
+     */
+    @Override
+    public InputStream getResourceAsStream(String name) {
+
+        if (log.isDebugEnabled())
+            log.debug("getResourceAsStream(" + name + ")");
+
+        checkStateForResourceLoading(name);
+
+        InputStream stream = null;
+
+        boolean delegateFirst = delegate || filter(name, false);
+
+        // (1) Delegate to parent if requested
+        if (delegateFirst) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader " + parent);
+            stream = parent.getResourceAsStream(name);
+            if (stream != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning stream from parent");
+                return stream;
+            }
+        }
+
+        // (2) Search local repositories
+        if (log.isDebugEnabled())
+            log.debug("  Searching local repositories");
+        String path = nameToPath(name);
+        WebResource resource = resources.getClassLoaderResource(path);
+        if (resource.exists()) {
+            stream = resource.getInputStream();
+            trackLastModified(path, resource);
+        }
+        try {
+            if (hasExternalRepositories && stream == null) {
+                URL url = super.findResource(name);
+                if (url != null) {
+                    stream = url.openStream();
+                }
+            }
+        } catch (IOException e) {
+            // Ignore
+        }
+        if (stream != null) {
+            if (log.isDebugEnabled())
+                log.debug("  --> Returning stream from local");
+            return stream;
+        }
+
+        // (3) Delegate to parent unconditionally
+        if (!delegateFirst) {
+            if (log.isDebugEnabled())
+                log.debug("  Delegating to parent classloader unconditionally " + parent);
+            stream = parent.getResourceAsStream(name);
+            if (stream != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  --> Returning stream from parent");
+                return stream;
+            }
+        }
+
+        // (4) Resource was not found
+        if (log.isDebugEnabled())
+            log.debug("  --> Resource not found, returning null");
+        return null;
+    }
+
+
+    /**
+     * Load the class with the specified name.  This method searches for
+     * classes in the same manner as <code>loadClass(String, boolean)</code>
+     * with <code>false</code> as the second argument.
+     *
+     * @param name The binary name of the class to be loaded
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+        return loadClass(name, false);
+    }
+
+
+    /**
+     * Load the class with the specified name, searching using the following
+     * algorithm until it finds and returns the class.  If the class cannot
+     * be found, returns <code>ClassNotFoundException</code>.
+     * <ul>
+     * <li>Call <code>findLoadedClass(String)</code> to check if the
+     *     class has already been loaded.  If it has, the same
+     *     <code>Class</code> object is returned.</li>
+     * <li>If the <code>delegate</code> property is set to <code>true</code>,
+     *     call the <code>loadClass()</code> method of the parent class
+     *     loader, if any.</li>
+     * <li>Call <code>findClass()</code> to find this class in our locally
+     *     defined repositories.</li>
+     * <li>Call the <code>loadClass()</code> method of our parent
+     *     class loader, if any.</li>
+     * </ul>
+     * If the class was found using the above steps, and the
+     * <code>resolve</code> flag is <code>true</code>, this method will then
+     * call <code>resolveClass(Class)</code> on the resulting Class object.
+     *
+     * @param name The binary name of the class to be loaded
+     * @param resolve If <code>true</code> then resolve the class
+     *
+     * @exception ClassNotFoundException if the class was not found
+     */
+    @Override
+    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+
+        synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
+            if (log.isDebugEnabled())
+                log.debug("loadClass(" + name + ", " + resolve + ")");
+            Class<?> clazz = null;
+
+            // Log access to stopped class loader
+            checkStateForClassLoading(name);
+
+            // (0) Check our previously loaded local class cache
+            clazz = findLoadedClass0(name);
+            if (clazz != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  Returning class from cache");
+                if (resolve)
+                    resolveClass(clazz);
+                return clazz;
+            }
+
+            // (0.1) Check our previously loaded class cache
+            clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
+            if (clazz != null) {
+                if (log.isDebugEnabled())
+                    log.debug("  Returning class from cache");
+                if (resolve)
+                    resolveClass(clazz);
+                return clazz;
+            }
+
+            // (0.2) Try loading the class with the system class loader, to prevent
+            //       the webapp from overriding Java SE classes. This implements
+            //       SRV.10.7.2
+            String resourceName = binaryNameToPath(name, false);
+
+            ClassLoader javaseLoader = getJavaseClassLoader();
+            boolean tryLoadingFromJavaseLoader;
+            try {
+                // Use getResource as it won't trigger an expensive
+                // ClassNotFoundException if the resource is not available from
+                // the Java SE class loader. However (see
+                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
+                // details) when running under a security manager in rare cases
+                // this call may trigger a ClassCircularityError.
+                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
+                // details of how this may trigger a StackOverflowError
+                // Given these reported errors, catch Throwable to ensure any
+                // other edge cases are also caught
+                URL url;
+                if (securityManager != null) {
+                    PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
+                    url = AccessController.doPrivileged(dp);
+                } else {
+                    url = javaseLoader.getResource(resourceName);
+                }
+                tryLoadingFromJavaseLoader = (url != null);
+            } catch (Throwable t) {
+                // Swallow all exceptions apart from those that must be re-thrown
+                ExceptionUtils.handleThrowable(t);
+                // The getResource() trick won't work for this class. We have to
+                // try loading it directly and accept that we might get a
+                // ClassNotFoundException.
+                tryLoadingFromJavaseLoader = true;
+            }
+
+            if (tryLoadingFromJavaseLoader) {
+                try {
+                    clazz = javaseLoader.loadClass(name);
+                    if (clazz != null) {
+                        if (resolve)
+                            resolveClass(clazz);
+                        return clazz;
+                    }
+                } catch (ClassNotFoundException e) {
+                    // Ignore
+                }
+            }
+
+            // (0.5) Permission to access this class when using a SecurityManager
+            if (securityManager != null) {
+                int i = name.lastIndexOf('.');
+                if (i >= 0) {
+                    try {
+                        securityManager.checkPackageAccess(name.substring(0,i));
+                    } catch (SecurityException se) {
+                        String error = sm.getString("webappClassLoader.restrictedPackage", name);
+                        log.info(error, se);
+                        throw new ClassNotFoundException(error, se);
+                    }
+                }
+            }
+
+            boolean delegateLoad = delegate || filter(name, true);
+
+            // (1) Delegate to our parent if requested
+            if (delegateLoad) {
+                if (log.isDebugEnabled())
+                    log.debug("  Delegating to parent classloader1 " + parent);
+                try {
+                    clazz = Class.forName(name, false, parent);
+                    if (clazz != null) {
+                        if (log.isDebugEnabled())
+                            log.debug("  Loading class from parent");
+                        if (resolve)
+                            resolveClass(clazz);
+                        return clazz;
+                    }
+                } catch (ClassNotFoundException e) {
+                    // Ignore
+                }
+            }
+
+            // (2) Search local repositories
+            if (log.isDebugEnabled())
+                log.debug("  Searching local repositories");
+            try {
+                clazz = findClass(name);
+                if (clazz != null) {
+                    if (log.isDebugEnabled())
+                        log.debug("  Loading class from local repository");
+                    if (resolve)
+                        resolveClass(clazz);
+                    return clazz;
+                }
+            } catch (ClassNotFoundException e) {
+                // Ignore
+            }
+
+            // (3) Delegate to parent unconditionally
+            if (!delegateLoad) {
+                if (log.isDebugEnabled())
+                    log.debug("  Delegating to parent classloader at end: " + parent);
+                try {
+                    clazz = Class.forName(name, false, parent);
+                    if (clazz != null) {
+                        if (log.isDebugEnabled())
+                            log.debug("  Loading class from parent");
+                        if (resolve)
+                            resolveClass(clazz);
+                        return clazz;
+                    }
+                } catch (ClassNotFoundException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        throw new ClassNotFoundException(name);
+    }
+
+
+    protected void checkStateForClassLoading(String className) throws ClassNotFoundException {
+        // It is not permitted to load new classes once the web application has
+        // been stopped.
+        try {
+            checkStateForResourceLoading(className);
+        } catch (IllegalStateException ise) {
+            throw new ClassNotFoundException(ise.getMessage(), ise);
+        }
+    }
+
+
+    protected void checkStateForResourceLoading(String resource) throws IllegalStateException {
+        // It is not permitted to load resources once the web application has
+        // been stopped.
+        if (!state.isAvailable()) {
+            String msg = sm.getString("webappClassLoader.stopped", resource);
+            IllegalStateException ise = new IllegalStateException(msg);
+            log.info(msg, ise);
+            throw ise;
+        }
+    }
+
+    /**
+     * Get the Permissions for a CodeSource.  If this instance
+     * of WebappClassLoaderBase is for a web application context,
+     * add read FilePermission for the appropriate resources.
+     *
+     * @param codeSource where the code was loaded from
+     * @return PermissionCollection for CodeSource
+     */
+    @Override
+    protected PermissionCollection getPermissions(CodeSource codeSource) {
+        String codeUrl = codeSource.getLocation().toString();
+        PermissionCollection pc;
+        if ((pc = loaderPC.get(codeUrl)) == null) {
+            pc = super.getPermissions(codeSource);
+            if (pc != null) {
+                for (Permission p : permissionList) {
+                    pc.add(p);
+                }
+                loaderPC.put(codeUrl,pc);
+            }
+        }
+        return pc;
+    }
+
+
+    @Override
+    public boolean check(Permission permission) {
+        if (!Globals.IS_SECURITY_ENABLED) {
+            return true;
+        }
+        Policy currentPolicy = Policy.getPolicy();
+        if (currentPolicy != null) {
+            URL contextRootUrl = resources.getResource("/").getCodeBase();
+            CodeSource cs = new CodeSource(contextRootUrl, (Certificate[]) null);
+            PermissionCollection pc = currentPolicy.getPermissions(cs);
+            if (pc.implies(permission)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Note that list of URLs returned by this method may not be complete. The
+     * web application class loader accesses class loader resources via the
+     * {@link WebResourceRoot} which supports the arbitrary mapping of
+     * additional files, directories and contents of JAR files under
+     * WEB-INF/classes. Any such resources will not be included in the URLs
+     * returned here.
+     */
+    @Override
+    public URL[] getURLs() {
+        ArrayList<URL> result = new ArrayList<>();
+        result.addAll(localRepositories);
+        result.addAll(Arrays.asList(super.getURLs()));
+        return result.toArray(new URL[0]);
+    }
+
+
+    // ------------------------------------------------------ Lifecycle Methods
+
+
+    /**
+     * Add a lifecycle event listener to this component.
+     *
+     * @param listener The listener to add
+     */
+    @Override
+    public void addLifecycleListener(LifecycleListener listener) {
+        // NOOP
+    }
+
+
+    /**
+     * Get the lifecycle listeners associated with this lifecycle. If this
+     * Lifecycle has no listeners registered, a zero-length array is returned.
+     */
+    @Override
+    public LifecycleListener[] findLifecycleListeners() {
+        return new LifecycleListener[0];
+    }
+
+
+    /**
+     * Remove a lifecycle event listener from this component.
+     *
+     * @param listener The listener to remove
+     */
+    @Override
+    public void removeLifecycleListener(LifecycleListener listener) {
+        // NOOP
+    }
+
+
+    /**
+     * Obtain the current state of the source component.
+     *
+     * @return The current state of the source component.
+     */
+    @Override
+    public LifecycleState getState() {
+        return state;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getStateName() {
+        return getState().toString();
+    }
+
+
+    @Override
+    public void init() {
+        state = LifecycleState.INITIALIZED;
+    }
+
+
+    /**
+     * Start the class loader.
+     *
+     * @exception LifecycleException if a lifecycle error occurs
+     */
+    @Override
+    public void start() throws LifecycleException {
+
+        state = LifecycleState.STARTING_PREP;
+
+        WebResource[] classesResources = resources.getResources("/WEB-INF/classes");
+        for (WebResource classes : classesResources) {
+            if (classes.isDirectory() && classes.canRead()) {
+                localRepositories.add(classes.getURL());
+            }
+        }
+        WebResource[] jars = resources.listResources("/WEB-INF/lib");
+        for (WebResource jar : jars) {
+            if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
+                localRepositories.add(jar.getURL());
+                jarModificationTimes.put(
+                        jar.getName(), Long.valueOf(jar.getLastModified()));
+            }
+        }
+
+        state = LifecycleState.STARTED;
+    }
+
+
+    /**
+     * Stop the class loader.
+     *
+     * @exception LifecycleException if a lifecycle error occurs
+     */
+    @Override
+    public void stop() throws LifecycleException {
+
+        state = LifecycleState.STOPPING_PREP;
+
+        // Clearing references should be done before setting started to
+        // false, due to possible side effects
+        clearReferences();
+
+        state = LifecycleState.STOPPING;
+
+        resourceEntries.clear();
+        jarModificationTimes.clear();
+        resources = null;
+
+        permissionList.clear();
+        loaderPC.clear();
+
+        state = LifecycleState.STOPPED;
+    }
+
+
+    @Override
+    public void destroy() {
+        state = LifecycleState.DESTROYING;
+
+        try {
+            super.close();
+        } catch (IOException ioe) {
+            log.warn(sm.getString("webappClassLoader.superCloseFail"), ioe);
+        }
+        state = LifecycleState.DESTROYED;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+    protected ClassLoader getJavaseClassLoader() {
+        return javaseClassLoader;
+    }
+
+    protected void setJavaseClassLoader(ClassLoader classLoader) {
+        if (classLoader == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("webappClassLoader.javaseClassLoaderNull"));
+        }
+        javaseClassLoader = classLoader;
+    }
+
+    /**
+     * Clear references.
+     */
+    protected void clearReferences() {
+
+        // If the JVM is shutting down, skip the memory leak checks
+        if (skipMemoryLeakChecksOnJvmShutdown
+            && !resources.getContext().getParent().getState().isAvailable()) {
+            // During reloading / redeployment the parent is expected to be
+            // available. Parent is not available so this might be a JVM
+            // shutdown.
+            try {
+                Thread dummyHook = new Thread();
+                Runtime.getRuntime().addShutdownHook(dummyHook);
+                Runtime.getRuntime().removeShutdownHook(dummyHook);
+            } catch (IllegalStateException ise) {
+                return;
+            }
+        }
+
+        if (!JreCompat.isGraalAvailable()) {
+            // De-register any remaining JDBC drivers
+            clearReferencesJdbc();
+        }
+
+        // Stop any threads the web application started
+        clearReferencesThreads();
+
+        // Clear any references retained in the serialization caches
+        if (clearReferencesObjectStreamClassCaches && !JreCompat.isGraalAvailable()) {
+            clearReferencesObjectStreamClassCaches();
+        }
+
+        // Check for leaks triggered by ThreadLocals loaded by this class loader
+        if (clearReferencesThreadLocals && !JreCompat.isGraalAvailable()) {
+            checkThreadLocalsForLeaks();
+        }
+
+        // Clear RMI Targets loaded by this class loader
+        if (clearReferencesRmiTargets) {
+            clearReferencesRmiTargets();
+        }
+
+         // Clear the IntrospectionUtils cache.
+        IntrospectionUtils.clear();
+
+        // Clear the classloader reference in common-logging
+        if (clearReferencesLogFactoryRelease) {
+            org.apache.juli.logging.LogFactory.release(this);
+        }
+
+        // Clear the classloader reference in the VM's bean introspector
+        java.beans.Introspector.flushCaches();
+
+        // Clear any custom URLStreamHandlers
+        TomcatURLStreamHandlerFactory.release(this);
+    }
+
+
+    /**
+     * Deregister any JDBC drivers registered by the webapp that the webapp
+     * forgot. This is made unnecessary complex because a) DriverManager
+     * checks the class loader of the calling class (it would be much easier
+     * if it checked the context class loader) b) using reflection would
+     * create a dependency on the DriverManager implementation which can,
+     * and has, changed.
+     *
+     * We can't just create an instance of JdbcLeakPrevention as it will be
+     * loaded by the common class loader (since it's .class file is in the
+     * $CATALINA_HOME/lib directory). This would fail DriverManager's check
+     * on the class loader of the calling class. So, we load the bytes via
+     * our parent class loader but define the class with this class loader
+     * so the JdbcLeakPrevention looks like a webapp class to the
+     * DriverManager.
+     *
+     * If only apps cleaned up after themselves...
+     */
+    private final void clearReferencesJdbc() {
+        // We know roughly how big the class will be (~ 1K) so allow 2k as a
+        // starting point
+        byte[] classBytes = new byte[2048];
+        int offset = 0;
+        try (InputStream is = getResourceAsStream(
+                "org/apache/catalina/loader/JdbcLeakPrevention.class")) {
+            int read = is.read(classBytes, offset, classBytes.length-offset);
+            while (read > -1) {
+                offset += read;
+                if (offset == classBytes.length) {
+                    // Buffer full - double size
+                    byte[] tmp = new byte[classBytes.length * 2];
+                    System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
+                    classBytes = tmp;
+                }
+                read = is.read(classBytes, offset, classBytes.length-offset);
+            }
+            Class<?> lpClass =
+                defineClass("org.apache.catalina.loader.JdbcLeakPrevention",
+                    classBytes, 0, offset, this.getClass().getProtectionDomain());
+            Object obj = lpClass.getConstructor().newInstance();
+            @SuppressWarnings("unchecked")
+            List<String> driverNames = (List<String>) obj.getClass().getMethod(
+                    "clearJdbcDriverRegistrations").invoke(obj);
+            for (String name : driverNames) {
+                log.warn(sm.getString("webappClassLoader.clearJdbc",
+                        getContextName(), name));
+            }
+        } catch (Exception e) {
+            // So many things to go wrong above...
+            Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
+            ExceptionUtils.handleThrowable(t);
+            log.warn(sm.getString(
+                    "webappClassLoader.jdbcRemoveFailed", getContextName()), t);
+        }
+    }
+
+
+    @SuppressWarnings("deprecation") // thread.stop()
+    private void clearReferencesThreads() {
+        Thread[] threads = getThreads();
+        List<Thread> threadsToStop = new ArrayList<>();
+
+        // Iterate over the set of threads
+        for (Thread thread : threads) {
+            if (thread != null) {
+                ClassLoader ccl = thread.getContextClassLoader();
+                if (ccl == this) {
+                    // Don't warn about this thread
+                    if (thread == Thread.currentThread()) {
+                        continue;
+                    }
+
+                    final String threadName = thread.getName();
+
+                    // JVM controlled threads
+                    ThreadGroup tg = thread.getThreadGroup();
+                    if (tg != null && JVM_THREAD_GROUP_NAMES.contains(tg.getName())) {
+                        // HttpClient keep-alive threads
+                        if (clearReferencesHttpClientKeepAliveThread &&
+                                threadName.equals("Keep-Alive-Timer")) {
+                            thread.setContextClassLoader(parent);
+                            log.debug(sm.getString("webappClassLoader.checkThreadsHttpClient"));
+                        }
+
+                        // Don't warn about remaining JVM controlled threads
+                        continue;
+                    }
+
+                    // Skip threads that have already died
+                    if (!thread.isAlive()) {
+                        continue;
+                    }
+
+                    // TimerThread can be stopped safely so treat separately
+                    // "java.util.TimerThread" in Sun/Oracle JDK
+                    // "java.util.Timer$TimerImpl" in Apache Harmony and in IBM JDK
+                    if (thread.getClass().getName().startsWith("java.util.Timer") &&
+                            clearReferencesStopTimerThreads) {
+                        clearReferencesStopTimerThread(thread);
+                        continue;
+                    }
+
+                    if (isRequestThread(thread)) {
+                        log.warn(sm.getString("webappClassLoader.stackTraceRequestThread",
+                                getContextName(), threadName, getStackTrace(thread)));
+                    } else {
+                        log.warn(sm.getString("webappClassLoader.stackTrace",
+                                getContextName(), threadName, getStackTrace(thread)));
+                    }
+
+                    // Don't try and stop the threads unless explicitly
+                    // configured to do so
+                    if (!clearReferencesStopThreads) {
+                        continue;
+                    }
+
+                    // If the thread has been started via an executor, try
+                    // shutting down the executor
+                    boolean usingExecutor = false;
+                    try {
+
+                        // Runnable wrapped by Thread
+                        // "target" in Sun/Oracle JDK
+                        // "runnable" in IBM JDK
+                        // "action" in Apache Harmony
+                        Object target = null;
+                        for (String fieldName : new String[] { "target", "runnable", "action" }) {
+                            try {
+                                Field targetField = thread.getClass().getDeclaredField(fieldName);
+                                targetField.setAccessible(true);
+                                target = targetField.get(thread);
+                                break;
+                            } catch (NoSuchFieldException nfe) {
+                                continue;
+                            }
+                        }
+
+                        // "java.util.concurrent" code is in public domain,
+                        // so all implementations are similar
+                        if (target != null && target.getClass().getCanonicalName() != null &&
+                                target.getClass().getCanonicalName().equals(
+                                        "java.util.concurrent.ThreadPoolExecutor.Worker")) {
+                            Field executorField = target.getClass().getDeclaredField("this$0");
+                            executorField.setAccessible(true);
+                            Object executor = executorField.get(target);
+                            if (executor instanceof ThreadPoolExecutor) {
+                                ((ThreadPoolExecutor) executor).shutdownNow();
+                                usingExecutor = true;
+                            }
+                        }
+                    } catch (SecurityException | NoSuchFieldException | IllegalArgumentException |
+                            IllegalAccessException e) {
+                        log.warn(sm.getString("webappClassLoader.stopThreadFail",
+                                thread.getName(), getContextName()), e);
+                    }
+
+                    // Stopping an executor automatically interrupts the
+                    // associated threads. For non-executor threads, interrupt
+                    // them here.
+                    if (!usingExecutor && !thread.isInterrupted()) {
+                        thread.interrupt();
+                    }
+
+                    // Threads are expected to take a short time to stop after
+                    // being interrupted. Make a note of all threads that are
+                    // expected to stop to enable them to be checked at the end
+                    // of this method.
+                    threadsToStop.add(thread);
+                }
+            }
+        }
+
+        // If thread stopping is enabled, threads should have been stopped above
+        // when the executor was shut down or the thread was interrupted but
+        // that depends on the thread correctly handling the interrupt. Check
+        // each thread and if any are still running give all threads up to a
+        // total of 2 seconds to shutdown.
+        int count = 0;
+        for (Thread t : threadsToStop) {
+            while (t.isAlive() && count < 100) {
+                try {
+                    Thread.sleep(20);
+                } catch (InterruptedException e) {
+                    // Quit the while loop
+                    break;
+                }
+                count++;
+            }
+            if (t.isAlive()) {
+                // This method is deprecated and for good reason. This is
+                // very risky code but is the only option at this point.
+                // A *very* good reason for apps to do this clean-up
+                // themselves.
+                t.stop();
+            }
+        }
+    }
+
+
+    /*
+     * Look at a threads stack trace to see if it is a request thread or not. It
+     * isn't perfect, but it should be good-enough for most cases.
+     */
+    private boolean isRequestThread(Thread thread) {
+
+        StackTraceElement[] elements = thread.getStackTrace();
+
+        if (elements == null || elements.length == 0) {
+            // Must have stopped already. Too late to ignore it. Assume not a
+            // request processing thread.
+            return false;
+        }
+
+        // Step through the methods in reverse order looking for calls to any
+        // CoyoteAdapter method. All request threads will have this unless
+        // Tomcat has been heavily modified - in which case there isn't much we
+        // can do.
+        for (int i = 0; i < elements.length; i++) {
+            StackTraceElement element = elements[elements.length - (i+1)];
+            if ("org.apache.catalina.connector.CoyoteAdapter".equals(
+                    element.getClassName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    private void clearReferencesStopTimerThread(Thread thread) {
+
+        // Need to get references to:
+        // in Sun/Oracle JDK:
+        // - newTasksMayBeScheduled field (in java.util.TimerThread)
+        // - queue field
+        // - queue.clear()
+        // in IBM JDK, Apache Harmony:
+        // - cancel() method (in java.util.Timer$TimerImpl)
+
+        try {
+
+            try {
+                Field newTasksMayBeScheduledField =
+                    thread.getClass().getDeclaredField("newTasksMayBeScheduled");
+                newTasksMayBeScheduledField.setAccessible(true);
+                Field queueField = thread.getClass().getDeclaredField("queue");
+                queueField.setAccessible(true);
+
+                Object queue = queueField.get(thread);
+
+                Method clearMethod = queue.getClass().getDeclaredMethod("clear");
+                clearMethod.setAccessible(true);
+
+                synchronized(queue) {
+                    newTasksMayBeScheduledField.setBoolean(thread, false);
+                    clearMethod.invoke(queue);
+                    // In case queue was already empty. Should only be one
+                    // thread waiting but use notifyAll() to be safe.
+                    queue.notifyAll();
+                }
+
+            }catch (NoSuchFieldException nfe){
+                Method cancelMethod = thread.getClass().getDeclaredMethod("cancel");
+                synchronized(thread) {
+                    cancelMethod.setAccessible(true);
+                    cancelMethod.invoke(thread);
+                }
+            }
+
+            log.warn(sm.getString("webappClassLoader.warnTimerThread",
+                    getContextName(), thread.getName()));
+
+        } catch (Exception e) {
+            // So many things to go wrong above...
+            Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
+            ExceptionUtils.handleThrowable(t);
+            log.warn(sm.getString(
+                    "webappClassLoader.stopTimerThreadFail",
+                    thread.getName(), getContextName()), t);
+        }
+    }
+
+    private void checkThreadLocalsForLeaks() {
+        Thread[] threads = getThreads();
+
+        try {
+            // Make the fields in the Thread class that store ThreadLocals
+            // accessible
+            Field threadLocalsField =
+                Thread.class.getDeclaredField("threadLocals");
+            threadLocalsField.setAccessible(true);
+            Field inheritableThreadLocalsField =
+                Thread.class.getDeclaredField("inheritableThreadLocals");
+            inheritableThreadLocalsField.setAccessible(true);
+            // Make the underlying array of ThreadLoad.ThreadLocalMap.Entry objects
+            // accessible
+            Class<?> tlmClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
+            Field tableField = tlmClass.getDeclaredField("table");
+            tableField.setAccessible(true);
+            Method expungeStaleEntriesMethod = tlmClass.getDeclaredMethod("expungeStaleEntries");
+            expungeStaleEntriesMethod.setAccessible(true);
+
+            for (Thread thread : threads) {
+                Object threadLocalMap;
+                if (thread != null) {
+
+                    // Clear the first map
+                    threadLocalMap = threadLocalsField.get(thread);
+                    if (null != threadLocalMap) {
+                        expungeStaleEntriesMethod.invoke(threadLocalMap);
+                        checkThreadLocalMapForLeaks(threadLocalMap, tableField);
+                    }
+
+                    // Clear the second map
+                    threadLocalMap = inheritableThreadLocalsField.get(thread);
+                    if (null != threadLocalMap) {
+                        expungeStaleEntriesMethod.invoke(threadLocalMap);
+                        checkThreadLocalMapForLeaks(threadLocalMap, tableField);
+                    }
+                }
+            }
+        } catch (Throwable t) {
+            JreCompat jreCompat = JreCompat.getInstance();
+            if (jreCompat.isInstanceOfInaccessibleObjectException(t)) {
+                // Must be running on Java 9 without the necessary command line
+                // options.
+                log.warn(sm.getString("webappClassLoader.addExportsThreadLocal"));
+            } else {
+                ExceptionUtils.handleThrowable(t);
+                log.warn(sm.getString(
+                        "webappClassLoader.checkThreadLocalsForLeaksFail",
+                        getContextName()), t);
+            }
+        }
+    }
+
+
+    /**
+     * Analyzes the given thread local map object. Also pass in the field that
+     * points to the internal table to save re-calculating it on every
+     * call to this method.
+     */
+    private void checkThreadLocalMapForLeaks(Object map,
+            Field internalTableField) throws IllegalAccessException,
+            NoSuchFieldException {
+        if (map != null) {
+            Object[] table = (Object[]) internalTableField.get(map);
+            if (table != null) {
+                for (Object obj : table) {
+                    if (obj != null) {
+                        boolean keyLoadedByWebapp = false;
+                        boolean valueLoadedByWebapp = false;
+                        // Check the key
+                        Object key = ((Reference<?>) obj).get();
+                        if (this.equals(key) || loadedByThisOrChild(key)) {
+                            keyLoadedByWebapp = true;
+                        }
+                        // Check the value
+                        Field valueField =
+                                obj.getClass().getDeclaredField("value");
+                        valueField.setAccessible(true);
+                        Object value = valueField.get(obj);
+                        if (this.equals(value) || loadedByThisOrChild(value)) {
+                            valueLoadedByWebapp = true;
+                        }
+                        if (keyLoadedByWebapp || valueLoadedByWebapp) {
+                            Object[] args = new Object[5];
+                            args[0] = getContextName();
+                            if (key != null) {
+                                args[1] = getPrettyClassName(key.getClass());
+                                try {
+                                    args[2] = key.toString();
+                                } catch (Exception e) {
+                                    log.warn(sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaks.badKey",
+                                            args[1]), e);
+                                    args[2] = sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaks.unknown");
+                                }
+                            }
+                            if (value != null) {
+                                args[3] = getPrettyClassName(value.getClass());
+                                try {
+                                    args[4] = value.toString();
+                                } catch (Exception e) {
+                                    log.warn(sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaks.badValue",
+                                            args[3]), e);
+                                    args[4] = sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaks.unknown");
+                                }
+                            }
+                            if (valueLoadedByWebapp) {
+                                log.error(sm.getString(
+                                        "webappClassLoader.checkThreadLocalsForLeaks",
+                                        args));
+                            } else if (value == null) {
+                                if (log.isDebugEnabled()) {
+                                    log.debug(sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaksNull",
+                                            args));
+                                }
+                            } else {
+                                if (log.isDebugEnabled()) {
+                                    log.debug(sm.getString(
+                                            "webappClassLoader.checkThreadLocalsForLeaksNone",
+                                            args));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private String getPrettyClassName(Class<?> clazz) {
+        String name = clazz.getCanonicalName();
+        if (name==null){
+            name = clazz.getName();
+        }
+        return name;
+    }
+
+    private String getStackTrace(Thread thread) {
+        StringBuilder builder = new StringBuilder();
+        for (StackTraceElement ste : thread.getStackTrace()) {
+            builder.append("\n ").append(ste);
+        }
+        return builder.toString();
+    }
+
+    /**
+     * @param o object to test, may be null
+     * @return <code>true</code> if o has been loaded by the current classloader
+     * or one of its descendants.
+     */
+    private boolean loadedByThisOrChild(Object o) {
+        if (o == null) {
+            return false;
+        }
+
+        Class<?> clazz;
+        if (o instanceof Class) {
+            clazz = (Class<?>) o;
+        } else {
+            clazz = o.getClass();
+        }
+
+        ClassLoader cl = clazz.getClassLoader();
+        while (cl != null) {
+            if (cl == this) {
+                return true;
+            }
+            cl = cl.getParent();
+        }
+
+        if (o instanceof Collection<?>) {
+            Iterator<?> iter = ((Collection<?>) o).iterator();
+            try {
+                while (iter.hasNext()) {
+                    Object entry = iter.next();
+                    if (loadedByThisOrChild(entry)) {
+                        return true;
+                    }
+                }
+            } catch (ConcurrentModificationException e) {
+                log.warn(sm.getString(
+                        "webappClassLoader.loadedByThisOrChildFail", clazz.getName(), getContextName()),
+                        e);
+            }
+        }
+        return false;
+    }
+
+    /*
+     * Get the set of current threads as an array.
+     */
+    private Thread[] getThreads() {
+        // Get the current thread group
+        ThreadGroup tg = Thread.currentThread().getThreadGroup();
+        // Find the root thread group
+        try {
+            while (tg.getParent() != null) {
+                tg = tg.getParent();
+            }
+        } catch (SecurityException se) {
+            String msg = sm.getString(
+                    "webappClassLoader.getThreadGroupError", tg.getName());
+            if (log.isDebugEnabled()) {
+                log.debug(msg, se);
+            } else {
+                log.warn(msg);
+            }
+        }
+
+        int threadCountGuess = tg.activeCount() + 50;
+        Thread[] threads = new Thread[threadCountGuess];
+        int threadCountActual = tg.enumerate(threads);
+        // Make sure we don't miss any threads
+        while (threadCountActual == threadCountGuess) {
+            threadCountGuess *=2;
+            threads = new Thread[threadCountGuess];
+            // Note tg.enumerate(Thread[]) silently ignores any threads that
+            // can't fit into the array
+            threadCountActual = tg.enumerate(threads);
+        }
+
+        return threads;
+    }
+
+
+    /**
+     * This depends on the internals of the Sun JVM so it does everything by
+     * reflection.
+     */
+    private void clearReferencesRmiTargets() {
+        try {
+            // Need access to the ccl field of sun.rmi.transport.Target to find
+            // the leaks
+            Class<?> objectTargetClass =
+                Class.forName("sun.rmi.transport.Target");
+            Field cclField = objectTargetClass.getDeclaredField("ccl");
+            cclField.setAccessible(true);
+            // Need access to the stub field to report the leaks
+            Field stubField = objectTargetClass.getDeclaredField("stub");
+            stubField.setAccessible(true);
+
+            // Clear the objTable map
+            Class<?> objectTableClass = Class.forName("sun.rmi.transport.ObjectTable");
+            Field objTableField = objectTableClass.getDeclaredField("objTable");
+            objTableField.setAccessible(true);
+            Object objTable = objTableField.get(null);
+            if (objTable == null) {
+                return;
+            }
+            Field tableLockField = objectTableClass.getDeclaredField("tableLock");
+            tableLockField.setAccessible(true);
+            Object tableLock = tableLockField.get(null);
+
+            synchronized (tableLock) {
+                // Iterate over the values in the table
+                if (objTable instanceof Map<?,?>) {
+                    Iterator<?> iter = ((Map<?,?>) objTable).values().iterator();
+                    while (iter.hasNext()) {
+                        Object obj = iter.next();
+                        Object cclObject = cclField.get(obj);
+                        if (this == cclObject) {
+                            iter.remove();
+                            Object stubObject = stubField.get(obj);
+                            log.error(sm.getString("webappClassLoader.clearRmi",
+                                    stubObject.getClass().getName(), stubObject));
+                        }
+                    }
+                }
+
+                // Clear the implTable map
+                Field implTableField = objectTableClass.getDeclaredField("implTable");
+                implTableField.setAccessible(true);
+                Object implTable = implTableField.get(null);
+                if (implTable == null) {
+                    return;
+                }
+
+                // Iterate over the values in the table
+                if (implTable instanceof Map<?,?>) {
+                    Iterator<?> iter = ((Map<?,?>) implTable).values().iterator();
+                    while (iter.hasNext()) {
+                        Object obj = iter.next();
+                        Object cclObject = cclField.get(obj);
+                        if (this == cclObject) {
+                            iter.remove();
+                        }
+                    }
+                }
+            }
+        } catch (ClassNotFoundException e) {
+            log.info(sm.getString("webappClassLoader.clearRmiInfo",
+                    getContextName()), e);
+        } catch (SecurityException | NoSuchFieldException | IllegalArgumentException |
+                IllegalAccessException e) {
+            log.warn(sm.getString("webappClassLoader.clearRmiFail",
+                    getContextName()), e);
+        } catch (Exception e) {
+            JreCompat jreCompat = JreCompat.getInstance();
+            if (jreCompat.isInstanceOfInaccessibleObjectException(e)) {
+                // Must be running on Java 9 without the necessary command line
+                // options.
+                log.warn(sm.getString("webappClassLoader.addExportsRmi"));
+            } else {
+                // Re-throw all other exceptions
+                throw e;
+            }
+        }
+    }
+
+
+    private void clearReferencesObjectStreamClassCaches() {
+        try {
+            Class<?> clazz = Class.forName("java.io.ObjectStreamClass$Caches");
+            clearCache(clazz, "localDescs");
+            clearCache(clazz, "reflectors");
+        } catch (ReflectiveOperationException | SecurityException | ClassCastException e) {
+            log.warn(sm.getString(
+                    "webappClassLoader.clearObjectStreamClassCachesFail", getContextName()), e);
+        }
+    }
+
+
+    private void clearCache(Class<?> target, String mapName)
+            throws ReflectiveOperationException, SecurityException, ClassCastException {
+        Field f = target.getDeclaredField(mapName);
+        f.setAccessible(true);
+        Map<?,?> map = (Map<?,?>) f.get(null);
+        Iterator<?> keys = map.keySet().iterator();
+        while (keys.hasNext()) {
+            Object key = keys.next();
+            if (key instanceof Reference) {
+                Object clazz = ((Reference<?>) key).get();
+                if (loadedByThisOrChild(clazz)) {
+                    keys.remove();
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Find specified class in local repositories.
+     *
+     * @param name The binary name of the class to be loaded
+     *
+     * @return the loaded class, or null if the class isn't found
+     */
+    protected Class<?> findClassInternal(String name) {
+
+        checkStateForResourceLoading(name);
+
+        if (name == null) {
+            return null;
+        }
+        String path = binaryNameToPath(name, true);
+
+        ResourceEntry entry = resourceEntries.get(path);
+        WebResource resource = null;
+
+        if (entry == null) {
+            resource = resources.getClassLoaderResource(path);
+
+            if (!resource.exists()) {
+                return null;
+            }
+
+            entry = new ResourceEntry();
+            entry.lastModified = resource.getLastModified();
+
+            // Add the entry in the local resource repository
+            synchronized (resourceEntries) {
+                // Ensures that all the threads which may be in a race to load
+                // a particular class all end up with the same ResourceEntry
+                // instance
+                ResourceEntry entry2 = resourceEntries.get(path);
+                if (entry2 == null) {
+                    resourceEntries.put(path, entry);
+                } else {
+                    entry = entry2;
+                }
+            }
+        }
+
+        Class<?> clazz = entry.loadedClass;
+        if (clazz != null)
+            return clazz;
+
+        synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
+            clazz = entry.loadedClass;
+            if (clazz != null)
+                return clazz;
+
+            if (resource == null) {
+                resource = resources.getClassLoaderResource(path);
+            }
+
+            if (!resource.exists()) {
+                return null;
+            }
+
+            byte[] binaryContent = resource.getContent();
+            if (binaryContent == null) {
+                // Something went wrong reading the class bytes (and will have
+                // been logged at debug level).
+                return null;
+            }
+            Manifest manifest = resource.getManifest();
+            URL codeBase = resource.getCodeBase();
+            Certificate[] certificates = resource.getCertificates();
+
+            if (transformers.size() > 0) {
+                // If the resource is a class just being loaded, decorate it
+                // with any attached transformers
+
+                // Ignore leading '/' and trailing CLASS_FILE_SUFFIX
+                // Should be cheaper than replacing '.' by '/' in class name.
+                String internalName = path.substring(1, path.length() - CLASS_FILE_SUFFIX.length());
+
+                for (ClassFileTransformer transformer : this.transformers) {
+                    try {
+                        byte[] transformed = transformer.transform(
+                                this, internalName, null, null, binaryContent);
+                        if (transformed != null) {
+                            binaryContent = transformed;
+                        }
+                    } catch (IllegalClassFormatException e) {
+                        log.error(sm.getString("webappClassLoader.transformError", name), e);
+                        return null;
+                    }
+                }
+            }
+
+            // Looking up the package
+            String packageName = null;
+            int pos = name.lastIndexOf('.');
+            if (pos != -1)
+                packageName = name.substring(0, pos);
+
+            Package pkg = null;
+
+            if (packageName != null) {
+                pkg = getPackage(packageName);
+                // Define the package (if null)
+                if (pkg == null) {
+                    try {
+                        if (manifest == null) {
+                            definePackage(packageName, null, null, null, null, null, null, null);
+                        } else {
+                            definePackage(packageName, manifest, codeBase);
+                        }
+                    } catch (IllegalArgumentException e) {
+                        // Ignore: normal error due to dual definition of package
+                    }
+                    pkg = getPackage(packageName);
+                }
+            }
+
+            if (securityManager != null) {
+
+                // Checking sealing
+                if (pkg != null) {
+                    boolean sealCheck = true;
+                    if (pkg.isSealed()) {
+                        sealCheck = pkg.isSealed(codeBase);
+                    } else {
+                        sealCheck = (manifest == null) || !isPackageSealed(packageName, manifest);
+                    }
+                    if (!sealCheck)
+                        throw new SecurityException
+                            ("Sealing violation loading " + name + " : Package "
+                             + packageName + " is sealed.");
+                }
+
+            }
+
+            try {
+                clazz = defineClass(name, binaryContent, 0,
+                        binaryContent.length, new CodeSource(codeBase, certificates));
+            } catch (UnsupportedClassVersionError ucve) {
+                throw new UnsupportedClassVersionError(
+                        ucve.getLocalizedMessage() + " " +
+                        sm.getString("webappClassLoader.wrongVersion",
+                                name));
+            }
+            entry.loadedClass = clazz;
+        }
+
+        return clazz;
+    }
+
+
+    private String binaryNameToPath(String binaryName, boolean withLeadingSlash) {
+        // 1 for leading '/', 6 for ".class"
+        StringBuilder path = new StringBuilder(7 + binaryName.length());
+        if (withLeadingSlash) {
+            path.append('/');
+        }
+        path.append(binaryName.replace('.', '/'));
+        path.append(CLASS_FILE_SUFFIX);
+        return path.toString();
+    }
+
+
+    private String nameToPath(String name) {
+        if (name.startsWith("/")) {
+            return name;
+        }
+        StringBuilder path = new StringBuilder(
+                1 + name.length());
+        path.append('/');
+        path.append(name);
+        return path.toString();
+    }
+
+
+    /**
+     * Returns true if the specified package name is sealed according to the
+     * given manifest.
+     *
+     * @param name Path name to check
+     * @param man Associated manifest
+     * @return <code>true</code> if the manifest associated says it is sealed
+     */
+    protected boolean isPackageSealed(String name, Manifest man) {
+
+        String path = name.replace('.', '/') + '/';
+        Attributes attr = man.getAttributes(path);
+        String sealed = null;
+        if (attr != null) {
+            sealed = attr.getValue(Name.SEALED);
+        }
+        if (sealed == null) {
+            if ((attr = man.getMainAttributes()) != null) {
+                sealed = attr.getValue(Name.SEALED);
+            }
+        }
+        return "true".equalsIgnoreCase(sealed);
+
+    }
+
+
+    /**
+     * Finds the class with the given name if it has previously been
+     * loaded and cached by this class loader, and return the Class object.
+     * If this class has not been cached, return <code>null</code>.
+     *
+     * @param name The binary name of the resource to return
+     * @return a loaded class
+     */
+    protected Class<?> findLoadedClass0(String name) {
+
+        String path = binaryNameToPath(name, true);
+
+        ResourceEntry entry = resourceEntries.get(path);
+        if (entry != null) {
+            return entry.loadedClass;
+        }
+        return null;
+    }
+
+
+    /**
+     * Refresh the system policy file, to pick up eventual changes.
+     */
+    protected void refreshPolicy() {
+
+        try {
+            // The policy file may have been modified to adjust
+            // permissions, so we're reloading it when loading or
+            // reloading a Context
+            Policy policy = Policy.getPolicy();
+            policy.refresh();
+        } catch (AccessControlException e) {
+            // Some policy files may restrict this, even for the core,
+            // so this exception is ignored
+        }
+
+    }
+
+
+    /**
+     * Filter classes.
+     *
+     * @param name class name
+     * @param isClassName <code>true</code> if name is a class name,
+     *                <code>false</code> if name is a resource name
+     * @return <code>true</code> if the class should be filtered
+     */
+    protected boolean filter(String name, boolean isClassName) {
+
+        if (name == null)
+            return false;
+
+        char ch;
+        if (name.startsWith("jakarta")) {
+            /* 7 == length("jakarta") */
+            if (name.length() == 7) {
+                return false;
+            }
+            ch = name.charAt(7);
+            if (isClassName && ch == '.') {
+                /* 8 == length("jakarta.") */
+                if (name.startsWith("servlet.jsp.jstl.", 8)) {
+                    return false;
+                }
+                if (name.startsWith("el.", 8) ||
+                    name.startsWith("servlet.", 8) ||
+                    name.startsWith("websocket.", 8) ||
+                    name.startsWith("security.auth.message.", 8)) {
+                    return true;
+                }
+            } else if (!isClassName && ch == '/') {
+                /* 8 == length("jakarta/") */
+                if (name.startsWith("servlet/jsp/jstl/", 8)) {
+                    return false;
+                }
+                if (name.startsWith("el/", 8) ||
+                    name.startsWith("servlet/", 8) ||
+                    name.startsWith("websocket/", 8) ||
+                    name.startsWith("security/auth/message/", 8)) {
+                    return true;
+                }
+            }
+        } else if (name.startsWith("javax")) {
+            /* 5 == length("javax") */
+            if (name.length() == 5) {
+                return false;
+            }
+            ch = name.charAt(5);
+            if (isClassName && ch == '.') {
+                /* 6 == length("javax.") */
+                if (name.startsWith("websocket.", 6)) {
+                    return true;
+                }
+            } else if (!isClassName && ch == '/') {
+                /* 6 == length("javax/") */
+                if (name.startsWith("websocket/", 6)) {
+                    return true;
+                }
+            }
+        } else if (name.startsWith("org")) {
+            /* 3 == length("org") */
+            if (name.length() == 3) {
+                return false;
+            }
+            ch = name.charAt(3);
+            if (isClassName && ch == '.') {
+                /* 4 == length("org.") */
+                if (name.startsWith("apache.", 4)) {
+                    /* 11 == length("org.apache.") */
+                    if (name.startsWith("tomcat.jdbc.", 11)) {
+                        return false;
+                    }
+                    if (name.startsWith("el.", 11) ||
+                        name.startsWith("catalina.", 11) ||
+                        name.startsWith("jasper.", 11) ||
+                        name.startsWith("juli.", 11) ||
+                        name.startsWith("tomcat.", 11) ||
+                        name.startsWith("naming.", 11) ||
+                        name.startsWith("coyote.", 11)) {
+                        return true;
+                    }
+                }
+            } else if (!isClassName && ch == '/') {
+                /* 4 == length("org/") */
+                if (name.startsWith("apache/", 4)) {
+                    /* 11 == length("org/apache/") */
+                    if (name.startsWith("tomcat/jdbc/", 11)) {
+                        return false;
+                    }
+                    if (name.startsWith("el/", 11) ||
+                        name.startsWith("catalina/", 11) ||
+                        name.startsWith("jasper/", 11) ||
+                        name.startsWith("juli/", 11) ||
+                        name.startsWith("tomcat/", 11) ||
+                        name.startsWith("naming/", 11) ||
+                        name.startsWith("coyote/", 11)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+
+    @Override
+    protected void addURL(URL url) {
+        super.addURL(url);
+        hasExternalRepositories = true;
+    }
+
+
+    @Override
+    public String getWebappName() {
+        return getContextName();
+    }
+
+
+    @Override
+    public String getHostName() {
+        if (resources != null) {
+            Container host = resources.getContext().getParent();
+            if (host != null) {
+                return host.getName();
+            }
+        }
+        return null;
+    }
+
+
+    @Override
+    public String getServiceName() {
+        if (resources != null) {
+            Container host = resources.getContext().getParent();
+            if (host != null) {
+                Container engine = host.getParent();
+                if (engine != null) {
+                    return engine.getName();
+                }
+            }
+        }
+        return null;
+    }
+
+
+    @Override
+    public boolean hasLoggingConfig() {
+        if (Globals.IS_SECURITY_ENABLED) {
+            Boolean result = AccessController.doPrivileged(new PrivilegedHasLoggingConfig());
+            return result.booleanValue();
+        } else {
+            return findResource("logging.properties") != null;
+        }
+    }
+
+
+    private class PrivilegedHasLoggingConfig implements PrivilegedAction<Boolean> {
+
+        @Override
+        public Boolean run() {
+            return Boolean.valueOf(findResource("logging.properties") != null);
+        }
+    }
+
+
+    private static class CombinedEnumeration implements Enumeration<URL> {
+
+        private final Enumeration<URL>[] sources;
+        private int index = 0;
+
+        public CombinedEnumeration(Enumeration<URL> enum1, Enumeration<URL> enum2) {
+            @SuppressWarnings("unchecked")
+            Enumeration<URL>[] sources = new Enumeration[] { enum1, enum2 };
+            this.sources = sources;
+        }
+
+
+        @Override
+        public boolean hasMoreElements() {
+            return inc();
+        }
+
+
+        @Override
+        public URL nextElement() {
+            if (inc()) {
+                return sources[index].nextElement();
+            }
+            throw new NoSuchElementException();
+        }
+
+
+        private boolean inc() {
+            while (index < sources.length) {
+                if (sources[index].hasMoreElements()) {
+                    return true;
+                }
+                index++;
+            }
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/apache/cxf/jaxb/JAXBContextInitializer.java b/jakarta/src/patch/java/org/apache/cxf/jaxb/JAXBContextInitializer.java
new file mode 100644
index 0000000..37b2d07
--- /dev/null
+++ b/jakarta/src/patch/java/org/apache/cxf/jaxb/JAXBContextInitializer.java
@@ -0,0 +1,611 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.jaxb;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAttribute;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlSeeAlso;
+import jakarta.xml.bind.annotation.XmlTransient;
+import jakarta.xml.bind.annotation.XmlType;
+import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.jaxb.JAXBUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.ASMHelper;
+import org.apache.cxf.common.util.ASMHelper.ClassWriter;
+import org.apache.cxf.common.util.ASMHelper.MethodVisitor;
+import org.apache.cxf.common.util.ASMHelper.Opcodes;
+import org.apache.cxf.common.util.ReflectionUtil;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.service.ServiceModelVisitor;
+import org.apache.cxf.service.model.MessageInfo;
+import org.apache.cxf.service.model.MessagePartInfo;
+import org.apache.cxf.service.model.OperationInfo;
+import org.apache.cxf.service.model.ServiceInfo;
+import org.apache.cxf.service.model.UnwrappedOperationInfo;
+
+/**
+ * Walks the service model and sets up the classes for the context.
+ */
+class JAXBContextInitializer extends ServiceModelVisitor {
+    private static final Logger LOG = LogUtils.getL7dLogger(JAXBContextInitializer.class);
+    private Set<Class<?>> classes;
+    private Collection<Object> typeReferences;
+    private Set<Class<?>> globalAdapters = new HashSet<>();
+    private Map<String, Object> unmarshallerProperties;
+
+    JAXBContextInitializer(ServiceInfo serviceInfo,
+                                  Set<Class<?>> classes,
+                                  Collection<Object> typeReferences,
+                                  Map<String, Object> unmarshallerProperties) {
+        super(serviceInfo);
+        this.classes = classes;
+        this.typeReferences = typeReferences;
+        this.unmarshallerProperties = unmarshallerProperties;
+    }
+
+    @Override
+    public void begin(MessagePartInfo part) {
+        Class<?> clazz = part.getTypeClass();
+        if (clazz == null) {
+            return;
+        }
+
+        if (Exception.class.isAssignableFrom(clazz)) {
+            //exceptions are handled special, make sure we mark it
+            part.setProperty(JAXBDataBinding.class.getName() + ".CUSTOM_EXCEPTION",
+                             Boolean.TRUE);
+        }
+        boolean isFromWrapper = part.getMessageInfo().getOperation().isUnwrapped();
+        if (isFromWrapper
+            && !Boolean.TRUE.equals(part.getProperty("messagepart.isheader"))) {
+            UnwrappedOperationInfo uop = (UnwrappedOperationInfo)part.getMessageInfo().getOperation();
+            OperationInfo op = uop.getWrappedOperation();
+            MessageInfo inf = null;
+            if (uop.getInput() == part.getMessageInfo()) {
+                inf = op.getInput();
+            } else if (uop.getOutput() == part.getMessageInfo()) {
+                inf = op.getOutput();
+            }
+            if (inf != null
+                && inf.getFirstMessagePart().getTypeClass() != null) {
+                //if the wrapper has a type class, we don't need to do anything
+                //as everything would have been discovered when walking the
+                //wrapper type (unless it's a header which wouldn't be in the wrapper)
+                return;
+            }
+        }
+        if (isFromWrapper
+            && clazz.isArray()
+            && !Byte.TYPE.equals(clazz.getComponentType())) {
+            clazz = clazz.getComponentType();
+        }
+
+        Annotation[] a = (Annotation[])part.getProperty("parameter.annotations");
+        checkForAdapter(clazz, a);
+
+        Type genericType = (Type) part.getProperty("generic.type");
+        if (genericType != null) {
+            boolean isList = Collection.class.isAssignableFrom(clazz);
+            if (isFromWrapper) {
+                if (genericType instanceof Class
+                    && ((Class<?>)genericType).isArray()) {
+                    Class<?> cl2 = (Class<?>)genericType;
+                    if (cl2.isArray()
+                        && !Byte.TYPE.equals(cl2.getComponentType())) {
+                        genericType = cl2.getComponentType();
+                    }
+                    addType(genericType);
+                } else if (!isList) {
+                    addType(genericType);
+                }
+            } else {
+                addType(genericType, true);
+            }
+
+            if (isList
+                && genericType instanceof ParameterizedType) {
+                ParameterizedType pt = (ParameterizedType) genericType;
+                if (pt.getActualTypeArguments().length > 0
+                    && pt.getActualTypeArguments()[0] instanceof Class) {
+
+                    Class<? extends Object> arrayCls =
+                        Array.newInstance((Class<?>) pt.getActualTypeArguments()[0], 0).getClass();
+                    clazz = arrayCls;
+                    part.setTypeClass(clazz);
+                    if (isFromWrapper) {
+                        addType(clazz.getComponentType(), true);
+                    }
+                } else if (pt.getActualTypeArguments().length > 0
+                    && pt.getActualTypeArguments()[0] instanceof GenericArrayType) {
+                    GenericArrayType gat = (GenericArrayType)pt.getActualTypeArguments()[0];
+                    gat.getGenericComponentType();
+                    Class<? extends Object> arrayCls =
+                        Array.newInstance((Class<?>) gat.getGenericComponentType(), 0).getClass();
+                    clazz = Array.newInstance(arrayCls, 0).getClass();
+                    part.setTypeClass(clazz);
+                    if (isFromWrapper) {
+                        addType(clazz.getComponentType(), true);
+                    }
+                }
+            }
+            if (isFromWrapper && isList) {
+                clazz = null;
+            }
+        }
+        if (clazz != null) {
+            if (!isFromWrapper
+                && clazz.getAnnotation(XmlRootElement.class) == null
+                && clazz.getAnnotation(XmlType.class) != null
+                && StringUtils.isEmpty(clazz.getAnnotation(XmlType.class).name())) {
+                Object ref = createTypeReference(part.getName(), clazz);
+                if (ref != null) {
+                    typeReferences.add(ref);
+                }
+            }
+
+            addClass(clazz);
+        }
+    }
+
+    private void checkForAdapter(Class<?> clazz, Annotation[] anns) {
+        if (anns != null) {
+            for (Annotation a : anns) {
+                if (XmlJavaTypeAdapter.class.isAssignableFrom(a.annotationType())) {
+                    Type t = Utils.getTypeFromXmlAdapter((XmlJavaTypeAdapter)a);
+                    if (t != null) {
+                        addType(t);
+                    }
+                }
+            }
+        }
+        XmlJavaTypeAdapter xjta = clazz.getAnnotation(XmlJavaTypeAdapter.class);
+        if (xjta != null) {
+            Type t = Utils.getTypeFromXmlAdapter(xjta);
+            if (t != null) {
+                addType(t);
+            }
+        }
+        if (clazz.getPackage() != null) {
+            XmlJavaTypeAdapters adapt = clazz.getPackage().getAnnotation(XmlJavaTypeAdapters.class);
+            if (adapt != null) {
+                for (XmlJavaTypeAdapter a : adapt.value()) {
+                    globalAdapters.add(a.type());
+                }
+                for (XmlJavaTypeAdapter a : adapt.value()) {
+                    Type t = Utils.getTypeFromXmlAdapter(a);
+                    if (t != null) {
+                        addType(t);
+                    }
+                }
+            }
+        }
+    }
+
+    private void addType(Type cls) {
+        addType(cls, false);
+    }
+    private void addType(Type cls, boolean allowArray) {
+        if (cls instanceof Class) {
+            if (globalAdapters.contains(cls)) {
+                return;
+            }
+            if (((Class<?>)cls).isArray() && !allowArray) {
+                addClass(((Class<?>)cls).getComponentType());
+            } else {
+                addClass((Class<?>)cls);
+            }
+        } else if (cls instanceof ParameterizedType) {
+            final ParameterizedType parameterizedType = (ParameterizedType)cls;
+            addType(parameterizedType.getRawType());
+            if (!parameterizedType.getRawType().equals(Enum.class)) {
+                for (Type t2 : parameterizedType.getActualTypeArguments()) {
+                    if (shouldTypeBeAdded(t2, parameterizedType)) {
+                        addType(t2);
+                    }
+                }
+            }
+        } else if (cls instanceof GenericArrayType) {
+            Class<?> ct;
+            GenericArrayType gt = (GenericArrayType)cls;
+            Type componentType = gt.getGenericComponentType();
+            if (componentType instanceof Class) {
+                ct = (Class<?>)componentType;
+            } else if (componentType instanceof ParameterizedType) {
+                final ParameterizedType parameterizedType = (ParameterizedType)componentType;
+                final Type rawType = parameterizedType.getRawType();
+                if (rawType instanceof Class) {
+                    ct = (Class<?>)rawType;
+                } else {
+                    throw new IllegalArgumentException("Unable to determine type for " + rawType);
+                }
+                if (!parameterizedType.getRawType().equals(Enum.class)) {
+                    for (Type t2 : parameterizedType.getActualTypeArguments()) {
+                        if (shouldTypeBeAdded(t2, parameterizedType)) {
+                            addType(t2);
+                        }
+                    }
+                }
+            } else {
+                TypeVariable<?> tv = (TypeVariable<?>)componentType;
+                Type[] bounds = tv.getBounds();
+                if (bounds != null && bounds.length == 1) {
+                    if (bounds[0] instanceof Class) {
+                        ct = (Class<?>)bounds[0];
+                    } else {
+                        throw new IllegalArgumentException("Unable to determine type for: " + tv);
+                    }
+                } else {
+                    throw new IllegalArgumentException("Unable to determine type for: " + tv);
+                }
+            }
+            ct = Array.newInstance(ct, 0).getClass();
+
+            addClass(ct);
+        } else if (cls instanceof WildcardType) {
+            for (Type t : ((WildcardType)cls).getUpperBounds()) {
+                addType(t);
+            }
+            for (Type t : ((WildcardType)cls).getLowerBounds()) {
+                addType(t);
+            }
+        } else if (cls instanceof TypeVariable) {
+            for (Type t : ((TypeVariable<?>)cls).getBounds()) {
+                addType(t);
+            }
+        }
+    }
+
+    private boolean shouldTypeBeAdded(final Type t2, final ParameterizedType parameterizedType) {
+        if (!(t2 instanceof TypeVariable)) {
+            return true;
+        }
+        TypeVariable<?> typeVariable = (TypeVariable<?>) t2;
+        final Type[] bounds = typeVariable.getBounds();
+        for (Type bound : bounds) {
+            if (bound instanceof ParameterizedType && bound.equals(parameterizedType)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void addClass(Class<?> claz) {
+        if (Throwable.class.isAssignableFrom(claz)) {
+            if (!Throwable.class.equals(claz)
+                && !Exception.class.equals(claz)) {
+                walkReferences(claz);
+            }
+            addClass(String.class);
+        } else if (claz.getName().startsWith("java.")
+            || claz.getName().startsWith("javax.")
+            || claz.getName().startsWith("jakarta.")) {
+            return;
+        } else {
+            Class<?> cls = JAXBUtils.getValidClass(claz);
+            if (cls == null
+                && ReflectionUtil.getDeclaredConstructors(claz).length > 0
+                && !Modifier.isAbstract(claz.getModifiers())) {
+                if (LOG.isLoggable(Level.INFO)) {
+                    LOG.info("Class " + claz.getName() + " does not have a default constructor which JAXB requires.");
+                }
+                //there is no init(), but other constructors
+                Object factory = createFactory(claz, ReflectionUtil.getDeclaredConstructors(claz)[0]);
+                unmarshallerProperties.put("com.sun.xml.bind.ObjectFactory", factory);
+                cls = claz;
+            }
+            if (null != cls) {
+                if (classes.contains(cls)) {
+                    return;
+                }
+
+                if (!cls.isInterface()) {
+                    classes.add(cls);
+                }
+
+                XmlSeeAlso xsa = cls.getAnnotation(XmlSeeAlso.class);
+                if (xsa != null) {
+                    for (Class<?> c : xsa.value()) {
+                        addClass(c);
+                    }
+                }
+                XmlJavaTypeAdapter xjta = cls.getAnnotation(XmlJavaTypeAdapter.class);
+                if (xjta != null) {
+                    //has an adapter.   We need to inspect the adapter and then
+                    //return as the adapter will handle the superclass
+                    //and interfaces and such
+                    Type t = Utils.getTypeFromXmlAdapter(xjta);
+                    if (t != null) {
+                        addType(t);
+                    }
+                    return;
+                }
+
+                if (cls.getSuperclass() != null) {
+                    //JAXB should do this, but it doesn't always.
+                    //in particular, older versions of jaxb don't
+                    addClass(cls.getSuperclass());
+                }
+
+                if (!cls.isInterface()) {
+                    walkReferences(cls);
+                }
+            }
+        }
+    }
+
+    private void walkReferences(Class<?> cls) {
+        if (cls == null) {
+            return;
+        }
+        if (cls.getName().startsWith("java.")
+            || cls.getName().startsWith("javax.")
+            || cls.getName().startsWith("jakarta.")) {
+            return;
+        }
+        //walk the public fields/methods to try and find all the classes. JAXB will only load the
+        //EXACT classes in the fields/methods if they are in a different package. Thus,
+        //subclasses won't be found and the xsi:type stuff won't work at all.
+        //We'll grab the public field/method types and then add the ObjectFactory stuff
+        //as well as look for jaxb.index files in those packages.
+
+        XmlAccessType accessType = Utils.getXmlAccessType(cls);
+
+        if (accessType != XmlAccessType.PROPERTY) {   // only look for fields if we are instructed to
+            //fields are accessible even if not public, must look at the declared fields
+            //then walk to parents declared fields, etc...
+            Field[] fields = ReflectionUtil.getDeclaredFields(cls);
+            for (Field f : fields) {
+                if (isFieldAccepted(f, accessType)) {
+                    XmlJavaTypeAdapter xjta = Utils.getFieldXJTA(f);
+                    if (xjta != null) {
+                        Type t = Utils.getTypeFromXmlAdapter(xjta);
+                        if (t != null) {
+                            addType(t);
+                            continue;
+                        }
+                    }
+                    addType(f.getGenericType());
+                }
+            }
+            walkReferences(cls.getSuperclass());
+        }
+
+        if (accessType != XmlAccessType.FIELD) {   // only look for methods if we are instructed to
+            Method[] methods = ReflectionUtil.getDeclaredMethods(cls);
+            for (Method m : methods) {
+                if (isMethodAccepted(m, accessType)) {
+                    XmlJavaTypeAdapter xjta = Utils.getMethodXJTA(m);
+                    if (xjta != null) {
+                        Type t = Utils.getTypeFromXmlAdapter(xjta);
+                        if (t != null) {
+                            addType(t);
+                            continue;
+                        }
+                    }
+                    addType(m.getGenericReturnType());
+                    for (Type t : m.getGenericParameterTypes()) {
+                        addType(t);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks if the field is accepted as a JAXB property.
+     */
+    static boolean isFieldAccepted(Field field, XmlAccessType accessType) {
+        // We only accept non static fields which are not marked @XmlTransient or has transient modifier
+        if (field.isAnnotationPresent(XmlTransient.class)
+            || Modifier.isTransient(field.getModifiers())) {
+            return false;
+        }
+        if (Modifier.isStatic(field.getModifiers())) {
+            return field.isAnnotationPresent(XmlAttribute.class);
+        }
+        if (accessType == XmlAccessType.PUBLIC_MEMBER
+            && !Modifier.isPublic(field.getModifiers())) {
+            return false;
+        }
+        if (accessType == XmlAccessType.NONE
+            || accessType == XmlAccessType.PROPERTY) {
+            return checkJaxbAnnotation(field.getAnnotations());
+        }
+        return true;
+    }
+
+    /**
+     * Checks if the method is accepted as a JAXB property getter.
+     */
+    static boolean isMethodAccepted(Method method, XmlAccessType accessType) {
+        // We only accept non static property getters which are not marked @XmlTransient
+        if (Modifier.isStatic(method.getModifiers())
+                || method.isAnnotationPresent(XmlTransient.class)
+                || !Modifier.isPublic(method.getModifiers())
+                || "getClass".equals(method.getName())) {
+            return false;
+        }
+
+        // must not have parameters and return type must not be void
+        if (method.getReturnType() == Void.class || method.getReturnType() == Void.TYPE
+            || method.getParameterTypes().length != 0
+            || (method.getDeclaringClass().equals(Throwable.class)
+            && !("getMessage".equals(method.getName())))
+            || !(method.getName().startsWith("get")
+                    || method.getName().startsWith("is"))) {
+            return false;
+        }
+        int beginIndex = 3;
+        if (method.getName().startsWith("is")) {
+            beginIndex = 2;
+        }
+
+        Method setter = null;
+        try {
+            setter = method.getDeclaringClass()
+                .getMethod("set" + method.getName().substring(beginIndex),
+                           new Class[] {method.getReturnType()});
+        } catch (Exception e) {
+            //getter, but no setter
+        }
+        if (setter != null) {
+            if (setter.isAnnotationPresent(XmlTransient.class)
+                || !Modifier.isPublic(setter.getModifiers())) {
+                return false;
+            }
+        } else if (!Collection.class.isAssignableFrom(method.getReturnType())
+            && !Throwable.class.isAssignableFrom(method.getDeclaringClass())) {
+            //no setter, it's not a collection (thus getter().add(...)), and
+            //not an Exception,
+            return false;
+        }
+
+        if (accessType == XmlAccessType.NONE
+            || accessType == XmlAccessType.FIELD) {
+            return checkJaxbAnnotation(method.getAnnotations());
+        }
+        return true;
+    }
+
+    /**
+     * Checks if there are JAXB annotations among the annotations of the class member.
+     * @param annotations the array of annotations from the class member
+     * @return true if JAXB annotations are present, false otherwise
+     */
+    static boolean checkJaxbAnnotation(Annotation[] annotations) {
+        // must check if there are any jaxb annotations
+        Package jaxbAnnotationsPackage = XmlElement.class.getPackage();
+        for (Annotation annotation : annotations) {
+            if (annotation.annotationType().getPackage() == jaxbAnnotationsPackage) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * The TypeReference class is a sun specific class that is found in two different
+     * locations depending on environment. In IBM JDK the class is not available at all.
+     * So we have to load it at runtime.
+     *
+     * @param n
+     * @param cls
+     * @return initiated TypeReference
+     */
+    private static Object createTypeReference(QName n, Class<?> cls) {
+        Class<?> refClass = null;
+        try {
+            refClass = ClassLoaderUtils.loadClass("com.sun.xml.bind.api.TypeReference",
+                                                  JAXBContextInitializer.class);
+        } catch (Throwable ex) {
+            try {
+                refClass = ClassLoaderUtils.loadClass("com.sun.xml.internal.bind.api.TypeReference",
+                                                      JAXBContextInitializer.class);
+            } catch (Throwable ex2) {
+                //ignore
+            }
+        }
+        if (refClass != null) {
+            try {
+                return refClass.getConstructor(QName.class, Type.class, new Annotation[0].getClass()) //NOPMD
+                    .newInstance(n, cls, new Annotation[0]);
+            } catch (Throwable e) {
+                //ignore
+            }
+        }
+        return null;
+    }
+
+    @SuppressWarnings("unused")
+    private Object createFactory(Class<?> cls, Constructor<?> contructor) {
+        String newClassName = cls.getName() + "Factory";
+        ASMHelper helper = new ASMHelper();
+        ClassWriter cw = helper.createClassWriter();
+        MethodVisitor mv;
+
+        cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER,
+                 ASMHelper.periodToSlashes(newClassName), null, "java/lang/Object", null);
+
+        cw.visitSource(cls.getSimpleName() + "Factory" + ".java", null);
+
+        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
+        mv.visitCode();
+        mv.visitVarInsn(Opcodes.ALOAD, 0);
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+        mv.visitInsn(Opcodes.RETURN);
+        mv.visitMaxs(1, 1);
+        mv.visitEnd();
+
+        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "create" + cls.getSimpleName(),
+                            "()L" + ASMHelper.periodToSlashes(cls.getName()) + ";", null, null);
+        mv.visitCode();
+        String name = cls.getName().replace(".", "/");
+        mv.visitTypeInsn(Opcodes.NEW, name);
+        mv.visitInsn(Opcodes.DUP);
+        StringBuilder paraString = new StringBuilder(32).append("(");
+
+        for (Class<?> paraClass : contructor.getParameterTypes()) {
+            mv.visitInsn(Opcodes.ACONST_NULL);
+            paraString.append("Ljava/lang/Object;");
+        }
+        paraString.append(")V");
+
+        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, name, "<init>", paraString.toString(), false);
+
+        mv.visitInsn(Opcodes.ARETURN);
+        mv.visitMaxs(1, 1);
+        mv.visitEnd();
+
+        cw.visitEnd();
+        Class<?> factoryClass = helper.loadClass(newClassName, cls, cw.toByteArray());
+        try {
+            return factoryClass.newInstance();
+        } catch (Exception e) {
+           //ignore
+        }
+        return null;
+    }
+
+
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/apache/openjpa/enhance/PCClassFileTransformer.java b/jakarta/src/patch/java/org/apache/openjpa/enhance/PCClassFileTransformer.java
new file mode 100644
index 0000000..ee2cdac
--- /dev/null
+++ b/jakarta/src/patch/java/org/apache/openjpa/enhance/PCClassFileTransformer.java
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.openjpa.enhance;
+
+import java.io.ByteArrayInputStream;
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.security.AccessController;
+import java.security.ProtectionDomain;
+import java.util.Set;
+
+import org.apache.openjpa.conf.OpenJPAConfiguration;
+import org.apache.openjpa.lib.log.Log;
+import org.apache.openjpa.lib.util.J2DoPrivHelper;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.lib.util.Options;
+import org.apache.openjpa.meta.MetaDataRepository;
+import org.apache.openjpa.util.GeneralException;
+
+import serp.bytecode.BCClass;
+import serp.bytecode.Project;
+import serp.bytecode.lowlevel.ConstantPoolTable;
+
+
+/**
+ * Transformer that makes persistent classes implement the
+ * {@link PersistenceCapable} interface at runtime.
+ *
+ * @author Abe White
+ */
+public class PCClassFileTransformer
+    implements ClassFileTransformer {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (PCClassFileTransformer.class);
+
+    private final MetaDataRepository _repos;
+    private final PCEnhancer.Flags _flags;
+    private final ClassLoader _tmpLoader;
+    private final Log _log;
+    private final Set _names;
+    private boolean _transforming = false;
+
+    /**
+     * Constructor.
+     *
+     * @param repos metadata repository to use internally
+     * @param opts enhancer configuration options
+     * @param loader temporary class loader for loading intermediate classes
+     */
+    public PCClassFileTransformer(MetaDataRepository repos, Options opts,
+        ClassLoader loader) {
+        this(repos, toFlags(opts), loader, opts.removeBooleanProperty
+            ("scanDevPath", "ScanDevPath", false));
+    }
+
+    /**
+     * Create enhancer flags from the given options.
+     */
+    private static PCEnhancer.Flags toFlags(Options opts) {
+        PCEnhancer.Flags flags = new PCEnhancer.Flags();
+        flags.addDefaultConstructor = opts.removeBooleanProperty
+            ("addDefaultConstructor", "AddDefaultConstructor",
+                flags.addDefaultConstructor);
+        flags.enforcePropertyRestrictions = opts.removeBooleanProperty
+            ("enforcePropertyRestrictions", "EnforcePropertyRestrictions",
+                flags.enforcePropertyRestrictions);
+        return flags;
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param repos metadata repository to use internally
+     * @param flags enhancer configuration
+     * @param tmpLoader temporary class loader for loading intermediate classes
+     * @param devscan whether to scan the dev classpath for persistent types
+     * if none are configured
+     */
+    public PCClassFileTransformer(MetaDataRepository repos,
+        PCEnhancer.Flags flags, ClassLoader tmpLoader, boolean devscan) {
+        _repos = repos;
+        _tmpLoader = tmpLoader;
+
+        _log = repos.getConfiguration().
+            getLog(OpenJPAConfiguration.LOG_ENHANCE);
+        _flags = flags;
+
+        _names = repos.getPersistentTypeNames(devscan, tmpLoader);
+        if (_names == null && _log.isInfoEnabled())
+            _log.info(_loc.get("runtime-enhance-pcclasses"));
+    }
+
+    @Override
+    public byte[] transform(ClassLoader loader, String className,
+        Class redef, ProtectionDomain domain, byte[] bytes)
+        throws IllegalClassFormatException {
+        if (loader == _tmpLoader)
+            return null;
+
+        // JDK bug -- OPENJPA-1676
+        if (className == null) {
+            return null;
+        }
+        // prevent re-entrant calls, which can occur if the enhancing
+        // loader is used to also load OpenJPA libraries; this is to prevent
+        // recursive enhancement attempts for internal openjpa libraries
+        if (_transforming)
+            return null;
+
+        _transforming = true;
+
+        return transform0(className, redef, bytes);
+    }
+
+    /**
+     * We have to split the transform method into two methods to avoid
+     * ClassCircularityError when executing method using pure-JIT JVMs
+     * such as JRockit.
+     */
+    private byte[] transform0(String className, Class redef, byte[] bytes)
+        throws IllegalClassFormatException {
+
+        byte[] returnBytes = null;
+        try {
+            Boolean enhance = needsEnhance(className, redef, bytes);
+            if (enhance != null && _log.isTraceEnabled())
+                _log.trace(_loc.get("needs-runtime-enhance", className,
+                    enhance));
+            if (enhance != Boolean.TRUE)
+                return null;
+
+            ClassLoader oldLoader = AccessController.doPrivileged(J2DoPrivHelper.getContextClassLoaderAction());
+            AccessController.doPrivileged(J2DoPrivHelper.setContextClassLoaderAction(_tmpLoader));
+            try {
+                PCEnhancer enhancer = new PCEnhancer(_repos.getConfiguration(),
+                        new Project().loadClass(new ByteArrayInputStream(bytes),
+                                _tmpLoader), _repos);
+                enhancer.setAddDefaultConstructor(_flags.addDefaultConstructor);
+                enhancer.setEnforcePropertyRestrictions
+                        (_flags.enforcePropertyRestrictions);
+
+                if (enhancer.run() == PCEnhancer.ENHANCE_NONE)
+                    return null;
+                BCClass pcb = enhancer.getPCBytecode();
+                returnBytes = AsmAdaptor.toByteArray(pcb, pcb.toByteArray());
+                return returnBytes;
+            } finally {
+                AccessController.doPrivileged(J2DoPrivHelper.setContextClassLoaderAction(oldLoader));
+            }
+        } catch (Throwable t) {
+            _log.warn(_loc.get("cft-exception-thrown", className), t);
+            if (t instanceof RuntimeException)
+                throw (RuntimeException) t;
+            if (t instanceof IllegalClassFormatException)
+                throw (IllegalClassFormatException) t;
+            throw new GeneralException(t);
+        } finally {
+            _transforming = false;
+            if (returnBytes != null && _log.isTraceEnabled())
+                _log.trace(_loc.get("runtime-enhance-complete", className,
+                    bytes.length, returnBytes.length));
+        }
+    }
+
+    /**
+     * Return whether the given class needs enhancement.
+     */
+    private Boolean needsEnhance(String clsName, Class redef, byte[] bytes) {
+        if (redef != null) {
+            Class[] intfs = redef.getInterfaces();
+            for (int i = 0; i < intfs.length; i++)
+                if (PersistenceCapable.class.getName().
+                    equals(intfs[i].getName()))
+                    return Boolean.valueOf(!isEnhanced(bytes));
+            return null;
+        }
+
+        if (_names != null) {
+            if (_names.contains(clsName.replace('/', '.')))
+                return Boolean.valueOf(!isEnhanced(bytes));
+            return null;
+        }
+
+        if (clsName.startsWith("java/") || clsName.startsWith("javax/") || clsName.startsWith("jakarta/"))
+            return null;
+        if (isEnhanced(bytes))
+            return Boolean.FALSE;
+
+        try {
+            Class c = Class.forName(clsName.replace('/', '.'), false,
+                _tmpLoader);
+            if (_repos.getMetaData(c, null, false) != null)
+                return Boolean.TRUE;
+            return null;
+        } catch (ClassNotFoundException cnfe) {
+            // cannot load the class: this might mean that it is a proxy
+            // or otherwise inaccessible class which can't be an entity
+            return Boolean.FALSE;
+        } catch (LinkageError cce) {
+            // this can happen if we are loading classes that this
+            // class depends on; these will never be enhanced anyway
+            return Boolean.FALSE;
+        } catch (RuntimeException re) {
+            throw re;
+        } catch (Throwable t) {
+            throw new GeneralException(t);
+        }
+    }
+
+    /**
+     * Analyze the bytecode to see if the given class definition implements
+     * {@link PersistenceCapable}.
+     */
+    private static boolean isEnhanced(byte[] b) {
+        if (AsmAdaptor.use())
+        {
+            return AsmAdaptor.isEnhanced(b);
+        }
+
+        ConstantPoolTable table = new ConstantPoolTable(b);
+        int idx = table.getEndIndex();
+
+        idx += 6; // skip access, cls, super
+        int ifaces = table.readUnsignedShort(idx);
+        int clsEntry, utfEntry;
+        String name;
+        for (int i = 0; i < ifaces; i++) {
+            idx += 2;
+            clsEntry = table.readUnsignedShort(idx);
+            utfEntry = table.readUnsignedShort(table.get(clsEntry));
+            name = table.readString(table.get(utfEntry));
+            if ("org/apache/openjpa/enhance/PersistenceCapable".equals(name))
+                return true;
+        }
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/apache/openjpa/lib/meta/ClassMetaDataIterator.java b/jakarta/src/patch/java/org/apache/openjpa/lib/meta/ClassMetaDataIterator.java
new file mode 100644
index 0000000..b972512
--- /dev/null
+++ b/jakarta/src/patch/java/org/apache/openjpa/lib/meta/ClassMetaDataIterator.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.openjpa.lib.meta;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.openjpa.lib.util.ClassUtil;
+import org.apache.openjpa.lib.util.J2DoPrivHelper;
+import org.apache.openjpa.lib.util.MultiClassLoader;
+
+/**
+ * Iterator over all metadata resources that might contain the
+ * metadata for a given class, starting with the most general. Assumes that
+ * package-level resources are named "package.&lt;suffix&gt;".
+ *
+ * @author Abe White
+ */
+public class ClassMetaDataIterator implements MetaDataIterator {
+
+    private final ClassLoader _loader;
+    private final List<String> _locs;
+    private int _loc = -1;
+    private final List<URL> _urls = new ArrayList<>(3);
+    private int _url = -1;
+
+    /**
+     * Constructor; supply the class whose metadata to find, the suffix
+     * of metadata files, and whether to parse top-down or bottom-up.
+     */
+    public ClassMetaDataIterator(Class<?> cls, String suffix, boolean topDown) {
+        this(cls, suffix, null, topDown);
+    }
+
+    /**
+     * Constructor; supply the class whose metadata to find, the suffix
+     * of metadata files, and whether to parse top-down or bottom-up.
+     */
+    public ClassMetaDataIterator(Class<?> cls, String suffix,
+            ClassLoader loader, boolean topDown) {
+        // skip classes that can't have metadata
+        if (cls != null && (cls.isPrimitive()
+            || cls.getName().startsWith("java.")
+            || cls.getName().startsWith("javax.")
+            || cls.getName().startsWith("jakarta."))) {
+            _loader = null;
+            _locs = Collections.emptyList();
+            return;
+        }
+
+        if (loader == null) {
+            MultiClassLoader multi = AccessController
+                .doPrivileged(J2DoPrivHelper.newMultiClassLoaderAction());
+            multi.addClassLoader(MultiClassLoader.SYSTEM_LOADER);
+            multi.addClassLoader(MultiClassLoader.THREAD_LOADER);
+            multi.addClassLoader(getClass().getClassLoader());
+            if (cls != null)
+            {
+                ClassLoader clsLoader = (ClassLoader)
+                    AccessController.doPrivileged(
+                        J2DoPrivHelper.getClassLoaderAction(cls));
+                if (clsLoader != null)
+                    multi.addClassLoader(clsLoader);
+            }
+            loader = multi;
+        }
+        _loader = loader;
+
+        // collect the set of all possible metadata locations; start with
+        // system locations
+        _locs = new ArrayList<>();
+        _locs.add("META-INF/package" + suffix);
+        _locs.add("WEB-INF/package" + suffix);
+        _locs.add("package" + suffix);
+
+        // put this legacy location at the end regardless of whether we're
+        // going top down or bottom up so we don't have to parse it as often
+        // during testing
+        if (!topDown)
+            _locs.add("system" + suffix);
+
+        if (cls != null) {
+            // also check:
+            // 1. for each package from the top down to cls' package:
+            // <path>/package<suffix>
+            // <path>/<package-name><suffix> (legacy support)
+            // <path>/../<package-name><suffix> (legacy support)
+            // 2. <path>/<class-name><suffix>
+            String pkg = ClassUtil.getPackageName(cls).replace('.', '/');
+            if (pkg.length() > 0) {
+                int idx, start = 0;
+                String pkgName, path, upPath = "";
+                do {
+                    idx = pkg.indexOf('/', start);
+                    if (idx == -1) {
+                        pkgName = (start == 0) ? pkg : pkg.substring(start);
+                        path = pkg + "/";
+                    } else {
+                        pkgName = pkg.substring(start, idx);
+                        path = pkg.substring(0, idx + 1);
+                    }
+
+                    _locs.add(path + "package" + suffix);
+                    _locs.add(path + pkgName + suffix); // legacy
+                    _locs.add(upPath + pkgName + suffix); // legacy
+                    if (idx == -1)
+                        _locs.add(path + ClassUtil.getClassName(cls) + suffix);
+
+                    start = idx + 1;
+                    upPath = path;
+                }
+                while (idx != -1);
+            } else {
+                // <class-name><suffix> for top-level classes
+                _locs.add(cls.getName() + suffix);
+            }
+        }
+        if (topDown)
+            _locs.add("system" + suffix); // legacy
+        else
+            Collections.reverse(_locs);
+    }
+
+    @Override
+    public boolean hasNext() throws IOException {
+        Enumeration<URL> e;
+        while (_url + 1 >= _urls.size()) {
+            if (++_loc >= _locs.size())
+                return false;
+
+            _url = -1;
+            _urls.clear();
+            try {
+                e = AccessController.doPrivileged(
+                    J2DoPrivHelper.getResourcesAction(
+                        _loader, _locs.get(_loc)));
+            } catch (PrivilegedActionException pae) {
+                throw (IOException) pae.getException();
+            }
+            while (e.hasMoreElements())
+                _urls.add(e.nextElement());
+        }
+        return true;
+    }
+
+    @Override
+    public URL next() throws IOException {
+        if (!hasNext())
+            throw new NoSuchElementException();
+        return _urls.get(++_url);
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+        if (_url == -1 || _url >= _urls.size())
+            throw new IllegalStateException();
+        try {
+            return AccessController.doPrivileged(
+                J2DoPrivHelper.openStreamAction(_urls.get(_url)));
+        } catch (PrivilegedActionException pae) {
+            throw (IOException) pae.getException();
+        }
+    }
+
+    @Override
+    public File getFile() throws IOException {
+        if (_url == -1 || _url >= _urls.size())
+            throw new IllegalStateException();
+        File file = new File(URLDecoder.decode((_urls.get(_url)).getFile()));
+        return ((AccessController.doPrivileged(
+            J2DoPrivHelper.existsAction(file))).booleanValue()) ? file:null;
+    }
+
+    @Override
+    public void close() {
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/apache/openjpa/lib/util/TemporaryClassLoader.java b/jakarta/src/patch/java/org/apache/openjpa/lib/util/TemporaryClassLoader.java
new file mode 100644
index 0000000..dc5a93c
--- /dev/null
+++ b/jakarta/src/patch/java/org/apache/openjpa/lib/util/TemporaryClassLoader.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.openjpa.lib.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import serp.bytecode.lowlevel.ConstantPoolTable;
+
+/**
+ * ClassLoader implementation that allows classes to be temporarily
+ * loaded and then thrown away. Useful for the enhancer to be able
+ * to run against a class without first loading(and thus polluting)
+ * the parent ClassLoader.
+ *
+ * @author Marc Prud'hommeaux
+ */
+public class TemporaryClassLoader extends ClassLoader {
+
+    public TemporaryClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    @Override
+    public Class loadClass(String name) throws ClassNotFoundException {
+        return loadClass(name, false);
+    }
+
+    @Override
+    protected Class loadClass(String name, boolean resolve)
+        throws ClassNotFoundException {
+        // see if we've already loaded it
+        Class c = findLoadedClass(name);
+        if (c != null)
+            return c;
+
+        // bug #283. defer to system if the name is a protected name.
+        // "sun." is required for JDK 1.4, which has an access check for
+        // sun.reflect.GeneratedSerializationConstructorAccessor1
+        if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("jakarta.")
+            || name.startsWith("sun.") || name.startsWith("jdk.")) {
+            return Class.forName(name, resolve, getClass().getClassLoader());
+        }
+
+        String resourceName = name.replace('.', '/') + ".class";
+        InputStream resource = getResourceAsStream(resourceName);
+        if (resource == null) {
+            throw new ClassNotFoundException(name);
+        }
+
+        ByteArrayOutputStream bout = new ByteArrayOutputStream();
+        byte[] b = new byte[1024];
+        try {
+            for (int n = 0; (n = resource.read(b, 0, b.length)) != -1;
+                bout.write(b, 0, n))
+                ;
+            byte[] classBytes = bout.toByteArray();
+            // To avoid classloader issues with the JVM (Sun and IBM), we
+            // will not load Enums via the TemporaryClassLoader either.
+            // Reference JIRA Issue OPENJPA-646 for more information.
+            if (isAnnotation(classBytes) || isEnum(classBytes)) {
+                try {
+                    Class<?> frameworkClass = Class.forName(name, resolve,
+                            getClass().getClassLoader());
+                    return frameworkClass;
+                } catch (ClassNotFoundException e) {
+                    // OPENJPA-1121 continue, as it must be a user-defined class
+                }
+            }
+
+            try {
+                return defineClass(name, classBytes, 0, classBytes.length);
+            } catch (SecurityException e) {
+                // possible prohibited package: defer to the parent
+                return super.loadClass(name, resolve);
+            }
+        } catch (IOException ioe) {
+            // defer to the parent
+            return super.loadClass(name, resolve);
+        }
+    }
+
+    /**
+     * Fast-parse the given class bytecode to determine if it is an
+     * annotation class.
+     */
+    private static boolean isAnnotation(byte[] b) {
+        if (JavaVersions.VERSION < 5)
+            return false;
+        int idx = ConstantPoolTable.getEndIndex(b);
+        int access = ConstantPoolTable.readUnsignedShort(b, idx);
+        return (access & 0x2000) != 0; // access constant for annotation type
+    }
+
+    /**
+     * Fast-parse the given class bytecode to determine if it is an
+     * enum class.
+     */
+    private static boolean isEnum(byte[] b) {
+        if (JavaVersions.VERSION < 5)
+            return false;
+        int idx = ConstantPoolTable.getEndIndex(b);
+        int access = ConstantPoolTable.readUnsignedShort(b, idx);
+        return (access & 0x4000) != 0; // access constant for enum type
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/apache/openjpa/meta/AbstractMetaDataDefaults.java b/jakarta/src/patch/java/org/apache/openjpa/meta/AbstractMetaDataDefaults.java
new file mode 100644
index 0000000..03cd4fc
--- /dev/null
+++ b/jakarta/src/patch/java/org/apache/openjpa/meta/AbstractMetaDataDefaults.java
@@ -0,0 +1,422 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.openjpa.meta;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.PrivilegedActionException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.openjpa.enhance.PCRegistry;
+import org.apache.openjpa.lib.log.Log;
+import org.apache.openjpa.lib.util.Localizer;
+import org.apache.openjpa.util.OpenJPAException;
+import org.apache.openjpa.util.UserException;
+
+/**
+ * Abstract implementation provides a set of generic utilities for detecting
+ * persistence meta-data of Field/Member. Also provides bean-style properties
+ * such as access style or identity type to be used by default when such
+ * information is not derivable from available meta-data.
+ *
+ * @author Abe White
+ * @author Pinaki Poddar
+ */
+public abstract class AbstractMetaDataDefaults
+    implements MetaDataDefaults {
+
+    private static final Localizer _loc = Localizer.forPackage
+        (AbstractMetaDataDefaults.class);
+
+    private int _access = AccessCode.FIELD;
+    private int _identity = ClassMetaData.ID_UNKNOWN;
+    private boolean _ignore = true;
+    private boolean _interface = true;
+    private boolean _pcRegistry = true;
+    private int _callback = CALLBACK_RETHROW;
+    private boolean _unwrapped = false;
+
+    /**
+     * Whether to attempt to use the information from registered classes
+     * to populate metadata defaults. Defaults to true.
+     */
+    public boolean getUsePCRegistry() {
+        return _pcRegistry;
+    }
+
+    /**
+     * Whether to attempt to use the information from registered classes
+     * to populate metadata defaults. Defaults to true.
+     */
+    public void setUsePCRegistry(boolean pcRegistry) {
+        _pcRegistry = pcRegistry;
+    }
+
+    /**
+     * The default access type for base classes with ACCESS_UNKNOWN.
+     * ACCESS_FIELD by default.
+     */
+    @Override
+    public int getDefaultAccessType() {
+        return _access;
+    }
+
+    /**
+     * The default access type for base classes with ACCESS_UNKNOWN.
+     * ACCESS_FIELD by default.
+     */
+    public void setDefaultAccessType(int access) {
+        _access = access;
+    }
+
+    /**
+     * The default identity type for unmapped classes without primary
+     * key fields. ID_UNKNOWN by default.
+     */
+    @Override
+    public int getDefaultIdentityType() {
+        return _identity;
+    }
+
+    /**
+     * The default identity type for unmapped classes without primary
+     * key fields. ID_UNKNOWN by default.
+     */
+    public void setDefaultIdentityType(int identity) {
+        _identity = identity;
+    }
+
+    @Override
+    public int getCallbackMode() {
+        return _callback;
+    }
+
+    public void setCallbackMode(int mode) {
+        _callback = mode;
+    }
+
+    public void setCallbackMode(int mode, boolean on) {
+        if (on)
+            _callback |= mode;
+        else
+            _callback &= ~mode;
+    }
+
+    @Override
+    public boolean getCallbacksBeforeListeners(int type) {
+        return false;
+    }
+
+    @Override
+    public boolean isDeclaredInterfacePersistent() {
+        return _interface;
+    }
+
+    public void setDeclaredInterfacePersistent(boolean pers) {
+        _interface = pers;
+    }
+
+    @Override
+    public boolean isDataStoreObjectIdFieldUnwrapped() {
+        return _unwrapped;
+    }
+
+    public void setDataStoreObjectIdFieldUnwrapped(boolean unwrapped) {
+        _unwrapped = unwrapped;
+    }
+
+    public boolean getIgnoreNonPersistent() {
+        return _ignore;
+    }
+
+    @Override
+    public void setIgnoreNonPersistent(boolean ignore) {
+        _ignore = ignore;
+    }
+
+    @Override
+    public void populate(ClassMetaData meta, int access) {
+        populate(meta, access, false);
+    }
+
+    @Override
+    public void populate(ClassMetaData meta, int access, boolean ignoreTransient) {
+        if (meta.getDescribedType() == Object.class)
+            return;
+        meta.setAccessType(access);
+
+        Log log = meta.getRepository().getLog();
+        if (log.isTraceEnabled())
+            log.trace(_loc.get("gen-meta", meta));
+        if (!_pcRegistry || !populateFromPCRegistry(meta)) {
+            if (log.isTraceEnabled())
+                log.trace(_loc.get("meta-reflect"));
+            populateFromReflection(meta, ignoreTransient);
+        }
+    }
+
+    /**
+     * Populate the given metadata using the {@link PCRegistry}.
+     */
+    private boolean populateFromPCRegistry(ClassMetaData meta) {
+        Class<?> cls = meta.getDescribedType();
+        if (!PCRegistry.isRegistered(cls))
+            return false;
+        try {
+            String[] fieldNames = PCRegistry.getFieldNames(cls);
+            Class<?>[] fieldTypes = PCRegistry.getFieldTypes(cls);
+            Member member;
+            FieldMetaData fmd;
+            for (int i = 0; i < fieldNames.length; i ++) {
+            	String property = fieldNames[i];
+                member = getMemberByProperty(meta, property,
+                	AccessCode.UNKNOWN, true);
+                if (member == null) // transient or indeterminable access
+                	continue;
+                fmd = meta.addDeclaredField(property, fieldTypes[i]);
+                fmd.backingMember(member);
+                populate(fmd);
+            }
+            return true;
+        } catch (OpenJPAException ke) {
+            throw ke;
+        } catch (Exception e) {
+            if (e instanceof PrivilegedActionException)
+                e = ((PrivilegedActionException) e).getException();
+            throw new UserException(e);
+        }
+    }
+
+    protected abstract List<Member> getPersistentMembers(ClassMetaData meta, boolean ignoreTransient);
+    /**
+     * Generate the given meta-data using reflection.
+     * Adds FieldMetaData for each persistent state.
+     * Delegate to concrete implementation to determine the persistent
+     * members.
+     */
+    private void populateFromReflection(ClassMetaData meta, boolean ignoreTransient) {
+        List<Member> members = getPersistentMembers(meta, ignoreTransient);
+        boolean iface = meta.getDescribedType().isInterface();
+        // If access is mixed or if the default is currently unknown,
+        // process all fields, otherwise only process members of the class
+        // level default access type.
+
+        String name;
+        boolean def;
+        FieldMetaData fmd;
+        for (int i = 0; i < members.size(); i++) {
+            Member member = members.get(i);
+            name = getFieldName(member);
+            if (name == null || isReservedFieldName(name))
+                continue;
+
+            def = isDefaultPersistent(meta, member, name, ignoreTransient);
+            if (!def && _ignore)
+                continue;
+
+            // passed the tests; persistent type -- we construct with
+            // Object.class because setting backing member will set proper
+            // type anyway
+            fmd = meta.addDeclaredField(name, Object.class);
+            fmd.backingMember(member);
+            if (!def) {
+                fmd.setExplicit(true);
+                fmd.setManagement(FieldMetaData.MANAGE_NONE);
+            }
+            populate(fmd);
+        }
+    }
+
+    protected void populate(FieldMetaData fmd) {
+
+    }
+
+    /**
+     * Return the list of fields in <code>meta</code> that use field access,
+     * or <code>null</code> if a list of fields is unobtainable. An empty list
+     * should be returned if the list of fields is obtainable, but there
+     * happens to be no field access in <code>meta</code>.
+     *
+     * This is used for error reporting purposes only, so need not be efficient.
+     *
+     * This implementation returns <code>null</code>.
+     */
+    protected List<String> getFieldAccessNames(ClassMetaData meta) {
+        return null;
+    }
+
+    /**
+     * Return the list of methods in <code>meta</code> that use property access,
+     * or <code>null</code> if a list of methods is unobtainable. An empty list
+     * should be returned if the list of methods is obtainable, but there
+     * happens to be no property access in <code>meta</code>.
+     *
+     * This is used for error reporting purposes only, so need not be efficient.
+     *
+     * This implementation returns <code>null</code>.
+     */
+    protected List<String> getPropertyAccessNames(ClassMetaData meta) {
+        return null;
+    }
+
+    /**
+     * Return the field name for the given member. This will only be invoked
+     * on members of the right type (field vs. method). Return null if the
+     * member cannot be managed. Default behavior: For fields, returns the
+     * field name. For getter methods, returns the minus "get" or "is" with
+     * the next letter lower-cased. For other methods, returns null.
+     */
+    public static String getFieldName(Member member) {
+        if (member instanceof Field)
+            return member.getName();
+        if (member instanceof Method == false)
+        	return null;
+        Method method = (Method) member;
+        String name = method.getName();
+        if (isNormalGetter(method))
+        	name = name.substring("get".length());
+        else if (isBooleanGetter(method))
+        	name = name.substring("is".length());
+        else
+            return null;
+
+        if (name.length() == 1)
+            return name.toLowerCase();
+        return Character.toLowerCase(name.charAt(0)) + name.substring(1);
+    }
+
+    /**
+     * Returns true if the given field name is reserved for unmanaged fields.
+     */
+    protected boolean isReservedFieldName(String name) {
+        // names used by enhancers
+        return name.startsWith("openjpa") || name.startsWith("jdo");
+    }
+
+    /**
+     * Return true if the given member is persistent by default. This will
+     * only be invoked on members of the right type (field vs. method).
+     * Returns false if member is static or final by default.
+     *
+     * @param name the field name from {@link #getFieldName}
+     */
+    protected abstract boolean isDefaultPersistent(ClassMetaData meta,
+        Member member, String name, boolean ignoreTransient);
+
+    /**
+     * Gets the backing member of the given field. If the field has not been
+     * assigned a backing member then get either the instance field or the
+     * getter method depending upon the access style of the defining class.
+     * <br>
+     * Defining class is used instead of declaring class because this method
+     * may be invoked during parsing phase when declaring metadata may not be
+     * available.
+     */
+    @Override
+    public Member getBackingMember(FieldMetaData fmd) {
+        if (fmd == null)
+            return null;
+        if (fmd.getBackingMember() != null)
+        	return fmd.getBackingMember();
+        return getMemberByProperty(fmd.getDeclaringMetaData(), fmd.getName(),
+            fmd.getAccessType(), true);
+    }
+
+    @Override
+    public Class<?> getUnimplementedExceptionType() {
+        return UnsupportedOperationException.class;
+    }
+
+    /**
+     * Helper method; returns true if the given class appears to be
+     * user-defined.
+     */
+    protected static boolean isUserDefined(Class<?> cls) {
+        return cls != null && !cls.getName().startsWith("java.")
+            && !cls.getName().startsWith ("javax.")
+            && !cls.getName().startsWith ("jakarta.");
+	}
+
+    /**
+     * Affirms if the given method matches the following signature
+     * <code> public T getXXX() </code>
+     * where T is any non-void type.
+     */
+    public static boolean isNormalGetter(Method method) {
+    	String methodName = method.getName();
+    	return startsWith(methodName, "get")
+    	    && method.getParameterTypes().length == 0
+    	    && method.getReturnType() != void.class;
+    }
+
+    /**
+     * Affirms if the given method matches the following signature
+     * <code> public boolean isXXX() </code>
+     * <code> public Boolean isXXX() </code>
+     */
+    public static boolean isBooleanGetter(Method method) {
+    	String methodName = method.getName();
+    	return startsWith(methodName, "is")
+    	    && method.getParameterTypes().length == 0
+    	    && isBoolean(method.getReturnType());
+    }
+
+    /**
+     * Affirms if the given method signature matches bean-style getter method
+     * signature.<br>
+     * <code> public T getXXX()</code> where T is any non-void type.<br>
+     * or<br>
+     * <code> public T isXXX()</code> where T is boolean or Boolean.<br>
+     */
+    public static boolean isGetter(Method method, boolean includePrivate) {
+    	if (method == null)
+    		return false;
+    	int mods = method.getModifiers();
+    	if (!(Modifier.isPublic(mods)
+    	      || Modifier.isProtected(mods)
+    	      || (Modifier.isPrivate(mods) && includePrivate))
+    	 || Modifier.isNative(mods)
+    	 || Modifier.isStatic(mods))
+    		return false;
+    	return isNormalGetter(method) || isBooleanGetter(method);
+    }
+
+    /**
+     * Affirms if the given full string starts with the given head.
+     */
+    public static boolean startsWith(String full, String head) {
+        return full != null && head != null && full.startsWith(head)
+            && full.length() > head.length();
+    }
+
+    public static boolean isBoolean(Class<?> cls) {
+    	return cls == boolean.class || cls == Boolean.class;
+    }
+
+    public static List<String> toNames(List<? extends Member> members) {
+    	List<String> result = new ArrayList<>();
+    	for (Member m : members)
+    		result.add(m.getName());
+    	return result;
+    }
+
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java b/jakarta/src/patch/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java
new file mode 100644
index 0000000..0c59207
--- /dev/null
+++ b/jakarta/src/patch/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.tomcat.util.modeler.modules;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map.Entry;
+
+import javax.management.ObjectName;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.modeler.AttributeInfo;
+import org.apache.tomcat.util.modeler.ManagedBean;
+import org.apache.tomcat.util.modeler.OperationInfo;
+import org.apache.tomcat.util.modeler.ParameterInfo;
+import org.apache.tomcat.util.modeler.Registry;
+
+public class MbeansDescriptorsIntrospectionSource extends ModelerSource
+{
+    private static final Log log = LogFactory.getLog(MbeansDescriptorsIntrospectionSource.class);
+
+    private Registry registry;
+    private String type;
+    private final List<ObjectName> mbeans = new ArrayList<>();
+
+    public void setRegistry(Registry reg) {
+        this.registry=reg;
+    }
+
+    /**
+     * Used if a single component is loaded
+     *
+     * @param type The type
+     */
+    public void setType( String type ) {
+       this.type=type;
+    }
+
+    public void setSource( Object source ) {
+        this.source=source;
+    }
+
+    @Override
+    public List<ObjectName> loadDescriptors(Registry registry, String type,
+            Object source) throws Exception {
+        setRegistry(registry);
+        setType(type);
+        setSource(source);
+        execute();
+        return mbeans;
+    }
+
+    public void execute() throws Exception {
+        if( registry==null ) registry=Registry.getRegistry(null, null);
+        try {
+            ManagedBean managed = createManagedBean(registry, null,
+                    (Class<?>)source, type);
+            if( managed==null ) return;
+            managed.setName( type );
+
+            registry.addManagedBean(managed);
+
+        } catch( Exception ex ) {
+            log.error(sm.getString("modules.readDescriptorsError"), ex);
+        }
+    }
+
+
+
+    // ------------ Implementation for non-declared introspection classes
+
+    private static final Hashtable<String,String> specialMethods = new Hashtable<>();
+    static {
+        specialMethods.put( "preDeregister", "");
+        specialMethods.put( "postDeregister", "");
+    }
+
+    private static final Class<?>[] supportedTypes  = new Class[] {
+        Boolean.class,
+        Boolean.TYPE,
+        Byte.class,
+        Byte.TYPE,
+        Character.class,
+        Character.TYPE,
+        Short.class,
+        Short.TYPE,
+        Integer.class,
+        Integer.TYPE,
+        Long.class,
+        Long.TYPE,
+        Float.class,
+        Float.TYPE,
+        Double.class,
+        Double.TYPE,
+        String.class,
+        String[].class,
+        BigDecimal.class,
+        BigInteger.class,
+        ObjectName.class,
+        Object[].class,
+        java.io.File.class,
+    };
+
+    /**
+     * Check if this class is one of the supported types.
+     * If the class is supported, returns true.  Otherwise,
+     * returns false.
+     * @param ret The class to check
+     * @return boolean True if class is supported
+     */
+    private boolean supportedType(Class<?> ret) {
+        for (Class<?> supportedType : supportedTypes) {
+            if (ret == supportedType) {
+                return true;
+            }
+        }
+        if (isBeanCompatible(ret)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if this class conforms to JavaBeans specifications.
+     * If the class is conformant, returns true.
+     *
+     * @param javaType The class to check
+     * @return boolean True if the class is compatible.
+     */
+    private boolean isBeanCompatible(Class<?> javaType) {
+        // Must be a non-primitive and non array
+        if (javaType.isArray() || javaType.isPrimitive()) {
+            return false;
+        }
+
+        // Anything in the java or javax package that
+        // does not have a defined mapping is excluded.
+        if (javaType.getName().startsWith("java.") ||
+            javaType.getName().startsWith("javax.") ||
+            javaType.getName().startsWith("jakarta.")) {
+            return false;
+        }
+
+        try {
+            javaType.getConstructor(new Class[]{});
+        } catch (java.lang.NoSuchMethodException e) {
+            return false;
+        }
+
+        // Make sure superclass is compatible
+        Class<?> superClass = javaType.getSuperclass();
+        if (superClass != null &&
+            superClass != java.lang.Object.class &&
+            superClass != java.lang.Exception.class &&
+            superClass != java.lang.Throwable.class) {
+            if (!isBeanCompatible(superClass)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Process the methods and extract 'attributes', methods, etc.
+     *
+     * @param realClass The class to process
+     * @param methods The methods to process
+     * @param attMap The attribute map (complete)
+     * @param getAttMap The readable attributes map
+     * @param setAttMap The settable attributes map
+     * @param invokeAttMap The invokable attributes map
+     */
+    private void initMethods(Class<?> realClass, Method methods[], Hashtable<String,Method> attMap,
+            Hashtable<String,Method> getAttMap, Hashtable<String,Method> setAttMap,
+            Hashtable<String,Method> invokeAttMap) {
+
+        for (Method method : methods) {
+            String name = method.getName();
+
+            if (Modifier.isStatic(method.getModifiers())) {
+                continue;
+            }
+            if (!Modifier.isPublic(method.getModifiers())) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Not public " + method);
+                }
+                continue;
+            }
+            if (method.getDeclaringClass() == Object.class) {
+                continue;
+            }
+            Class<?> params[] = method.getParameterTypes();
+
+            if (name.startsWith("get") && params.length == 0) {
+                Class<?> ret = method.getReturnType();
+                if (!supportedType(ret)) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Unsupported type " + method);
+                    }
+                    continue;
+                }
+                name = unCapitalize(name.substring(3));
+
+                getAttMap.put(name, method);
+                // just a marker, we don't use the value
+                attMap.put(name, method);
+            } else if (name.startsWith("is") && params.length == 0) {
+                Class<?> ret = method.getReturnType();
+                if (Boolean.TYPE != ret) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Unsupported type " + method + " " + ret);
+                    }
+                    continue;
+                }
+                name = unCapitalize(name.substring(2));
+
+                getAttMap.put(name, method);
+                // just a marker, we don't use the value
+                attMap.put(name, method);
+
+            } else if (name.startsWith("set") && params.length == 1) {
+                if (!supportedType(params[0])) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Unsupported type " + method + " " + params[0]);
+                    }
+                    continue;
+                }
+                name = unCapitalize(name.substring(3));
+                setAttMap.put(name, method);
+                attMap.put(name, method);
+            } else {
+                if (params.length == 0) {
+                    if (specialMethods.get(method.getName()) != null) {
+                        continue;
+                    }
+                    invokeAttMap.put(name, method);
+                } else {
+                    boolean supported = true;
+                    for (Class<?> param : params) {
+                        if (!supportedType(param)) {
+                            supported = false;
+                            break;
+                        }
+                    }
+                    if (supported) {
+                        invokeAttMap.put(name, method);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * XXX Find if the 'className' is the name of the MBean or
+     *       the real class ( I suppose first )
+     * XXX Read (optional) descriptions from a .properties, generated
+     *       from source
+     * XXX Deal with constructors
+     *
+     * @param registry The Bean registry (not used)
+     * @param domain The bean domain (not used)
+     * @param realClass The class to analyze
+     * @param type The bean type
+     * @return ManagedBean The create MBean
+     */
+    public ManagedBean createManagedBean(Registry registry, String domain,
+                                         Class<?> realClass, String type)
+    {
+        ManagedBean mbean= new ManagedBean();
+
+        Method methods[]=null;
+
+        Hashtable<String,Method> attMap = new Hashtable<>();
+        // key: attribute val: getter method
+        Hashtable<String,Method> getAttMap = new Hashtable<>();
+        // key: attribute val: setter method
+        Hashtable<String,Method> setAttMap = new Hashtable<>();
+        // key: operation val: invoke method
+        Hashtable<String,Method> invokeAttMap = new Hashtable<>();
+
+        methods = realClass.getMethods();
+
+        initMethods(realClass, methods, attMap, getAttMap, setAttMap, invokeAttMap );
+
+        try {
+
+            Enumeration<String> en = attMap.keys();
+            while( en.hasMoreElements() ) {
+                String name = en.nextElement();
+                AttributeInfo ai=new AttributeInfo();
+                ai.setName( name );
+                Method gm = getAttMap.get(name);
+                if( gm!=null ) {
+                    //ai.setGetMethodObj( gm );
+                    ai.setGetMethod( gm.getName());
+                    Class<?> t=gm.getReturnType();
+                    if( t!=null )
+                        ai.setType( t.getName() );
+                }
+                Method sm = setAttMap.get(name);
+                if( sm!=null ) {
+                    //ai.setSetMethodObj(sm);
+                    Class<?> t = sm.getParameterTypes()[0];
+                    if( t!=null )
+                        ai.setType( t.getName());
+                    ai.setSetMethod( sm.getName());
+                }
+                ai.setDescription("Introspected attribute " + name);
+                if( log.isDebugEnabled()) log.debug("Introspected attribute " +
+                        name + " " + gm + " " + sm);
+                if( gm==null )
+                    ai.setReadable(false);
+                if( sm==null )
+                    ai.setWriteable(false);
+                if( sm!=null || gm!=null )
+                    mbean.addAttribute(ai);
+            }
+
+            // This map is populated by iterating the methods (which end up as
+            // values in the Map) and obtaining the key from the value. It is
+            // impossible for a key to be associated with a null value.
+            for (Entry<String,Method> entry : invokeAttMap.entrySet()) {
+                String name = entry.getKey();
+                Method m = entry.getValue();
+
+                OperationInfo op=new OperationInfo();
+                op.setName(name);
+                op.setReturnType(m.getReturnType().getName());
+                op.setDescription("Introspected operation " + name);
+                Class<?> parms[] = m.getParameterTypes();
+                for(int i=0; i<parms.length; i++ ) {
+                    ParameterInfo pi=new ParameterInfo();
+                    pi.setType(parms[i].getName());
+                    pi.setName(("param" + i).intern());
+                    pi.setDescription(("Introspected parameter param" + i).intern());
+                    op.addParameter(pi);
+                }
+                mbean.addOperation(op);
+            }
+
+            if( log.isDebugEnabled())
+                log.debug("Setting name: " + type );
+            mbean.setName( type );
+
+            return mbean;
+        } catch( Exception ex ) {
+            ex.printStackTrace();
+            return null;
+        }
+    }
+
+
+    // -------------------- Utils --------------------
+    /**
+     * Converts the first character of the given
+     * String into lower-case.
+     *
+     * @param name The string to convert
+     * @return String
+     */
+    private static String unCapitalize(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+        char chars[] = name.toCharArray();
+        chars[0] = Character.toLowerCase(chars[0]);
+        return new String(chars);
+    }
+
+}
diff --git a/jakarta/src/patch/java/org/apache/webbeans/proxy/AbstractProxyFactory.java b/jakarta/src/patch/java/org/apache/webbeans/proxy/AbstractProxyFactory.java
new file mode 100644
index 0000000..eec5ba5
--- /dev/null
+++ b/jakarta/src/patch/java/org/apache/webbeans/proxy/AbstractProxyFactory.java
@@ -0,0 +1,737 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.webbeans.proxy;
+
+import static org.apache.xbean.asm7.ClassReader.SKIP_CODE;
+import static org.apache.xbean.asm7.ClassReader.SKIP_DEBUG;
+import static org.apache.xbean.asm7.ClassReader.SKIP_FRAMES;
+
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.exception.ProxyGenerationException;
+import org.apache.webbeans.exception.WebBeansException;
+import org.apache.webbeans.spi.DefiningClassService;
+import org.apache.xbean.asm7.ClassReader;
+import org.apache.xbean.asm7.ClassWriter;
+import org.apache.xbean.asm7.MethodVisitor;
+import org.apache.xbean.asm7.Opcodes;
+import org.apache.xbean.asm7.Type;
+import org.apache.xbean.asm7.shade.commons.EmptyVisitor;
+
+/**
+ * Base class for all OWB Proxy factories
+ */
+public abstract class AbstractProxyFactory
+{
+    public static final int MAX_CLASSLOAD_TRIES = 10000;
+
+    /**
+     * This is needed as the Modifier#VARARGS is not (yet) public.
+     * Note that the bitcode is the same as Modifier#TRANSIENT.
+     * But 'varargs' is only for methods, whereas 'transient' is only for fields.
+     */
+    public static final int MODIFIER_VARARGS = 0x00000080;
+
+    protected final Unsafe unsafe;
+
+    private final DefiningClassService definingService;
+
+    protected WebBeansContext webBeansContext;
+
+    private final int javaVersion;
+
+
+    /**
+     * The name of the field which stores the passivationID of the Bean this proxy serves.
+     * This is needed in case the proxy gets de-serialized back into a JVM
+     * which didn't have this bean loaded yet.
+     */
+    public static final String FIELD_BEAN_PASSIVATION_ID = "owbBeanPassivationId";
+
+
+    protected AbstractProxyFactory(WebBeansContext webBeansContext)
+    {
+        this.webBeansContext = webBeansContext;
+        javaVersion = determineDefaultJavaVersion();
+        unsafe = new Unsafe();
+        definingService = webBeansContext.getService(DefiningClassService.class);
+    }
+
+    private int determineDefaultJavaVersion()
+    {
+        String javaVersionProp = webBeansContext.getOpenWebBeansConfiguration().getGeneratorJavaVersion();
+        if (javaVersionProp == null)  // try to align on the runtime
+        {
+            javaVersionProp = System.getProperty("java.version");
+        }
+        if (javaVersionProp != null)
+        {
+            if (javaVersionProp.startsWith("1.8"))
+            {
+                return Opcodes.V1_8;
+            }
+            else if (javaVersionProp.startsWith("9") || javaVersionProp.startsWith("1.9"))
+            {
+                return Opcodes.V9;
+            }
+            else if (javaVersionProp.startsWith("10"))
+            {
+                return Opcodes.V10;
+            }
+            else if (javaVersionProp.startsWith("11"))
+            {
+                return Opcodes.V11;
+            }
+            else if (javaVersionProp.startsWith("12"))
+            {
+                return Opcodes.V12;
+            }
+            else if (javaVersionProp.startsWith("13"))
+            {
+                return Opcodes.V13;
+            }
+            else
+            {
+                try
+                {
+                    final int i = Integer.parseInt(javaVersionProp);
+                    if (i > 13)
+                    {
+                        return Opcodes.V13 + (i - 13);
+                    }
+                }
+                catch (final NumberFormatException nfe)
+                {
+                    // let's default
+                }
+            }
+        }
+
+        // the fallback is the lowest one to ensure it supports all possible classes of current environments
+        return Opcodes.V1_8;
+    }
+
+
+    protected ClassLoader getProxyClassLoader(Class<?> beanClass)
+    {
+        if (definingService != null)
+        {
+            return definingService.getProxyClassLoader(beanClass);
+        }
+        return webBeansContext.getApplicationBoundaryService().getBoundaryClassLoader(beanClass);
+    }
+
+    /**
+     * @return the marker interface which should be used for this proxy.
+     */
+    protected abstract Class getMarkerInterface();
+
+    /**
+     * generate the bytecode for creating the instance variables of the class
+     */
+    protected abstract void createInstanceVariables(ClassWriter cw, Class<?> classToProxy, String classFileName);
+
+    /**
+     * generate the bytecode for serialization.
+     */
+    protected abstract void createSerialisation(ClassWriter cw, String proxyClassFileName, Class<?> classToProxy, String classFileName);
+
+    /**
+     * Each of our interceptor/decorator proxies has exactly 1 constructor
+     * which invokes the super ct + sets the delegation field.
+     *
+     * @param cw
+     * @param classToProxy
+     * @param classFileName
+     * @throws org.apache.webbeans.exception.ProxyGenerationException
+     */
+    protected abstract void createConstructor(ClassWriter cw, String proxyClassFileName, Class<?> classToProxy, String classFileName, Constructor<?> injectConstructor)
+            throws ProxyGenerationException;
+
+    /**
+     * generate the bytecode for invoking all intercepted methods
+     */
+    protected abstract void delegateInterceptedMethods(ClassLoader classLoader, ClassWriter cw, String proxyClassFileName, Class<?> classToProxy, Method[] interceptedMethods)
+            throws ProxyGenerationException;
+
+    /**
+     * generate the bytecode for invoking all non-intercepted methods
+     */
+    protected abstract void delegateNonInterceptedMethods(ClassLoader classLoader, ClassWriter cw, String proxyClassFileName, Class<?> classToProxy, Method[] noninterceptedMethods)
+            throws ProxyGenerationException;
+
+    /**
+     * Detect a free classname based on the given one
+     * @param proxyClassName
+     * @return
+     */
+    protected String getUnusedProxyClassName(ClassLoader classLoader, String proxyClassName)
+    {
+        proxyClassName = fixPreservedPackages(proxyClassName);
+
+        String finalName = proxyClassName;
+
+        for (int i = 0; i < MAX_CLASSLOAD_TRIES; i++)
+        {
+            try
+            {
+                finalName = proxyClassName + i;
+                Class.forName(finalName, true, classLoader);
+            }
+            catch (ClassNotFoundException cnfe)
+            {
+                // this is exactly what we need!
+                return finalName;
+            }
+            // otherwise we continue ;)
+        }
+
+        throw new WebBeansException("Unable to detect a free proxy class name based on: " + proxyClassName);
+    }
+
+    protected  <T> String getSignedClassProxyName(final Class<T> classToProxy)
+    {
+        // avoid java.lang.SecurityException: class's signer information
+        // does not match signer information of other classes in the same package
+        return "org.apache.webbeans.custom.signed." + classToProxy.getName();
+    }
+
+    protected String fixPreservedPackages(String proxyClassName)
+    {
+        proxyClassName = fixPreservedPackage(proxyClassName, "java.");
+        proxyClassName = fixPreservedPackage(proxyClassName, "javax.");
+        proxyClassName = fixPreservedPackage(proxyClassName, "jakarta.");
+        proxyClassName = fixPreservedPackage(proxyClassName, "sun.misc.");
+
+        return proxyClassName;
+    }
+    /**
+     * Detect if the provided className is in the forbidden package.
+     * If so, move it to org.apache.webbeans.custom.
+     * @param forbiddenPackagePrefix including the '.', e.g. 'javax.'
+     */
+    private String fixPreservedPackage(String className, String forbiddenPackagePrefix)
+    {
+        String fixedClassName = className;
+
+        if (className.startsWith(forbiddenPackagePrefix))
+        {
+            fixedClassName = "org.apache.webbeans.custom." + className.substring(forbiddenPackagePrefix.length());
+        }
+
+        return fixedClassName;
+    }
+
+    protected <T> Class<T> createProxyClass(ClassLoader classLoader, String proxyClassName, Class<T> classToProxy,
+                                            Method[] interceptedMethods, Method[] nonInterceptedMethods)
+            throws ProxyGenerationException
+    {
+        return createProxyClass(classLoader, proxyClassName, classToProxy, interceptedMethods, nonInterceptedMethods, null);
+    }
+
+    /**
+     * @param classLoader to use for creating the class in
+     * @param classToProxy the class for which a subclass will get generated
+     * @param interceptedMethods the list of intercepted or decorated business methods.
+     * @param nonInterceptedMethods all methods which are <b>not</b> intercepted nor decorated and shall get delegated directly
+     * @param <T>
+     * @return the proxy class
+     */
+    protected <T> Class<T> createProxyClass(ClassLoader classLoader, String proxyClassName, Class<T> classToProxy,
+                                                      Method[] interceptedMethods, Method[] nonInterceptedMethods,
+                                                      Constructor<T> constructor)
+            throws ProxyGenerationException
+    {
+        String proxyClassFileName = proxyClassName.replace('.', '/');
+
+        byte[] proxyBytes = generateProxy(classLoader,
+                classToProxy,
+                proxyClassName,
+                proxyClassFileName,
+                sortOutDuplicateMethods(interceptedMethods),
+                sortOutDuplicateMethods(nonInterceptedMethods),
+                constructor);
+
+        if (definingService != null)
+        {
+            return definingService.defineAndLoad(proxyClassName, proxyBytes, classToProxy);
+        }
+        return unsafe.defineAndLoadClass(classLoader, proxyClassName, proxyBytes);
+    }
+
+    private Method[] sortOutDuplicateMethods(Method[] methods)
+    {
+        if (methods == null || methods.length == 0)
+        {
+            return null;
+        }
+
+        ArrayList<Method> duplicates = new ArrayList<>();
+
+        for (Method outer : methods)
+        {
+            for (Method inner : methods)
+            {
+                if (inner != outer
+                        && hasSameSignature(outer, inner)
+                        && !(duplicates.contains(outer) || duplicates.contains(inner)))
+                {
+                    duplicates.add(inner);
+                }
+            }
+        }
+
+        ArrayList<Method> outsorted = new ArrayList<>(Arrays.asList(methods));
+        outsorted.removeAll(duplicates);
+        return outsorted.toArray(new Method[outsorted.size()]);
+    }
+
+    private boolean hasSameSignature(Method a, Method b)
+    {
+        return a.getName().equals(b.getName())
+                && a.getReturnType().equals(b.getReturnType())
+                && Arrays.equals(a.getParameterTypes(), b.getParameterTypes());
+    }
+
+    private byte[] generateProxy(ClassLoader classLoader, Class<?> classToProxy, String proxyClassName, String proxyClassFileName,
+                                 Method[] interceptedMethods, Method[] nonInterceptedMethods, Constructor<?> constructor)
+            throws ProxyGenerationException
+    {
+        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+        String classFileName = classToProxy.getName().replace('.', '/');
+
+        String[] interfaceNames = {Type.getInternalName(getMarkerInterface())};
+        String superClassName = classFileName;
+
+        if (classToProxy.isInterface())
+        {
+            interfaceNames = new String[]{Type.getInternalName(classToProxy), interfaceNames[0]};
+            superClassName = Type.getInternalName(Object.class);
+        }
+
+        cw.visit(findJavaVersion(classToProxy), Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER + Opcodes.ACC_SYNTHETIC, proxyClassFileName, null, superClassName, interfaceNames);
+        cw.visitSource(classFileName + ".java", null);
+
+        createInstanceVariables(cw, classToProxy, classFileName);
+        createSerialisation(cw, proxyClassFileName, classToProxy, classFileName);
+
+
+
+        // create a static String Field which contains the passivationId of the Bean or null if not PassivationCapable
+        cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
+                FIELD_BEAN_PASSIVATION_ID, Type.getDescriptor(String.class), null, null).visitEnd();
+
+        createConstructor(cw, proxyClassFileName, classToProxy, classFileName, constructor);
+
+
+        if (nonInterceptedMethods != null)
+        {
+            delegateNonInterceptedMethods(classLoader, cw, proxyClassFileName, classToProxy, nonInterceptedMethods);
+        }
+
+        if (interceptedMethods != null)
+        {
+            delegateInterceptedMethods(classLoader, cw, proxyClassFileName, classToProxy, interceptedMethods);
+        }
+
+        return cw.toByteArray();
+    }
+
+    private int findJavaVersion(final Class<?> from)
+    {
+        final String resource = from.getName().replace('.', '/') + ".class";
+        try (final InputStream stream = from.getClassLoader().getResourceAsStream(resource))
+        {
+            if (stream == null)
+            {
+                return javaVersion;
+            }
+            final ClassReader reader = new ClassReader(stream);
+            final VersionVisitor visitor = new VersionVisitor();
+            reader.accept(visitor, SKIP_DEBUG + SKIP_CODE + SKIP_FRAMES);
+            if (visitor.version != 0)
+            {
+                return visitor.version;
+            }
+        }
+        catch (final Exception e)
+        {
+            // no-op
+        }
+        // mainly for JVM classes - outside the classloader, find to fallback on the JVM version
+        return javaVersion;
+    }
+
+
+
+    protected boolean unproxyableMethod(Method delegatedMethod)
+    {
+        int modifiers = delegatedMethod.getModifiers();
+
+        return (modifiers & (Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL | Modifier.NATIVE)) > 0 ||
+               "finalize".equals(delegatedMethod.getName()) || delegatedMethod.isBridge();
+    }
+
+    /**
+     * @return the wrapper type for a primitive, e.g. java.lang.Integer for int
+     */
+    protected String getWrapperType(Class<?> type)
+    {
+        if (Integer.TYPE.equals(type))
+        {
+            return Integer.class.getCanonicalName().replace('.', '/');
+        }
+        else if (Boolean.TYPE.equals(type))
+        {
+            return Boolean.class.getCanonicalName().replace('.', '/');
+        }
+        else if (Character.TYPE.equals(type))
+        {
+            return Character.class.getCanonicalName().replace('.', '/');
+        }
+        else if (Byte.TYPE.equals(type))
+        {
+            return Byte.class.getCanonicalName().replace('.', '/');
+        }
+        else if (Short.TYPE.equals(type))
+        {
+            return Short.class.getCanonicalName().replace('.', '/');
+        }
+        else if (Float.TYPE.equals(type))
+        {
+            return Float.class.getCanonicalName().replace('.', '/');
+        }
+        else if (Long.TYPE.equals(type))
+        {
+            return Long.class.getCanonicalName().replace('.', '/');
+        }
+        else if (Double.TYPE.equals(type))
+        {
+            return Double.class.getCanonicalName().replace('.', '/');
+        }
+        else if (Void.TYPE.equals(type))
+        {
+            return Void.class.getCanonicalName().replace('.', '/');
+        }
+
+        throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+    }
+
+    /**
+     * Returns the appropriate bytecode instruction to load a value from a variable to the stack
+     *
+     * @param type Type to load
+     * @return Bytecode instruction to use
+     */
+    protected int getVarInsn(Class<?> type)
+    {
+        if (type.isPrimitive())
+        {
+            if (Integer.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Boolean.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Character.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Byte.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Short.TYPE.equals(type))
+            {
+                return Opcodes.ILOAD;
+            }
+            else if (Float.TYPE.equals(type))
+            {
+                return Opcodes.FLOAD;
+            }
+            else if (Long.TYPE.equals(type))
+            {
+                return Opcodes.LLOAD;
+            }
+            else if (Double.TYPE.equals(type))
+            {
+                return Opcodes.DLOAD;
+            }
+        }
+
+        throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+    }
+
+    /**
+     * Invokes the most appropriate bytecode instruction to put a number on the stack
+     *
+     * @param mv
+     * @param i
+     */
+    protected void pushIntOntoStack(MethodVisitor mv, int i)
+    {
+        if (i == 0)
+        {
+            mv.visitInsn(Opcodes.ICONST_0);
+        }
+        else if (i == 1)
+        {
+            mv.visitInsn(Opcodes.ICONST_1);
+        }
+        else if (i == 2)
+        {
+            mv.visitInsn(Opcodes.ICONST_2);
+        }
+        else if (i == 3)
+        {
+            mv.visitInsn(Opcodes.ICONST_3);
+        }
+        else if (i == 4)
+        {
+            mv.visitInsn(Opcodes.ICONST_4);
+        }
+        else if (i == 5)
+        {
+            mv.visitInsn(Opcodes.ICONST_5);
+        }
+        else if (i > 5 && i <= 255)
+        {
+            mv.visitIntInsn(Opcodes.BIPUSH, i);
+        }
+        else
+        {
+            mv.visitIntInsn(Opcodes.SIPUSH, i);
+        }
+    }
+
+    /**
+     * Gets the appropriate bytecode instruction for RETURN, according to what type we need to return
+     *
+     * @param type Type the needs to be returned
+     * @return The matching bytecode instruction
+     */
+    protected int getReturnInsn(Class<?> type)
+    {
+        if (type.isPrimitive())
+        {
+            if (Void.TYPE.equals(type))
+            {
+                return Opcodes.RETURN;
+            }
+            if (Integer.TYPE.equals(type))
+            {
+                return Opcodes.IRETURN;
+            }
+            else if (Boolean.TYPE.equals(type))
+            {
+                return Opcodes.IRETURN;
+            }
+            else if (Character.TYPE.equals(type))
+            {
+                return Opcodes.IRETURN;
+            }
+            else if (Byte.TYPE.equals(type))
+            {
+                return Opcodes.IRETURN;
+            }
+            else if (Short.TYPE.equals(type))
+            {
+                return Opcodes.IRETURN;
+            }
+            else if (Float.TYPE.equals(type))
+            {
+                return Opcodes.FRETURN;
+            }
+            else if (Long.TYPE.equals(type))
+            {
+                return Opcodes.LRETURN;
+            }
+            else if (Double.TYPE.equals(type))
+            {
+                return Opcodes.DRETURN;
+            }
+        }
+
+        return Opcodes.ARETURN;
+    }
+
+    /**
+     * Gets the string to use for CHECKCAST instruction, returning the correct value for any type, including primitives and arrays
+     *
+     * @param returnType The type to cast to with CHECKCAST
+     * @return CHECKCAST parameter
+     */
+    protected String getCastType(Class<?> returnType)
+    {
+        if (returnType.isPrimitive())
+        {
+            return getWrapperType(returnType);
+        }
+        else
+        {
+            return Type.getInternalName(returnType);
+        }
+    }
+
+    /**
+     * Returns the name of the Java method to call to get the primitive value from an Object - e.g. intValue for java.lang.Integer
+     *
+     * @param type Type whose primitive method we want to lookup
+     * @return The name of the method to use
+     */
+    protected String getPrimitiveMethod(Class<?> type)
+    {
+        if (Integer.TYPE.equals(type))
+        {
+            return "intValue";
+        }
+        else if (Boolean.TYPE.equals(type))
+        {
+            return "booleanValue";
+        }
+        else if (Character.TYPE.equals(type))
+        {
+            return "charValue";
+        }
+        else if (Byte.TYPE.equals(type))
+        {
+            return "byteValue";
+        }
+        else if (Short.TYPE.equals(type))
+        {
+            return "shortValue";
+        }
+        else if (Float.TYPE.equals(type))
+        {
+            return "floatValue";
+        }
+        else if (Long.TYPE.equals(type))
+        {
+            return "longValue";
+        }
+        else if (Double.TYPE.equals(type))
+        {
+            return "doubleValue";
+        }
+
+        throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+    }
+
+    protected void generateReturn(MethodVisitor mv, Method delegatedMethod)
+    {
+        Class<?> returnType = delegatedMethod.getReturnType();
+        mv.visitInsn(getReturnInsn(returnType));
+    }
+
+    /**
+     * Create an Object[] parameter which contains all the parameters of the currently invoked method
+     * and store this array for use in the call stack.
+     * @param mv
+     * @param parameterTypes
+     */
+    protected void pushMethodParameterArray(MethodVisitor mv, Class<?>[] parameterTypes)
+    {
+        // need to construct the array of objects passed in
+        // create the Object[]
+        createArrayDefinition(mv, parameterTypes.length, Object.class);
+
+        int index = 1;
+        // push parameters into array
+        for (int i = 0; i < parameterTypes.length; i++)
+        {
+            // keep copy of array on stack
+            mv.visitInsn(Opcodes.DUP);
+
+            Class<?> parameterType = parameterTypes[i];
+
+            // push number onto stack
+            pushIntOntoStack(mv, i);
+
+            if (parameterType.isPrimitive())
+            {
+                String wrapperType = getWrapperType(parameterType);
+                mv.visitVarInsn(getVarInsn(parameterType), index);
+
+                mv.visitMethodInsn(Opcodes.INVOKESTATIC, wrapperType, "valueOf",
+                        "(" + Type.getDescriptor(parameterType) + ")L" + wrapperType + ";", false);
+                mv.visitInsn(Opcodes.AASTORE);
+
+                if (Long.TYPE.equals(parameterType) || Double.TYPE.equals(parameterType))
+                {
+                    index += 2;
+                }
+                else
+                {
+                    index++;
+                }
+            }
+            else
+            {
+                mv.visitVarInsn(Opcodes.ALOAD, index);
+                mv.visitInsn(Opcodes.AASTORE);
+                index++;
+            }
+        }
+    }
+
+    /**
+     * pushes an array of the specified size to the method visitor. The generated bytecode will leave
+     * the new array at the top of the stack.
+     *
+     * @param mv   MethodVisitor to use
+     * @param size Size of the array to create
+     * @param type Type of array to create
+     * @throws ProxyGenerationException
+     */
+    protected void createArrayDefinition(MethodVisitor mv, int size, Class<?> type)
+            throws ProxyGenerationException
+    {
+        // create a new array of java.lang.class (2)
+
+        if (size < 0)
+        {
+            throw new ProxyGenerationException("Array size cannot be less than zero");
+        }
+
+        pushIntOntoStack(mv, size);
+
+        mv.visitTypeInsn(Opcodes.ANEWARRAY, type.getCanonicalName().replace('.', '/'));
+    }
+
+
+    private static class VersionVisitor extends EmptyVisitor
+    {
+        private int version;
+
+        @Override
+        public void visit(final int version, final int access, final String name,
+                          final String signature, final String superName, final String[] interfaces)
+        {
+            this.version = version;
+        }
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/deployment/JavaSECMPInitializer.java b/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/deployment/JavaSECMPInitializer.java
new file mode 100644
index 0000000..3b773b3
--- /dev/null
+++ b/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/deployment/JavaSECMPInitializer.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2018 IBM Corporation. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+// Contributors:
+//     Oracle - initial API and implementation from Oracle TopLink
+//     tware - 1.0RC1 - refactor for OSGi
+//     zhao jianyong - Bug 324627 - JarList stream is not explicitly closed after use in JavaSECMPInitializer
+package org.eclipse.persistence.internal.jpa.deployment;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.lang.reflect.Constructor;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.ProtectionDomain;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import jakarta.persistence.PersistenceException;
+import jakarta.persistence.spi.ClassTransformer;
+import jakarta.persistence.spi.PersistenceUnitInfo;
+
+import org.eclipse.persistence.config.PersistenceUnitProperties;
+import org.eclipse.persistence.exceptions.EntityManagerSetupException;
+import org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider;
+import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
+import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor;
+import org.eclipse.persistence.internal.security.PrivilegedInvokeConstructor;
+import org.eclipse.persistence.logging.AbstractSessionLog;
+import org.eclipse.persistence.logging.SessionLog;
+
+/**
+ * INTERNAL:
+ *
+ * JavaSECMPInitializer is used to bootstrap the deployment of EntityBeans in EJB 3.0
+ * when deployed in a non-managed setting
+ *
+ * It is called internally by our Provider
+ *
+ * @see org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider
+ */
+public class JavaSECMPInitializer extends JPAInitializer {
+
+    // Used when byte code enhancing
+    public static Instrumentation globalInstrumentation;
+    // Adding this flag because globalInstrumentation could be set to null after weaving is done.
+    protected static boolean usesAgent;
+    // Adding this flag to know that within a JEE container so weaving should be enabled without an agent for non managed persistence units.
+    protected static boolean isInContainer;
+    // Indicates whether has been initialized - that could be done only once.
+    protected static boolean isInitialized;
+    // Singleton corresponding to the main class loader. Created only if agent is used.
+    protected static JavaSECMPInitializer initializer;
+    // Used as a lock in getJavaSECMPInitializer.
+    private static final Object initializationLock = new Object();
+
+    public static boolean isInContainer() {
+        return isInContainer;
+    }
+    public static void setIsInContainer(boolean isInContainer) {
+        JavaSECMPInitializer.isInContainer = isInContainer;
+    }
+
+    /**
+     * Get the singleton entityContainer.
+     */
+    public static JavaSECMPInitializer getJavaSECMPInitializer() {
+        return getJavaSECMPInitializer(Thread.currentThread().getContextClassLoader(), null, false);
+    }
+    public static JavaSECMPInitializer getJavaSECMPInitializer(ClassLoader classLoader) {
+        return getJavaSECMPInitializer(classLoader, null, false);
+    }
+    public static JavaSECMPInitializer getJavaSECMPInitializerFromAgent() {
+        return getJavaSECMPInitializer(Thread.currentThread().getContextClassLoader(), null, true);
+    }
+    public static JavaSECMPInitializer getJavaSECMPInitializerFromMain(Map m) {
+        return getJavaSECMPInitializer(Thread.currentThread().getContextClassLoader(), m, false);
+    }
+    public static JavaSECMPInitializer getJavaSECMPInitializer(ClassLoader classLoader, Map m, boolean fromAgent) {
+        if(!isInitialized) {
+            if(globalInstrumentation != null) {
+                synchronized(initializationLock) {
+                    if(!isInitialized) {
+                        initializeTopLinkLoggingFile();
+                        if(fromAgent) {
+                            AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_initialize_from_agent", (Object[])null);
+                        }
+                        usesAgent = true;
+                        initializer = new JavaSECMPInitializer(classLoader);
+                        initializer.initialize(m != null ? m : new HashMap(0));
+                        // all the transformers have been added to instrumentation, don't need it any more.
+                        globalInstrumentation = null;
+                    }
+                }
+            }
+            isInitialized = true;
+        }
+        if(initializer != null && initializer.getInitializationClassLoader() == classLoader) {
+            return initializer;
+        } else {
+            // when agent is not used initializer does not need to be initialized.
+            return new JavaSECMPInitializer(classLoader);
+        }
+    }
+
+    /**
+     * User should not instantiate JavaSECMPInitializer.
+     */
+    protected JavaSECMPInitializer() {
+        super();
+    }
+
+    protected JavaSECMPInitializer(ClassLoader loader) {
+        super();
+        this.initializationClassloader = loader;
+    }
+
+    /**
+     * Check whether weaving is possible and update the properties and variable as appropriate
+     * @param properties The list of properties to check for weaving and update if weaving is not needed
+     */
+    @Override
+    public void checkWeaving(Map properties){
+        String weaving = EntityManagerFactoryProvider.getConfigPropertyAsString(PersistenceUnitProperties.WEAVING, properties, null);
+        // Check usesAgent instead of globalInstrumentation!=null because globalInstrumentation is set to null after initialization,
+        // but we still have to keep weaving so that the resulting projects correspond to the woven (during initialization) classes.
+        if (!usesAgent && !isInContainer) {
+            if (weaving == null) {
+               properties.put(PersistenceUnitProperties.WEAVING, "false");
+               weaving = "false";
+            } else if (weaving.equalsIgnoreCase("true")) {
+                throw new PersistenceException(EntityManagerSetupException.wrongWeavingPropertyValue());
+            }
+        }
+        if ((weaving != null) && ((weaving.equalsIgnoreCase("false")) || (weaving.equalsIgnoreCase("static")))){
+            shouldCreateInternalLoader = false;
+        }
+    }
+
+    /**
+     * Create a temporary class loader that can be used to inspect classes and then
+     * thrown away.  This allows classes to be introspected prior to loading them
+     * with application's main class loader enabling weaving.
+     */
+    @Override
+    protected ClassLoader createTempLoader(Collection col) {
+        return createTempLoader(col, true);
+    }
+
+    @Override
+    protected ClassLoader createTempLoader(Collection col, boolean shouldOverrideLoadClassForCollectionMembers) {
+        if (!shouldCreateInternalLoader) {
+            return Thread.currentThread().getContextClassLoader();
+        }
+
+        ClassLoader currentLoader = Thread.currentThread().getContextClassLoader();
+        if (!(currentLoader instanceof URLClassLoader)) {
+            //we can't create a TempEntityLoader so just use the current one
+            //shouldn't be a problem (and should only occur) in JavaSE
+            return currentLoader;
+        }
+        URL[] urlPath = ((URLClassLoader)currentLoader).getURLs();
+
+        ClassLoader tempLoader = null;
+        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
+            try {
+                Class[] argsClasses = new Class[] { URL[].class, ClassLoader.class, Collection.class, boolean.class };
+                Object[] args = new Object[] { urlPath, currentLoader, col, shouldOverrideLoadClassForCollectionMembers };
+                Constructor classLoaderConstructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor(TempEntityLoader.class, argsClasses, true));
+                tempLoader = (ClassLoader) AccessController.doPrivileged(new PrivilegedInvokeConstructor(classLoaderConstructor, args));
+            } catch (PrivilegedActionException privilegedException) {
+                throw new PersistenceException(EntityManagerSetupException.failedToInstantiateTemporaryClassLoader(privilegedException));
+            }
+        } else {
+            tempLoader = new TempEntityLoader(urlPath, currentLoader, col, shouldOverrideLoadClassForCollectionMembers);
+        }
+
+        AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_tempLoader_created", tempLoader);
+        AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_shouldOverrideLoadClassForCollectionMembers", Boolean.valueOf(shouldOverrideLoadClassForCollectionMembers));
+
+        return tempLoader;
+    }
+
+    /**
+     * INTERNAL:
+     * Should be called only by the agent. (when weaving classes)
+     * If succeeded return true, false otherwise.
+     */
+    protected static void initializeFromAgent(Instrumentation instrumentation) throws Exception {
+        // Squirrel away the instrumentation for later
+        globalInstrumentation = instrumentation;
+        getJavaSECMPInitializerFromAgent();
+    }
+
+    /**
+     * Usually JavaSECMPInitializer is initialized from agent during premain
+     * to ensure that the classes to be weaved haven't been loaded before initialization.
+     * However, in this case initialization can't be debugged.
+     * In order to be able to debug initialization specify
+     * in java options -javaagent with parameter "main":  (note: a separate eclipselink-agent.jar is no longer required)
+     *   -javaagent:c:\trunk\eclipselink.jar=main
+     * that causes instrumentation to be cached during premain and postpones initialization until main.
+     * With initialization done in main (during the first createEntityManagerFactory call)
+     * there's a danger of the classes to be weaved being already loaded.
+     * In that situation initializeFromMain should be called before any classes are loaded.
+     * The sure-to-work method would be to create a new runnable class with a main method
+     * consisting of just two lines: calling initializeFromMain
+     * followed by reflective call to the main method of the original runnable class.
+     * The same could be achieved by calling PersistenceProvider.createEntityManagerFactory method instead
+     * of JavaSECMPInitializer.initializeFromMain method,
+     * however initializeFromMain might be more convenient because it
+     * doesn't require a persistence unit name.
+     * The method doesn't do anything if JavaSECMPInitializer has been already initialized.
+     * @param m - a map containing the set of properties to instantiate with.
+     */
+    public static void initializeFromMain(Map m) {
+        getJavaSECMPInitializerFromMain(m);
+    }
+
+    /**
+     * The version of initializeFromMain that passes an empty map.
+     */
+    public static void initializeFromMain() {
+        initializeFromMain(new HashMap());
+    }
+
+    /**
+     * Register a transformer.  In this case, we use the instrumentation to add a transformer for the
+     * JavaSE environment
+     * @param transformer
+     * @param persistenceUnitInfo
+     */
+    @Override
+    public void registerTransformer(final ClassTransformer transformer, PersistenceUnitInfo persistenceUnitInfo, Map properties){
+        if ((transformer != null) && (globalInstrumentation != null)) {
+            AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_register_transformer", persistenceUnitInfo.getPersistenceUnitName());
+            globalInstrumentation.addTransformer(new ClassFileTransformer() {
+                // adapt ClassTransformer to ClassFileTransformer interface
+                @Override
+                public byte[] transform(
+                        ClassLoader loader, String className,
+                        Class<?> classBeingRedefined,
+                        ProtectionDomain protectionDomain,
+                        byte[] classfileBuffer) throws IllegalClassFormatException {
+                    return transformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
+                }
+            });
+        } else if (transformer == null) {
+            AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_transformer_is_null", null, true);
+        } else if (globalInstrumentation == null) {
+            AbstractSessionLog.getLog().log(SessionLog.FINER, SessionLog.WEAVER, "cmp_init_globalInstrumentation_is_null", null, true);
+        }
+    }
+
+    /**
+     * Indicates whether puName uniquely defines the persistence unit.
+     * usesAgent means that it is a stand alone SE case.
+     * Otherwise it could be an application server case where different persistence units
+     * may have the same name: that could happen if they are loaded by different classloaders;
+     * the common case is the same persistence unit jar deployed in several applications.
+     */
+    @Override
+    public boolean isPersistenceUnitUniquelyDefinedByName() {
+        return usesAgent;
+    }
+
+    /**
+     * Indicates whether initialization has already occurred.
+     */
+    public static boolean isInitialized() {
+        return isInitialized;
+    }
+
+    /**
+     * Indicates whether Java agent and globalInstrumentation was used.
+     */
+    public static boolean usesAgent() {
+        return usesAgent;
+    }
+
+    /**
+     * Indicates whether initialPuInfos and initialEmSetupImpls are used.
+     */
+    @Override
+    protected boolean keepAllPredeployedPersistenceUnits() {
+        return usesAgent;
+    }
+
+    /*********************************/
+    /***** Temporary Classloader *****/
+    /*********************************/
+    /**
+     * This class loader is provided at initialization time to allow us to temporarily load
+     * domain classes so we can examine them for annotations.  After they are loaded we will throw this
+     * class loader away.  Transformers can then be registered on the real class loader to allow
+     * weaving to occur.
+     *
+     * It selectively loads classes based on the list of classnames it is instantiated with.  Classes
+     * not on that list are allowed to be loaded by the parent.
+     */
+    public static class TempEntityLoader extends URLClassLoader {
+        Collection classNames;
+        boolean shouldOverrideLoadClassForCollectionMembers;
+
+        //added to resolved gf #589 - without this, the orm.xml url would be returned twice
+        @Override
+        public Enumeration<URL> getResources(String name) throws java.io.IOException {
+            return this.getParent().getResources(name);
+        }
+
+        public TempEntityLoader(URL[] urls, ClassLoader parent, Collection classNames, boolean shouldOverrideLoadClassForCollectionMembers) {
+            super(urls, parent);
+            this.classNames = classNames;
+            this.shouldOverrideLoadClassForCollectionMembers = shouldOverrideLoadClassForCollectionMembers;
+        }
+
+        public TempEntityLoader(URL[] urls, ClassLoader parent, Collection classNames) {
+            this(urls, parent, classNames, true);
+        }
+
+        // Indicates if the classLoad should be overridden for the passed className.
+        // Returns true in case the class should NOT be loaded by parent classLoader.
+        protected boolean shouldOverrideLoadClass(String name) {
+            if (shouldOverrideLoadClassForCollectionMembers) {
+                // Override classLoad if the name is in collection
+                return (classNames != null) && classNames.contains(name);
+            } else {
+                // Directly opposite: Override classLoad if the name is NOT in collection.
+                // Forced to check for java. and javax. packages here, because even if the class
+                // has been loaded by parent loader we would load it again
+                // (see comment in loadClass)
+                return !name.startsWith("java.") && !name.startsWith("javax.") && !name.startsWith("jakarta.") && ((classNames == null) || !classNames.contains(name));
+            }
+        }
+
+        @Override
+        protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
+            if (shouldOverrideLoadClass(name)) {
+                // First, check if the class has already been loaded.
+                // Note that the check only for classes loaded by this loader,
+                // it doesn't return true if the class has been loaded by parent loader
+                // (forced to live with that because findLoadedClass method defined as final protected:
+                //  neither can override it nor call it on the parent loader)
+                Class c = findLoadedClass(name);
+                if (c == null) {
+                    c = findClass(name);
+                }
+                if (resolve) {
+                    resolveClass(c);
+                }
+                return c;
+            } else {
+                return super.loadClass(name, resolve);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataAsmFactory.java b/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataAsmFactory.java
new file mode 100644
index 0000000..04fce33
--- /dev/null
+++ b/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataAsmFactory.java
@@ -0,0 +1,712 @@
+/*
+ * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2018 Hans Harz, Andrew Rustleund, IBM Corporation. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+// Contributors:
+//     James Sutherland - initial impl
+//     05/14/2010-2.1 Guy Pelletier
+//       - 253083: Add support for dynamic persistence using ORM.xml/eclipselink-orm.xml
+//     Hans Harz, Andrew Rustleund - Bug 324862 - IndexOutOfBoundsException in
+//           DatabaseSessionImpl.initializeDescriptors because @MapKey Annotation is not found.
+//     04/21/2011-2.3 dclarke: Upgraded to support ASM 3.3.1
+//     08/10/2011-2.3 Lloyd Fernandes : Bug 336133 - Validation error during processing on parameterized generic OneToMany Entity relationship from MappedSuperclass
+//     10/05/2012-2.4.1 Guy Pelletier
+//       - 373092: Exceptions using generics, embedded key and entity inheritance
+//     19/04/2014-2.6 Lukas Jungmann
+//       - 429992: JavaSE 8/ASM 5.0.1 support (EclipseLink silently ignores Entity classes with lambda expressions)
+//     11/05/2015-2.6 Dalia Abo Sheasha
+//       - 480787 : Wrap several privileged method calls with a doPrivileged block
+package org.eclipse.persistence.internal.jpa.metadata.accessors.objects;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.persistence.internal.helper.Helper;
+import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor;
+import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger;
+import org.eclipse.persistence.internal.libraries.asm.AnnotationVisitor;
+import org.eclipse.persistence.internal.libraries.asm.Attribute;
+import org.eclipse.persistence.internal.libraries.asm.ClassReader;
+import org.eclipse.persistence.internal.libraries.asm.ClassVisitor;
+import org.eclipse.persistence.internal.libraries.asm.EclipseLinkClassReader;
+import org.eclipse.persistence.internal.libraries.asm.FieldVisitor;
+import org.eclipse.persistence.internal.libraries.asm.MethodVisitor;
+import org.eclipse.persistence.internal.libraries.asm.Opcodes;
+import org.eclipse.persistence.internal.libraries.asm.Type;
+import org.eclipse.persistence.internal.localization.ExceptionLocalization;
+import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
+import org.eclipse.persistence.logging.AbstractSessionLog;
+import org.eclipse.persistence.logging.SessionLog;
+import org.eclipse.persistence.logging.SessionLogEntry;
+
+/**
+ * INTERNAL: A metadata factory that uses ASM technology and no reflection
+ * whatsoever to process the metadata model.
+ *
+ * @author James Sutherland
+ * @since EclipseLink 1.2
+ */
+public class MetadataAsmFactory extends MetadataFactory {
+    /** Set of primitive type codes. */
+    public static final String PRIMITIVES = "VJIBZCSFD";
+    /** Set of desc token characters. */
+    public static final String TOKENS = "()<>;";
+
+    /**
+     * INTERNAL:
+     */
+    public MetadataAsmFactory(MetadataLogger logger, ClassLoader loader) {
+        super(logger, loader);
+
+        addMetadataClass("I", new MetadataClass(this, int.class));
+        addMetadataClass("J", new MetadataClass(this, long.class));
+        addMetadataClass("S", new MetadataClass(this, short.class));
+        addMetadataClass("Z", new MetadataClass(this, boolean.class));
+        addMetadataClass("F", new MetadataClass(this, float.class));
+        addMetadataClass("D", new MetadataClass(this, double.class));
+        addMetadataClass("C", new MetadataClass(this, char.class));
+        addMetadataClass("B", new MetadataClass(this, byte.class));
+    }
+
+    /**
+     * Build the class metadata for the class name using ASM to read the class
+     * byte codes.
+     */
+    protected void buildClassMetadata(MetadataClass metadataClass, String className, boolean isLazy) {
+        ClassMetadataVisitor visitor = new ClassMetadataVisitor(metadataClass, isLazy);
+        InputStream stream = null;
+        try {
+            String resourceString = className.replace('.', '/') + ".class";
+            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
+                final String f_resourceString = resourceString;
+                stream = AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
+                    @Override
+                    public InputStream run() {
+                        return m_loader.getResourceAsStream(f_resourceString);
+                    }
+                });
+            } else {
+                stream = m_loader.getResourceAsStream(resourceString);
+            }
+
+            ClassReader reader = new ClassReader(stream);
+            Attribute[] attributes = new Attribute[0];
+            reader.accept(visitor, attributes, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+        } catch (IllegalArgumentException iae) {
+            // class was probably compiled with some newer than officially
+            // supported and tested JDK
+            // in such case log a warning and try to re-read the class
+            // without class version check
+            SessionLog log = getLogger().getSession() != null
+                    ? getLogger().getSession().getSessionLog() : AbstractSessionLog.getLog();
+            if (log.shouldLog(SessionLog.SEVERE, SessionLog.METADATA)) {
+                SessionLogEntry entry = new SessionLogEntry(getLogger().getSession(), SessionLog.SEVERE, SessionLog.METADATA, iae);
+                entry.setMessage(ExceptionLocalization.buildMessage("unsupported_classfile_version", new Object[] { className }));
+                log.log(entry);
+            }
+            if (stream != null) {
+                try {
+                    ClassReader reader = new EclipseLinkClassReader(stream);
+                    Attribute[] attributes = new Attribute[0];
+                    reader.accept(visitor, attributes, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+                } catch (Exception e) {
+                    if (log.shouldLog(SessionLog.SEVERE, SessionLog.METADATA)) {
+                        SessionLogEntry entry = new SessionLogEntry(getLogger().getSession(), SessionLog.SEVERE, SessionLog.METADATA, e);
+                        entry.setMessage(ExceptionLocalization.buildMessage("unsupported_classfile_version", new Object[] { className }));
+                        log.log(entry);
+                    }
+                    addMetadataClass(getVirtualMetadataClass(className));
+                }
+            } else {
+                addMetadataClass(getVirtualMetadataClass(className));
+            }
+        } catch (Exception exception) {
+            SessionLog log = getLogger().getSession() != null
+                    ? getLogger().getSession().getSessionLog() : AbstractSessionLog.getLog();
+            if (log.shouldLog(SessionLog.FINEST, SessionLog.METADATA)) {
+                log.logThrowable(SessionLog.FINEST, SessionLog.METADATA, exception);
+            }
+            addMetadataClass(getVirtualMetadataClass(className));
+        } finally {
+            try {
+                if (stream != null) {
+                    stream.close();
+                }
+            } catch (IOException ignore) {
+                // Ignore.
+            }
+        }
+    }
+
+    /**
+     * Return the class metadata for the class name.
+     */
+    @Override
+    public MetadataClass getMetadataClass(String className) {
+        return getMetadataClass(className, false);
+    }
+
+    /**
+     * Return the class metadata for the class name.
+     */
+    @Override
+    public MetadataClass getMetadataClass(String className, boolean isLazy) {
+        if (className == null) {
+            return null;
+        }
+
+        MetadataClass metaClass = m_metadataClasses.get(className);
+        if ((metaClass == null) || (!isLazy && metaClass.isLazy())) {
+            if (metaClass != null) {
+                metaClass.setIsLazy(false);
+            }
+            buildClassMetadata(metaClass, className, isLazy);
+            metaClass = m_metadataClasses.get(className);
+        }
+
+        return metaClass;
+    }
+
+    /**
+     * INTERNAL: This method resolves generic types based on the ASM class
+     * metadata. Unless every other factory (e.g. APT mirror factory) respects
+     * the generic format as built from ASM this method will not work since it
+     * is very tied to it.
+     */
+    @Override
+    public void resolveGenericTypes(MetadataClass child, List<String> genericTypes, MetadataClass parent, MetadataDescriptor descriptor) {
+        // If we have a generic parent we need to grab our generic types
+        // that may be used (and therefore need to be resolved) to map
+        // accessors correctly.
+        if (genericTypes != null) {
+            // The generic types provided map to its parents generic types. The
+            // generics also include the superclass, and interfaces. The parent
+            // generics include the type and ":" and class.
+
+            List<String> parentGenericTypes = parent.getGenericType();
+            if (parentGenericTypes != null) {
+                List genericParentTemp = new ArrayList(genericTypes);
+                genericParentTemp.removeAll(child.getInterfaces());
+
+                int size = genericParentTemp.size();
+                int parentIndex = 0;
+
+                for (int index = genericTypes.indexOf(parent.getName()) + 1; index < size; index++) {
+                    String actualTypeArgument = genericTypes.get(index);
+                    // Ignore extra types on the end of the child, such as
+                    // interface generics.
+                    if (parentIndex >= parentGenericTypes.size()) {
+                        break;
+                    }
+                    String variable = parentGenericTypes.get(parentIndex);
+
+                    // if we get as far as the superclass name in the parent generic type list,
+                    // there is nothing more to process.  We have processed all the generics in the type definition
+                    if (variable.equals(parent.getSuperclassName())){
+                        break;
+                    }
+                    parentIndex = parentIndex + 3;
+
+                    // We are building bottom up and need to link up any
+                    // TypeVariables with the actual class from the originating
+                    // entity.
+                    if (actualTypeArgument.length() == 1) {
+                        index++;
+                        actualTypeArgument = genericTypes.get(index);
+                        descriptor.addGenericType(variable, descriptor.getGenericType(actualTypeArgument));
+                    } else {
+                        descriptor.addGenericType(variable, actualTypeArgument);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Walk the class byte codes and collect the class info.
+     */
+    public class ClassMetadataVisitor extends ClassVisitor {
+
+        private boolean isLazy;
+        private boolean processedMemeber;
+        private MetadataClass classMetadata;
+
+        ClassMetadataVisitor(MetadataClass metadataClass, boolean isLazy) {
+            super(Opcodes.ASM7);
+            this.isLazy = isLazy;
+            this.classMetadata = metadataClass;
+        }
+
+        @Override
+        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+            String className = toClassName(name);
+            if ((this.classMetadata == null) || !this.classMetadata.getName().equals(className)) {
+                this.classMetadata = new MetadataClass(MetadataAsmFactory.this, className, isLazy);
+                addMetadataClass(this.classMetadata);
+            }
+            this.classMetadata.setName(className);
+            this.classMetadata.setSuperclassName(toClassName(superName));
+            this.classMetadata.setModifiers(access);
+            this.classMetadata.setGenericType(processDescription(signature, true));
+
+            for (String interfaceName : interfaces) {
+                this.classMetadata.addInterface(toClassName(interfaceName));
+            }
+        }
+
+        @Override
+        public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
+            this.processedMemeber = true;
+            if (this.classMetadata.isLazy()) {
+                return null;
+            }
+            return new MetadataFieldVisitor(this.classMetadata, access, name, desc, signature, value);
+        }
+
+        @Override
+        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+            this.processedMemeber = true;
+            if (this.classMetadata.isLazy() || name.indexOf("init>") != -1) {
+                return null;
+            }
+            return new MetadataMethodVisitor(this.classMetadata, access, name, signature, desc, exceptions);
+        }
+
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+            boolean isJPA = false;
+            if (desc.startsWith("Lkotlin")) {
+                //ignore kotlin annotations
+                return null;
+            }
+            if (desc.startsWith("Ljava")) {
+                char c = desc.charAt(5);
+                //ignore annotations from 'java' namespace
+                if (c == '/') {
+                    return null;
+                }
+                //ignore annotations from other then 'javax/persistence' namespace
+                if (desc.regionMatches(5, "x/", 0, 2)) {
+                    if (desc.regionMatches(7, "persistence", 0, "persistence".length())) {
+                        isJPA = true;
+                    } else {
+                        return null;
+                    }
+                }
+            }
+            if (desc.startsWith("Ljakarta")) {
+                //ignore annotations from other then 'jakarta/persistence' namespace
+                    if (desc.regionMatches(9, "persistence", 0, "persistence".length())) {
+                        isJPA = true;
+                    } else {
+                        return null;
+                    }
+            }
+            if (!this.processedMemeber && this.classMetadata.isLazy()) {
+                this.classMetadata.setIsLazy(false);
+            }
+            //this currently forbids us to use meta-annotations defined in EclipseLink packages
+            return new MetadataAnnotationVisitor(this.classMetadata, desc, isJPA || desc.startsWith("Lorg/eclipse/persistence"));
+        }
+
+    }
+
+    /**
+     * {@link AnnotationVisitor} used to process class, field , and method
+     * annotations populating a {@link MetadataAnnotation} and its nested state.
+     *
+     * @see MetadataAnnotationArrayVisitor for population of array attributes
+     */
+    class MetadataAnnotationVisitor extends AnnotationVisitor {
+
+        /**
+         * Element the annotation is being applied to. If this is null the
+         * {@link MetadataAnnotation} being constructed is a nested annotation
+         * and is already referenced from its parent.
+         */
+        private MetadataAnnotatedElement element;
+
+        /**
+         * {@link MetadataAnnotation} being populated
+         */
+        private MetadataAnnotation annotation;
+
+        MetadataAnnotationVisitor(MetadataAnnotatedElement element, String name) {
+            this(element, name, true);
+        }
+
+        MetadataAnnotationVisitor(MetadataAnnotatedElement element, String name, boolean isRegular) {
+            super(Opcodes.ASM7);
+            this.element = element;
+            this.annotation = new MetadataAnnotation();
+            this.annotation.setName(processDescription(name, false).get(0));
+            this.annotation.setIsMeta(!isRegular);
+        }
+
+        public MetadataAnnotationVisitor(MetadataAnnotation annotation) {
+            super(Opcodes.ASM7);
+            this.annotation = annotation;
+        }
+
+        @Override
+        public void visit(String name, Object value) {
+            this.annotation.addAttribute(name, annotationValue(null, value));
+        }
+
+        @Override
+        public void visitEnum(String name, String desc, String value) {
+            this.annotation.addAttribute(name, annotationValue(desc, value));
+        }
+
+        @Override
+        public AnnotationVisitor visitAnnotation(String name, String desc) {
+            MetadataAnnotation mda = new MetadataAnnotation();
+            mda.setName(processDescription(desc, false).get(0));
+            this.annotation.addAttribute(name, mda);
+            return new MetadataAnnotationVisitor(mda);
+        }
+
+        @Override
+        public AnnotationVisitor visitArray(String name) {
+            return new MetadataAnnotationArrayVisitor(this.annotation, name);
+        }
+
+        @Override
+        public void visitEnd() {
+            if (this.element != null) {
+                if (this.annotation.isMeta()) {
+                    this.element.addMetaAnnotation(this.annotation);
+                } else {
+                    this.element.addAnnotation(this.annotation);
+                }
+            }
+        }
+    }
+
+    /**
+     * Specialized visitor to handle the population of arrays of annotation
+     * values.
+     */
+    class MetadataAnnotationArrayVisitor extends AnnotationVisitor {
+
+        private MetadataAnnotation annotation;
+
+        private String attributeName;
+
+        private List<Object> values;
+
+        public MetadataAnnotationArrayVisitor(MetadataAnnotation annotation, String name) {
+            super(Opcodes.ASM7);
+            this.annotation = annotation;
+            this.attributeName = name;
+            this.values = new ArrayList<Object>();
+        }
+
+        @Override
+        public void visit(String name, Object value) {
+            this.values.add(annotationValue(null, value));
+        }
+
+        @Override
+        public void visitEnum(String name, String desc, String value) {
+            this.values.add(annotationValue(desc, value));
+        }
+
+        @Override
+        public AnnotationVisitor visitAnnotation(String name, String desc) {
+            MetadataAnnotation mda = new MetadataAnnotation();
+            mda.setName(processDescription(desc, false).get(0));
+            this.values.add(mda);
+            return new MetadataAnnotationVisitor(mda);
+        }
+
+        @Override
+        public void visitEnd() {
+            this.annotation.addAttribute(this.attributeName, this.values.toArray());
+        }
+    }
+
+    /**
+     * Factory for the creation of {@link MetadataField} handling basic type,
+     * generics, and annotations.
+     */
+    class MetadataFieldVisitor extends FieldVisitor {
+
+        private MetadataField field;
+
+        public MetadataFieldVisitor(MetadataClass classMetadata, int access, String name, String desc, String signature, Object value) {
+            super(Opcodes.ASM7);
+            this.field = new MetadataField(classMetadata);
+            this.field.setModifiers(access);
+            this.field.setName(name);
+            this.field.setAttributeName(name);
+            this.field.setGenericType(processDescription(signature, true));
+            this.field.setType(processDescription(desc, false).get(0));
+        }
+
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+            if (desc.startsWith("Ljavax/persistence") || desc.startsWith("Ljakarta/persistence")
+                    || desc.startsWith("Lorg/eclipse/persistence")) {
+                return new MetadataAnnotationVisitor(this.field, desc);
+            }
+            return null;
+        }
+
+        @Override
+        public void visitEnd() {
+            this.field.getDeclaringClass().addField(this.field);
+        }
+    }
+
+    /**
+     * Factory for the creation of {@link MetadataMethod} handling basic type,
+     * generics, and annotations.
+     */
+    // Note: Subclassed EmptyListener to minimize signature requirements for
+    // ignored MethodVisitor API
+    class MetadataMethodVisitor extends MethodVisitor {
+
+        private MetadataMethod method;
+
+        public MetadataMethodVisitor(MetadataClass classMetadata, int access, String name, String desc, String signature, String[] exceptions) {
+            super(Opcodes.ASM7);
+            this.method = new MetadataMethod(MetadataAsmFactory.this, classMetadata);
+
+            this.method.setName(name);
+            this.method.setAttributeName(Helper.getAttributeNameFromMethodName(name));
+            this.method.setModifiers(access);
+
+            this.method.setGenericType(processDescription(desc, true));
+
+            List<String> argumentNames = processDescription(signature, false);
+            if (argumentNames != null && !argumentNames.isEmpty()) {
+                this.method.setReturnType(argumentNames.get(argumentNames.size() - 1));
+                argumentNames.remove(argumentNames.size() - 1);
+                this.method.setParameters(argumentNames);
+            }
+        }
+
+        @Override
+        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+            if (desc.startsWith("Ljavax/persistence") || desc.startsWith("Ljakarta/persistence")
+                    || desc.startsWith("Lorg/eclipse/persistence")) {
+                return new MetadataAnnotationVisitor(this.method, desc);
+            }
+            return null;
+        }
+
+        /**
+         * At the end of visiting this method add it to the
+         * {@link MetadataClass} and handle duplicate method names by chaining
+         * them.
+         */
+        @Override
+        public void visitEnd() {
+            MetadataClass classMetadata = this.method.getMetadataClass();
+
+            MetadataMethod existing = classMetadata.getMethods().get(this.method.getName());
+            if (existing == null) {
+                classMetadata.getMethods().put(this.method.getName(), this.method);
+            } else {
+                // Handle methods with the same name.
+                while (existing.getNext() != null) {
+                    existing = existing.getNext();
+                }
+                existing.setNext(this.method);
+            }
+        }
+
+    }
+
+    /**
+     * Get MetadataClass for a class which can not be found
+     * @param className class which has not been found
+     * @return MetadataClass
+     */
+    private MetadataClass getVirtualMetadataClass(String className) {
+        // Some basic types can't be found, so can just be registered
+        // (i.e. arrays). Also, VIRTUAL classes may also not exist,
+        // therefore, tag the MetadataClass as loadable false. This will be
+        // used to determine if a class will be dynamically created or not.
+        MetadataClass metadataClass = new MetadataClass(this, className, false);
+        // If the class is a JDK class, then maybe there is a class loader
+        // issues,
+        // since it is a JDK class, just use reflection.
+        if ((className.length() > 5) && className.substring(0, 5).equals("java.")) {
+            try {
+                Class reflectClass = Class.forName(className);
+                if (reflectClass.getSuperclass() != null) {
+                    metadataClass.setSuperclassName(reflectClass.getSuperclass().getName());
+                }
+                for (Class reflectInterface : reflectClass.getInterfaces()) {
+                    metadataClass.addInterface(reflectInterface.getName());
+                }
+            } catch (Exception failed) {
+                SessionLog log = getLogger().getSession() != null
+                        ? getLogger().getSession().getSessionLog() : AbstractSessionLog.getLog();
+                if (log.shouldLog(SessionLog.FINE, SessionLog.METADATA)) {
+                    log.logThrowable(SessionLog.FINE, SessionLog.METADATA, failed);
+                }
+                metadataClass.setIsAccessible(false);
+            }
+        } else {
+            metadataClass.setIsAccessible(false);
+        }
+        return metadataClass;
+    }
+
+    /**
+     * Process the byte-code argument description and return the array of Java
+     * class names. i.e.
+     * "(Lorg/foo/Bar;Z)Ljava/lang/Boolean;"=>[org.foo.Bar,boolean
+     * ,java.lang.Boolean]
+     */
+    private static List<String> processDescription(String desc, boolean isGeneric) {
+        if (desc == null) {
+            return null;
+        }
+        List<String> arguments = new ArrayList<String>();
+        int index = 0;
+        int length = desc.length();
+        boolean isGenericTyped=false;
+        // PERF: Use char array to make char index faster (note this is a heavily optimized method, be very careful on changes)
+        char[] chars = desc.toCharArray();
+        while (index < length) {
+            char next = chars[index];
+            if (('(' != next) && (')' != next) && ('<' != next) && ('>' != next) && (';' != next)) {
+                if (next == 'L') {
+                    index++;
+                    int start = index;
+                    next = chars[index];
+                    while (('(' != next) && (')' != next) && ('<' != next) && ('>' != next) && (';' != next)) {
+                        index++;
+                        next = chars[index];
+                    }
+                    arguments.add(toClassName(desc.substring(start, index)));
+                    if(isGenericTyped) {
+                        isGenericTyped=false;
+                        if(next == '<') {
+                            int cnt = 1;
+                            while((cnt > 0) && (++index<desc.length())) {
+                               switch (desc.charAt(index)) {
+                                    case '<': cnt ++; break;
+                                    case '>': cnt --; break;
+                               }
+                            }
+                         }
+                     }
+                } else if (!isGeneric && (PRIMITIVES.indexOf(next) != -1)) {
+                    // Primitives.
+                    arguments.add(getPrimitiveName(next));
+                } else if (next == '[') {
+                    // Arrays.
+                    int start = index;
+                    index++;
+                    next = chars[index];
+                    // Nested arrays.
+                    while (next == '[') {
+                        index++;
+                        next = chars[index];
+                    }
+                    if (PRIMITIVES.indexOf(next) == -1) {
+                        while (next != ';') {
+                            index++;
+                            next = chars[index];
+                        }
+                        arguments.add(toClassName(desc.substring(start, index + 1)));
+                    } else {
+                        arguments.add(desc.substring(start, index + 1));
+                    }
+                } else {
+                    // Is a generic type variable.
+                    int start = index;
+                    int end = start;
+
+                    char myNext = next;
+
+                    while (':' != myNext && '(' != myNext && ')' != myNext && '<' != myNext && '>' != myNext && ';' != myNext && end < length - 1) {
+                        end++;
+                        myNext = chars[end];
+                    }
+
+                    if (myNext == ':') {
+                        arguments.add(desc.substring(start, end));
+                        isGenericTyped=true;
+                        index = end;
+                        arguments.add(":");
+                        if(desc.charAt(index+1)==':') {
+                            index ++;
+                        }
+                    } else if (myNext == ';' && next == 'T') {
+                        arguments.add(new String(new char[] { next }));
+                        arguments.add(desc.substring(start + 1, end));
+                        index = end - 1;
+                    } else {
+                        arguments.add(new String(new char[] { next }));
+                    }
+                }
+            }
+            index++;
+        }
+        return arguments;
+
+    }
+
+    /**
+     * Return the Java type name for the primitive code.
+     */
+    private static String getPrimitiveName(char primitive) {
+        if (primitive == 'V') {
+            return "void";
+        } else if (primitive == 'I') {
+            return "int";
+        } else if (primitive == 'Z') {
+            return "boolean";
+        } else if (primitive == 'J') {
+            return "long";
+        } else if (primitive == 'F') {
+            return "float";
+        } else if (primitive == 'D') {
+            return "double";
+        } else if (primitive == 'B') {
+            return "byte";
+        } else if (primitive == 'C') {
+            return "char";
+        } else if (primitive == 'S') {
+            return "short";
+        } else {
+            return new String(new char[] { primitive });
+        }
+    }
+
+    private static String toClassName(String classDescription) {
+        if (classDescription == null) {
+            return "void";
+        }
+        return classDescription.replace('/', '.');
+    }
+
+    /**
+     * Convert the annotation value into the value used in the meta model
+     */
+    private static Object annotationValue(String description, Object value) {
+        if (value instanceof Type) {
+            return ((Type) value).getClassName();
+        }
+        return value;
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataClass.java b/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataClass.java
new file mode 100644
index 0000000..0670e30
--- /dev/null
+++ b/jakarta/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataClass.java
@@ -0,0 +1,628 @@
+/*
+ * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+// Contributors:
+//     Oracle - initial API and implementation from Oracle TopLink
+//     05/16/2008-1.0M8 Guy Pelletier
+//       - 218084: Implement metadata merging functionality between mapping files
+//     03/08/2010-2.1 Guy Pelletier
+//       - 303632: Add attribute-type for mapping attributes to EclipseLink-ORM
+//     05/04/2010-2.1 Guy Pelletier
+//       - 309373: Add parent class attribute to EclipseLink-ORM
+//     05/14/2010-2.1 Guy Pelletier
+//       - 253083: Add support for dynamic persistence using ORM.xml/eclipselink-orm.xml
+//     01/25/2011-2.3 Guy Pelletier
+//       - 333488: Serializable attribute being defaulted to a variable one to one mapping and causing exception
+//     07/16/2013-2.5.1 Guy Pelletier
+//       - 412384: Applying Converter for parameterized basic-type for joda-time's DateTime does not work
+package org.eclipse.persistence.internal.jpa.metadata.accessors.objects;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.persistence.internal.helper.Helper;
+import org.eclipse.persistence.internal.libraries.asm.Opcodes;
+
+/**
+ * INTERNAL:
+ * An object to hold onto a valid JPA decorated class.
+ *
+ * @author Guy Pelletier
+ * @since TopLink 10.1.3/EJB 3.0 Preview
+ */
+public class MetadataClass extends MetadataAnnotatedElement {
+    protected boolean m_isLazy;
+    protected boolean m_isAccessible;
+    protected boolean m_isPrimitive;
+    protected boolean m_isJDK;
+    protected int m_modifiers;
+
+    // Stores the implements interfaces of this class.
+    protected List<String> m_interfaces;
+
+    // Stores a list of enclosed classes found inside this metadata class.
+    // E.g. inner classes, enums etc.
+    protected List<MetadataClass> m_enclosedClasses;
+
+    // Store the classes field metadata, keyed by the field's name.
+    protected Map<String, MetadataField> m_fields;
+
+    // Store the classes method metadata, keyed by the method's name.
+    // Method's next is used if multiple method with the same name.
+    protected Map<String, MetadataMethod> m_methods;
+
+    protected MetadataClass m_superclass;
+    protected String m_superclassName;
+
+    /**
+     * Create the metadata class with the class name.
+     */
+    public MetadataClass(MetadataFactory factory, String name, boolean isLazy) {
+        super(factory);
+        setName(name);
+
+        // By default, set the type to be the same as the name. The canonical
+        // model generator relies on types which in most cases is the name, but
+        // the generator resolves generic types a little differently to
+        // correctly generate model classes.
+        setType(name);
+
+        m_isAccessible = true;
+        m_isLazy = isLazy;
+    }
+
+    /**
+     * Create the metadata class with the class name.
+     */
+    public MetadataClass(MetadataFactory factory, String name) {
+        this(factory, name, false);
+    }
+
+    /**
+     * Create the metadata class based on the class.
+     * Mainly used for primitive defaults.
+     */
+    public MetadataClass(MetadataFactory factory, Class cls) {
+        this(factory, cls.getName(), false);
+        m_isPrimitive = cls.isPrimitive();
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void addEnclosedClass(MetadataClass enclosedClass) {
+        if (m_enclosedClasses == null) {
+            m_enclosedClasses = new ArrayList<MetadataClass>();
+        }
+
+        m_enclosedClasses.add(enclosedClass);
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void addField(MetadataField field) {
+        if (m_fields == null) {
+            m_fields = new HashMap<String, MetadataField>();
+        }
+
+        m_fields.put(field.getName(), field);
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void addInterface(String interfaceName) {
+        if (m_interfaces == null) {
+            m_interfaces = new ArrayList<String>();
+        }
+
+        m_interfaces.add(interfaceName);
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void addMethod(MetadataMethod method) {
+        if (m_methods == null) {
+            m_methods = new HashMap<String, MetadataMethod>();
+        }
+
+        m_methods.put(method.getName(), method);
+    }
+
+    /**
+     * Allow comparison to Java classes and Metadata classes.
+     */
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof Class) {
+            if (getName() == null) {
+                // Void's name is null.
+                return ((Class)object).getName() == null;
+            }
+
+            return getName().equals(((Class)object).getName());
+        }
+
+        return super.equals(object);
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this class is or extends, or super class extends the class.
+     */
+    public boolean extendsClass(Class javaClass) {
+        return extendsClass(javaClass.getName());
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this class is or extends, or super class extends the class.
+     */
+    public boolean extendsClass(String className) {
+        if (getName() == null) {
+            return className == null;
+        }
+
+        if (getName().equals(className)) {
+            return true;
+        }
+
+        if (getSuperclassName() == null) {
+            return false;
+        }
+
+        if (getSuperclassName().equals(className)) {
+            return true;
+        }
+
+        return getSuperclass().extendsClass(className);
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this class is or extends, or super class extends the interface.
+     */
+    public boolean extendsInterface(Class javaClass) {
+        return extendsInterface(javaClass.getName());
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this class is or extends, or super class extends the interface.
+     */
+    public boolean extendsInterface(String className) {
+        if (getName() == null) {
+            return false;
+        }
+
+        if (getName().equals(className)) {
+            return true;
+        }
+
+        if (getInterfaces().contains(className)) {
+            return true;
+        }
+
+        for (String interfaceName : getInterfaces()) {
+            if (getMetadataClass(interfaceName).extendsInterface(className)) {
+                return true;
+            }
+        }
+
+        if (getSuperclassName() == null) {
+            return false;
+        }
+
+        return getSuperclass().extendsInterface(className);
+    }
+
+    /**
+     * INTERNAL:
+     * Return the list of classes defined within this metadata class. E.g.
+     * enums and inner classes.
+     */
+    public List<MetadataClass> getEnclosedClasses() {
+        if (m_enclosedClasses == null) {
+            m_enclosedClasses = new ArrayList<MetadataClass>();
+        }
+
+        return m_enclosedClasses;
+    }
+
+    /**
+     * INTERNAL:
+     * Return the field with the name.
+     * Search for any declared or inherited field.
+     */
+    public MetadataField getField(String name) {
+        return getField(name, true);
+    }
+
+    /**
+     * INTERNAL:
+     * Return the field with the name.
+     * Search for any declared or inherited field.
+     */
+    public MetadataField getField(String name, boolean checkSuperClass) {
+        MetadataField field = getFields().get(name);
+
+        if (checkSuperClass && (field == null) && (getSuperclassName() != null)) {
+            return getSuperclass().getField(name);
+        }
+
+        return field;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public Map<String, MetadataField> getFields() {
+        if (m_fields == null) {
+            m_fields = new HashMap<String, MetadataField>();
+
+            if (m_isLazy) {
+                m_factory.getMetadataClass(getName(), false);
+            }
+        }
+
+        return m_fields;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public List<String> getInterfaces() {
+        if (m_interfaces == null) {
+            m_interfaces = new ArrayList<String>();
+        }
+
+        return m_interfaces;
+    }
+
+    /**
+     * INTERNAL:
+     * Return the method with the name and no arguments.
+     */
+    protected MetadataMethod getMethod(String name) {
+        return getMethods().get(name);
+    }
+
+    /**
+     * INTERNAL:
+     * Return the method with the name and argument types.
+     */
+    public MetadataMethod getMethod(String name, Class[] arguments) {
+        List<String> argumentNames = new ArrayList<String>(arguments.length);
+
+        for (int index = 0; index < arguments.length; index++) {
+            argumentNames.add(arguments[index].getName());
+        }
+
+        return getMethod(name, argumentNames);
+    }
+
+    /**
+     * INTERNAL:
+     * Return the method with the name and argument types (class names).
+     */
+    public MetadataMethod getMethod(String name, List<String> arguments) {
+        return getMethod(name, arguments, true);
+    }
+
+    /**
+     * INTERNAL:
+     * Return the method with the name and argument types (class names).
+     */
+    public MetadataMethod getMethod(String name, List<String> arguments, boolean checkSuperClass) {
+        MetadataMethod method = getMethods().get(name);
+
+        while ((method != null) && !method.getParameters().equals(arguments)) {
+            method = method.getNext();
+        }
+
+        if (checkSuperClass && (method == null) && (getSuperclassName() != null)) {
+            return getSuperclass().getMethod(name, arguments);
+        }
+
+        return method;
+    }
+
+    /**
+     * INTERNAL:
+     * Return the method with the name and argument types (class names).
+     */
+    public MetadataMethod getMethod(String name, String[] arguments) {
+        return getMethod(name, Arrays.asList(arguments));
+    }
+
+    /**
+     * INTERNAL:
+     * Return the method for the given property name.
+     */
+    public MetadataMethod getMethodForPropertyName(String propertyName) {
+        MetadataMethod method;
+
+        String leadingChar = String.valueOf(propertyName.charAt(0)).toUpperCase();
+        String restOfName = propertyName.substring(1);
+
+        // Look for a getPropertyName() method
+        method = getMethod(Helper.GET_PROPERTY_METHOD_PREFIX.concat(leadingChar).concat(restOfName), new String[]{});
+
+        if (method == null) {
+            // Look for an isPropertyName() method
+            method = getMethod(Helper.IS_PROPERTY_METHOD_PREFIX.concat(leadingChar).concat(restOfName), new String[]{});
+        }
+
+        if (method != null) {
+            method.setSetMethod(method.getSetMethod(this));
+        }
+
+        return method;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public Map<String, MetadataMethod> getMethods() {
+        if (m_methods == null) {
+            m_methods = new HashMap<String, MetadataMethod>();
+
+            if (m_isLazy) {
+                m_factory.getMetadataClass(getName(), false);
+            }
+        }
+        return m_methods;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    @Override
+    public int getModifiers() {
+        return m_modifiers;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public MetadataClass getSuperclass() {
+        if (m_superclass == null) {
+            m_superclass = getMetadataClass(m_superclassName);
+        }
+
+        return m_superclass;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public String getSuperclassName() {
+        return m_superclassName;
+    }
+
+    /**
+     * Return the ASM type name.
+     */
+    public String getTypeName() {
+        if (isArray()) {
+            return getName().replace('.', '/');
+        } else if (isPrimitive()) {
+            if (getName().equals("int")) {
+                return "I";
+            } else if (getName().equals("long")) {
+                return "J";
+            } else if (getName().equals("short")) {
+                return "S";
+            } else if (getName().equals("boolean")) {
+                return "Z";
+            } else if (getName().equals("float")) {
+                return "F";
+            } else if (getName().equals("double")) {
+                return "D";
+            } else if (getName().equals("char")) {
+                return "C";
+            } else if (getName().equals("byte")) {
+                return "B";
+            }
+        }
+        return "L" + getName().replace('.', '/') + ";";
+    }
+
+    /**
+     * INTERNAL:
+     * Return true is this class accessible to be found.
+     */
+    public boolean isAccessible() {
+        return m_isAccessible;
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this class is an array type.
+     */
+    public boolean isArray() {
+        return (getName() != null) && (getName().charAt(0) == '[');
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is extends Collection.
+     */
+    public boolean isCollection() {
+        return extendsInterface(Collection.class);
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is extends Enum.
+     */
+    public boolean isEnum() {
+        return extendsClass(Enum.class);
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is an interface (super is null).
+     */
+    public boolean isInterface() {
+        return (Opcodes.ACC_INTERFACE & m_modifiers) != 0;
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is a JDK (java/javax) class.
+     */
+    public boolean isJDK() {
+        return m_isJDK;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public boolean isLazy() {
+        return m_isLazy;
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is extends List.
+     */
+    public boolean isList() {
+        return extendsInterface(List.class);
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is extends Map.
+     */
+    public boolean isMap() {
+        return extendsInterface(Map.class);
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is Object class.
+     */
+    public boolean isObject() {
+        return getName().equals(Object.class.getName());
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is a primitive.
+     */
+    public boolean isPrimitive() {
+        return m_isPrimitive;
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this class extends Serializable or is an array type.
+     */
+    public boolean isSerializable() {
+        if (isArray()) {
+            return true;
+        }
+
+        return extendsInterface(Serializable.class);
+    }
+
+    /**
+     * INTENAL:
+     * Return true is this class is the Serializable.class interface.
+     */
+    public boolean isSerializableInterface() {
+        return getName().equals(Serializable.class.getName());
+    }
+
+    /**
+     * INTERNAL:
+     * Return true if this extends Set.
+     */
+    public boolean isSet() {
+        return extendsInterface(Set.class);
+    }
+
+    /**
+     * INTERNAL:
+     * Return if this is the void class.
+     */
+    public boolean isVoid() {
+        return getName().equals(void.class.getName()) || getName().equals(Void.class.getName());
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void setIsAccessible(boolean isAccessible) {
+        m_isAccessible = isAccessible;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void setIsJDK(boolean isJDK) {
+        m_isJDK = isJDK;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void setIsLazy(boolean isLazy) {
+        m_isLazy = isLazy;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    @Override
+    public void setModifiers(int modifiers) {
+        m_modifiers = modifiers;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    @Override
+    public void setName(String name) {
+        super.setName(name);
+
+        if ((!MetadataFactory.ALLOW_JDK) && (name.startsWith("java.")
+                || name.startsWith("javax.") || name.startsWith("jakarta.")
+                || name.startsWith("org.eclipse.persistence.internal."))) {
+            setIsJDK(true);
+        }
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void setSuperclass(MetadataClass superclass) {
+        m_superclass = superclass;
+    }
+
+    /**
+     * INTERNAL:
+     */
+    public void setSuperclassName(String superclass) {
+        m_superclassName = superclass;
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/eclipse/persistence/jaxb/javamodel/Helper.java b/jakarta/src/patch/java/org/eclipse/persistence/jaxb/javamodel/Helper.java
new file mode 100644
index 0000000..a8318eb
--- /dev/null
+++ b/jakarta/src/patch/java/org/eclipse/persistence/jaxb/javamodel/Helper.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+// Contributors:
+//     Oracle - initial API and implementation from Oracle TopLink
+package org.eclipse.persistence.jaxb.javamodel;
+
+import static org.eclipse.persistence.jaxb.JAXBContextFactory.PKG_SEPARATOR;
+import static org.eclipse.persistence.jaxb.compiler.XMLProcessor.DEFAULT;
+
+import java.lang.annotation.Annotation;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
+
+import jakarta.xml.bind.JAXBElement;
+
+import org.eclipse.persistence.internal.oxm.Constants;
+
+/**
+ * INTERNAL:
+ * <p><b>Purpose:</b>To provide helper methods and constants to assist
+ * in integrating TopLink JAXB 2.0 Generation with the JDEV JOT APIs.
+ * <p><b>Responsibilities:</b>
+ * <ul>
+ * <li>Make available a map of JOT - XML type pairs</li>
+ * <li>Redirect method calls to the current JavaModel implementation as
+ * required</li>
+ * <li>Provide methods for accessing generics, annotations, etc. on a
+ * given implementaiton's classes</li>
+ * <li>Provide a dynamic proxy instance for a given JavaAnnotation in
+ * the JOT implementation (for reflection a Java SDK annotation is
+ * returned)</li>
+ * </ul>
+ *
+ * @since Oracle TopLink 11.1.1.0.0
+ * @see JavaModel
+ * @see AnnotationProxy
+ *
+ */
+public class Helper {
+    protected ClassLoader loader;
+    protected JavaModel jModel;
+    private HashMap xmlToJavaTypeMap;
+    private boolean facets;
+
+    public final static String APBYTE = "byte[]";
+    public final static String BIGDECIMAL = "java.math.BigDecimal";
+    public final static String BIGINTEGER = "java.math.BigInteger";
+    public final static String PBOOLEAN = "boolean";
+    public final static String PBYTE = "byte";
+    public final static String CALENDAR = "java.util.Calendar";
+    public final static String CHARACTER = "java.lang.Character";
+    public final static String CHAR = "char";
+    public final static String OBJECT = "java.lang.Object";
+    public final static String CLASS = "java.lang.Class";
+    public final static String PDOUBLE = "double";
+    public final static String PFLOAT = "float";
+    public final static String PINT = "int";
+    public final static String PLONG = "long";
+    public final static String PSHORT = "short";
+    public final static String QNAME_CLASS = "javax.xml.namespace.QName";
+    public final static String STRING = "java.lang.String";
+    public final static String ABYTE = "java.lang.Byte[]";
+    public final static String BOOLEAN = "java.lang.Boolean";
+    public final static String BYTE = "java.lang.Byte";
+    public final static String GREGORIAN_CALENDAR = "java.util.GregorianCalendar";
+    public final static String DOUBLE = "java.lang.Double";
+    public final static String FLOAT = "java.lang.Float";
+    public final static String INTEGER = "java.lang.Integer";
+    public final static String UUID = "java.util.UUID";
+    public final static String LONG = "java.lang.Long";
+    public final static String SHORT = "java.lang.Short";
+    public final static String UTIL_DATE = "java.util.Date";
+    public final static String SQL_DATE = "java.sql.Date";
+    public final static String SQL_TIME = "java.sql.Time";
+    public final static String SQL_TIMESTAMP = "java.sql.Timestamp";
+    public final static String DURATION = "javax.xml.datatype.Duration";
+    public final static String XMLGREGORIANCALENDAR = "javax.xml.datatype.XMLGregorianCalendar";
+    public final static String URI = "java.net.URI";
+    public final static String URL = "java.net.URL";
+    protected final static String JAVA_PKG = "java.";
+    protected final static String JAVAX_PKG = "javax.";
+    protected final static String JAKARTA_PKG = "jakarta.";
+    protected final static String JAVAX_WS_PKG = "javax.xml.ws.";
+    protected final static String JAKARTA_WS_PKG = "jakarta.xml.ws.";
+    protected final static String JAVAX_RPC_PKG = "javax.xml.rpc.";
+    protected final static String JAKARTA_RPC_PKG = "jakarta.xml.rpc.";
+
+    private JavaClass collectionClass;
+    private JavaClass setClass;
+    private JavaClass listClass;
+    private JavaClass mapClass;
+    private JavaClass jaxbElementClass;
+    private JavaClass objectClass;
+
+    /**
+     * INTERNAL:
+     * This is the preferred constructor.
+     *
+     * This constructor builds the map of XML-Java type pairs,
+     * and sets the JavaModel and ClassLoader.
+     *
+     * @param model
+     */
+    public Helper(JavaModel model) {
+        xmlToJavaTypeMap = buildXMLToJavaTypeMap();
+        setJavaModel(model);
+        setClassLoader(model.getClassLoader());
+        collectionClass = getJavaClass(CoreClassConstants.Collection_Class);
+        listClass = getJavaClass(CoreClassConstants.List_Class);
+        setClass = getJavaClass(CoreClassConstants.Set_Class);
+        mapClass = getJavaClass(CoreClassConstants.Map_Class);
+        jaxbElementClass = getJavaClass(JAXBElement.class);
+        objectClass = getJavaClass(CoreClassConstants.OBJECT);
+    }
+
+    /**
+     * Builds a map of Java types to XML types.
+     *
+     * @return
+     */
+    private HashMap buildXMLToJavaTypeMap() {
+        HashMap javaTypes = new HashMap();
+        // jaxb 2.0 spec pairs
+        javaTypes.put(APBYTE, Constants.BASE_64_BINARY_QNAME);
+        javaTypes.put(BIGDECIMAL, Constants.DECIMAL_QNAME);
+        javaTypes.put(BIGINTEGER, Constants.INTEGER_QNAME);
+        javaTypes.put(PBOOLEAN, Constants.BOOLEAN_QNAME);
+        javaTypes.put(PBYTE, Constants.BYTE_QNAME);
+        javaTypes.put(CALENDAR, Constants.DATE_TIME_QNAME);
+        javaTypes.put(PDOUBLE, Constants.DOUBLE_QNAME);
+        javaTypes.put(PFLOAT, Constants.FLOAT_QNAME);
+        javaTypes.put(PINT, Constants.INT_QNAME);
+        javaTypes.put(PLONG, Constants.LONG_QNAME);
+        javaTypes.put(PSHORT, Constants.SHORT_QNAME);
+        javaTypes.put(QNAME_CLASS, Constants.QNAME_QNAME);
+        javaTypes.put(STRING, Constants.STRING_QNAME);
+        javaTypes.put(CHAR, Constants.STRING_QNAME);
+        javaTypes.put(CHARACTER, Constants.STRING_QNAME);
+        // other pairs
+        javaTypes.put(ABYTE, Constants.BYTE_QNAME);
+        javaTypes.put(BOOLEAN, Constants.BOOLEAN_QNAME);
+        javaTypes.put(BYTE, Constants.BYTE_QNAME);
+        javaTypes.put(CLASS, Constants.STRING_QNAME);
+        javaTypes.put(GREGORIAN_CALENDAR, Constants.DATE_TIME_QNAME);
+        javaTypes.put(DOUBLE, Constants.DOUBLE_QNAME);
+        javaTypes.put(FLOAT, Constants.FLOAT_QNAME);
+        javaTypes.put(INTEGER, Constants.INT_QNAME);
+        javaTypes.put(LONG, Constants.LONG_QNAME);
+        javaTypes.put(OBJECT, Constants.ANY_TYPE_QNAME);
+        javaTypes.put(SHORT, Constants.SHORT_QNAME);
+        javaTypes.put(UTIL_DATE, Constants.DATE_TIME_QNAME);
+        javaTypes.put(SQL_DATE, Constants.DATE_QNAME);
+        javaTypes.put(SQL_TIME, Constants.TIME_QNAME);
+        javaTypes.put(SQL_TIMESTAMP, Constants.DATE_TIME_QNAME);
+        javaTypes.put(DURATION, Constants.DURATION_QNAME);
+        javaTypes.put(UUID, Constants.STRING_QNAME);
+        javaTypes.put(URI, Constants.STRING_QNAME);
+        javaTypes.put(URL, Constants.ANY_URI_QNAME);
+        return javaTypes;
+    }
+
+    /**
+     * Return a given method's generic return type as a JavaClass.
+     *
+     * @param meth
+     * @return
+     */
+    public JavaClass getGenericReturnType(JavaMethod meth) {
+        JavaClass result = meth.getReturnType();
+        JavaClass jClass = null;
+        if (result == null) { return null; }
+
+        Collection args = result.getActualTypeArguments();
+        if (args.size() >0) {
+            jClass = (JavaClass) args.iterator().next();
+        }
+        return jClass;
+    }
+
+    /**
+     * Return a JavaClass instance created based the provided class.
+     * This assumes that the provided class exists on the classpath
+     * - null is returned otherwise.
+     *
+     * @param javaClass
+     * @return
+     */
+    public JavaClass getJavaClass(Class javaClass) {
+        return jModel.getClass(javaClass);
+    }
+
+    /**
+     * Return array of JavaClass instances created based on the provided classes.
+     * This assumes provided classes exist on the classpath.
+     *
+     * @param classes
+     * @return JavaClass array
+     */
+    public JavaClass[] getJavaClassArray(Class... classes) {
+        if (0 == classes.length) {
+            return new JavaClass[0];
+        }
+        JavaClass[] result = new JavaClass[classes.length];
+        int i = 0;
+        for (Class clazz : classes) {
+            result[i++] = getJavaClass(clazz);
+        }
+        return result;
+    }
+
+    /**
+     * Return a JavaClass instance created based on fully qualified
+     * class name.  This assumes that a class with the provided name
+     * exists on the classpath - null is returned otherwise.
+     *
+     * @param javaClassName
+     * @return
+     */
+    public JavaClass getJavaClass(String javaClassName) {
+        return jModel.getClass(javaClassName);
+    }
+
+    /**
+     * Return a map of default Java types to XML types.
+     * @return
+     */
+    public HashMap getXMLToJavaTypeMap() {
+        return xmlToJavaTypeMap;
+    }
+
+    /**
+     * Returns a either a dynamic proxy instance that allows an element
+     * to be treated as an annotation (for JOT), or a Java annotation
+     * (for Reflection), or null if the specified annotation does not
+     * exist.
+     * Intended to be used in conjunction with isAnnotationPresent.
+     *
+     * @param element
+     * @param annotationClass
+     * @return
+     * @see #isAnnotationPresent
+     */
+    public Annotation getAnnotation(JavaHasAnnotations element, Class annotationClass) {
+        JavaAnnotation janno = element.getAnnotation(jModel.getClass(annotationClass));
+        if (janno == null) {
+            return null;
+        }
+        return jModel.getAnnotation(janno, annotationClass);
+    }
+
+    /**
+     * Returns a JavaClass instance wrapping the provided field's resolved
+     * type.
+     *
+     * @param field
+     * @return
+     */
+    public JavaClass getType(JavaField field) {
+        JavaClass type = field.getResolvedType();
+        try {
+            return jModel.getClass(type.getRawName());
+        } catch (Exception x) {}
+        return null;
+    }
+
+    /**
+     * Return a JavaClass instance based on the @see jakarta.xml.bind.JAXBElement .
+     *
+     * Replacement of direct access to JAXBELEMENT_CLASS field.
+     *
+     * @return
+     */
+    public JavaClass getJaxbElementClass() {
+        return jaxbElementClass;
+    }
+
+    /**
+     * Return a JavaClass instance based on the @see java.lang.Object .
+     *
+     * Replacement of direct access to OBJECT_CLASS field.
+     *
+     * @return
+     */
+    public JavaClass getObjectClass() {
+        return objectClass;
+    }
+
+    /**
+     * Indicates if element contains a given annotation.
+     *
+     * @param element
+     * @param annotationClass
+     * @return
+     */
+    public boolean isAnnotationPresent(JavaHasAnnotations element, Class annotationClass) {
+        if (element == null || annotationClass == null) {
+            return false;
+        }
+        return (element.getAnnotation(jModel.getClass(annotationClass)) != null);
+    }
+
+    /**
+     * Indicates if a given JavaClass is a built-in Java type.
+     *
+     * A JavaClass is considered to be a built-in type if:
+     * 1 - the XMLToJavaTypeMap map contains a key equal to the provided
+     *     JavaClass' raw name
+     * 2 - the provided JavaClass' raw name starts with "java."
+     * 3 - the provided JavaClass' raw name starts with "javax.", with
+     *     the exception of "jakarta.xml.ws." and "javax.xml.rpc"
+     * @param jClass
+     * @return
+     */
+    public boolean isBuiltInJavaType(JavaClass jClass) {
+        String rawName = jClass.getRawName();
+        if(null == rawName) {
+            return true;
+        }
+        return (getXMLToJavaTypeMap().containsKey(rawName) || rawName.startsWith(JAVA_PKG) || ((rawName.startsWith(JAVAX_PKG) || rawName.startsWith(JAKARTA_PKG)) && !(
+                rawName.startsWith(JAVAX_WS_PKG) ||
+                rawName.startsWith(JAVAX_RPC_PKG) ||
+                rawName.startsWith(JAKARTA_WS_PKG) ||
+                rawName.startsWith(JAKARTA_RPC_PKG)
+        )));
+    }
+
+    public void setClassLoader(ClassLoader loader) {
+        this.loader = loader;
+    }
+
+    public void setJavaModel(JavaModel model) {
+        jModel = model;
+    }
+    public ClassLoader getClassLoader() {
+        return loader;
+    }
+
+    public Class getClassForJavaClass(JavaClass javaClass){
+        String javaClassName = javaClass.getRawName();
+        if (javaClass.isPrimitive() || javaClass.isArray() && javaClass.getComponentType().isPrimitive()){
+            if (CoreClassConstants.APBYTE.getCanonicalName().equals(javaClassName)){
+                return Byte[].class;
+            }
+            if (CoreClassConstants.PBYTE.getCanonicalName().equals(javaClassName)){
+                return Byte.class;
+            }
+            if (CoreClassConstants.PBOOLEAN.getCanonicalName().equals(javaClassName)){
+                return Boolean.class;
+            }
+            if (CoreClassConstants.PSHORT.getCanonicalName().equals(javaClassName)){
+                return Short.class;
+            }
+            if (CoreClassConstants.PFLOAT.getCanonicalName().equals(javaClassName)){
+                return Float.class;
+            }
+            if (CoreClassConstants.PCHAR.getCanonicalName().equals(javaClassName)){
+                return Character.class;
+            }
+            if (CoreClassConstants.PDOUBLE.getCanonicalName().equals(javaClassName)){
+                return Double.class;
+            }
+            if (CoreClassConstants.PINT.getCanonicalName().equals(javaClassName)){
+                return Integer.class;
+            }
+            if (CoreClassConstants.PLONG.getCanonicalName().equals(javaClassName)){
+                return Long.class;
+            }
+            return null;
+        }
+        return org.eclipse.persistence.internal.helper.Helper.getClassFromClasseName(javaClass.getQualifiedName(), loader);
+    }
+
+    /**
+     * Convenience method to determine if a class exists in a given ArrayList.
+     */
+    public boolean classExistsInArray(JavaClass theClass, List<JavaClass> existingClasses) {
+        for (JavaClass jClass : existingClasses) {
+            if (areClassesEqual(jClass, theClass)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Convenience method to determine if two JavaClass instances are equal.
+     *
+     * @param classA
+     * @param classB
+     * @return
+     */
+    private boolean areClassesEqual(JavaClass classA, JavaClass classB) {
+        if (classA == classB) {
+            return true;
+        }
+        if (!(classA.getQualifiedName().equals(classB.getQualifiedName()))) {
+            return false;
+        }
+
+        Collection classAargs = classA.getActualTypeArguments();
+        Collection classBargs = classB.getActualTypeArguments();
+        if (classAargs != null) {
+            if (classBargs == null) {
+                return false;
+            }
+            if (classAargs.size() != classBargs.size()) {
+                return false;
+            }
+
+            Iterator classAargsIter = classAargs.iterator();
+            Iterator classBargsIter = classBargs.iterator();
+
+            while(classAargsIter.hasNext()){
+                JavaClass nestedClassA = (JavaClass) classAargsIter.next();
+                JavaClass nestedClassB = (JavaClass) classBargsIter.next();
+                if (!areClassesEqual(nestedClassA, nestedClassB)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        if (classBargs == null) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * Prepends a package name to a given java type name, if it is not already present.
+     *
+     * @param javaTypeName Java type name that may/may not contain 'packageName'
+     * @param packageName package name to prepend to javaTypeName if not already
+     * @return fully qualified java type name
+     */
+    public static String getQualifiedJavaTypeName(String javaTypeName, String packageName) {
+        // prepend the package name if not already present
+        if (packageName != null && packageName.length() > 0 && !packageName.equals(DEFAULT) && !javaTypeName.contains(PKG_SEPARATOR)) {
+            return packageName + PKG_SEPARATOR + javaTypeName;
+        }
+        return javaTypeName;
+    }
+
+    public boolean isCollectionType(JavaClass type) {
+     if (collectionClass.isAssignableFrom(type)
+             || listClass.isAssignableFrom(type)
+             || setClass.isAssignableFrom(type)) {
+             return true;
+         }
+         return false;
+    }
+
+    public boolean isMapType(JavaClass type) {
+        return mapClass.isAssignableFrom(type);
+    }
+
+    public boolean isFacets() {
+        return facets;
+    }
+
+    public void setFacets(boolean facets) {
+        this.facets = facets;
+    }
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/eclipse/persistence/jaxb/javamodel/reflection/JavaClassImpl.java b/jakarta/src/patch/java/org/eclipse/persistence/jaxb/javamodel/reflection/JavaClassImpl.java
new file mode 100644
index 0000000..a1c74ba
--- /dev/null
+++ b/jakarta/src/patch/java/org/eclipse/persistence/jaxb/javamodel/reflection/JavaClassImpl.java
@@ -0,0 +1,576 @@
+/*
+ * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+// Contributors:
+//     Oracle - initial API and implementation from Oracle TopLink
+package org.eclipse.persistence.jaxb.javamodel.reflection;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.persistence.exceptions.JAXBException;
+import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
+import org.eclipse.persistence.jaxb.javamodel.JavaAnnotation;
+import org.eclipse.persistence.jaxb.javamodel.JavaClass;
+import org.eclipse.persistence.jaxb.javamodel.JavaClassInstanceOf;
+import org.eclipse.persistence.jaxb.javamodel.JavaConstructor;
+import org.eclipse.persistence.jaxb.javamodel.JavaField;
+import org.eclipse.persistence.jaxb.javamodel.JavaMethod;
+import org.eclipse.persistence.jaxb.javamodel.JavaPackage;
+
+/**
+ * INTERNAL:
+ * <p><b>Purpose:</b>A wrapper class for a JDK Class.  This implementation
+ * of the EclipseLink JAXB 2.X Java model simply makes reflective calls on the
+ * underlying JDK object.
+ *
+ * <p><b>Responsibilities:</b>
+ * <ul>
+ * <li>Provide access to the underlying JDK Class' name, package,
+ * method/field names and parameters, annotations, etc.</li>
+ * </ul>
+ *
+ * @since Oracle TopLink 11.1.1.0.0
+ * @see org.eclipse.persistence.jaxb.javamodel.JavaClass
+ * @see java.lang.Class
+ */
+public class JavaClassImpl implements JavaClass {
+
+    protected ParameterizedType jType;
+    protected Class jClass;
+    protected JavaModelImpl javaModelImpl;
+    protected boolean isMetadataComplete;
+    protected JavaClass superClassOverride;
+
+    protected static final String XML_REGISTRY_CLASS_NAME = "jakarta.xml.bind.annotation.XmlRegistry";
+
+    public JavaClassImpl(Class javaClass, JavaModelImpl javaModelImpl) {
+        this.jClass = javaClass;
+        this.javaModelImpl = javaModelImpl;
+        isMetadataComplete = false;
+    }
+
+    public JavaClassImpl(ParameterizedType javaType, Class javaClass, JavaModelImpl javaModelImpl) {
+        this.jType = javaType;
+        this.jClass = javaClass;
+        this.javaModelImpl = javaModelImpl;
+        isMetadataComplete = false;
+    }
+
+    public void setJavaModelImpl(JavaModelImpl javaModel) {
+        this.javaModelImpl = javaModel;
+    }
+    public Collection getActualTypeArguments() {
+        ArrayList<JavaClass> argCollection = new ArrayList<JavaClass>();
+        if (jType != null) {
+            Type[] params = jType.getActualTypeArguments();
+            for (Type type : params) {
+                if (type instanceof ParameterizedType) {
+                    ParameterizedType pt = (ParameterizedType) type;
+                    argCollection.add(new JavaClassImpl(pt, (Class) pt.getRawType(), javaModelImpl));
+                } else if(type instanceof WildcardType){
+                    Type[] upperTypes = ((WildcardType)type).getUpperBounds();
+                    if(upperTypes.length >0){
+                        Type upperType = upperTypes[0];
+                        if(upperType instanceof Class){
+                            argCollection.add(javaModelImpl.getClass((Class) upperType));
+                        }
+                    }
+                } else if (type instanceof Class) {
+                    argCollection.add(javaModelImpl.getClass((Class) type));
+                } else if(type instanceof GenericArrayType) {
+                    Class genericTypeClass = (Class)((GenericArrayType)type).getGenericComponentType();
+                    genericTypeClass = java.lang.reflect.Array.newInstance(genericTypeClass, 0).getClass();
+                    argCollection.add(javaModelImpl.getClass(genericTypeClass));
+                } else if(type instanceof TypeVariable) {
+                    Type[] boundTypes = ((TypeVariable) type).getBounds();
+                    if(boundTypes.length > 0) {
+                        Type boundType = boundTypes[0];
+                        if(boundType instanceof Class) {
+                            argCollection.add(javaModelImpl.getClass((Class) boundType));
+                        }
+                    }
+                }
+            }
+        }
+        return argCollection;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    /**
+     * Assumes JavaType is a JavaClassImpl instance
+     */
+    public JavaAnnotation getAnnotation(JavaClass arg0) {
+        // the only annotation we will return if isMetadataComplete == true is XmlRegistry
+        if (arg0 != null && (!isMetadataComplete || arg0.getQualifiedName().equals(XML_REGISTRY_CLASS_NAME))) {
+            Class annotationClass = ((JavaClassImpl) arg0).getJavaClass();
+            if (javaModelImpl.getAnnotationHelper().isAnnotationPresent(getAnnotatedElement(), annotationClass)) {
+                return new JavaAnnotationImpl(this.javaModelImpl.getAnnotationHelper().getAnnotation(getAnnotatedElement(), annotationClass));
+            }
+        }
+        return null;
+    }
+
+    public Collection<JavaAnnotation> getAnnotations() {
+        List<JavaAnnotation> annotationCollection = new ArrayList<JavaAnnotation>();
+        if (!isMetadataComplete) {
+            Annotation[] annotations = javaModelImpl.getAnnotationHelper().getAnnotations(getAnnotatedElement());
+            for (Annotation annotation : annotations) {
+                annotationCollection.add(new JavaAnnotationImpl(annotation));
+            }
+        }
+        return annotationCollection;
+    }
+
+    public Collection<JavaClass> getDeclaredClasses() {
+        List<JavaClass> classCollection = new ArrayList<JavaClass>();
+        Class[] classes = jClass.getDeclaredClasses();
+        for (Class javaClass : classes) {
+            classCollection.add(javaModelImpl.getClass(javaClass));
+        }
+        return classCollection;
+    }
+
+    public JavaField getDeclaredField(String arg0) {
+        try {
+            return getJavaField(jClass.getDeclaredField(arg0));
+        } catch (NoSuchFieldException nsfe) {
+            return null;
+        }
+    }
+
+    public Collection<JavaField> getDeclaredFields() {
+        List<JavaField> fieldCollection = new ArrayList<JavaField>();
+        Field[] fields = PrivilegedAccessHelper.getDeclaredFields(jClass);
+
+        for (Field field : fields) {
+            field.setAccessible(true);
+            fieldCollection.add(getJavaField(field));
+        }
+        return fieldCollection;
+    }
+
+    /**
+     * Assumes JavaType[] contains JavaClassImpl instances
+     */
+    public JavaMethod getDeclaredMethod(String arg0, JavaClass[] arg1) {
+        if (arg1 == null) {
+            arg1 = new JavaClass[0];
+        }
+        Class[] params = new Class[arg1.length];
+        for (int i=0; i<arg1.length; i++) {
+            JavaClass jType = arg1[i];
+            if (jType != null) {
+                params[i] = ((JavaClassImpl) jType).getJavaClass();
+            }
+        }
+        try {
+            return getJavaMethod(jClass.getDeclaredMethod(arg0, params));
+        } catch (NoSuchMethodException nsme) {
+            return null;
+        }
+    }
+
+    public Collection getDeclaredMethods() {
+        ArrayList<JavaMethod> methodCollection = new ArrayList<JavaMethod>();
+        Method[] methods = jClass.getDeclaredMethods();
+        for (Method method : methods) {
+            methodCollection.add(getJavaMethod(method));
+        }
+        return methodCollection;
+    }
+
+    public JavaConstructor getConstructor(JavaClass[] paramTypes) {
+        if (paramTypes == null) {
+            paramTypes = new JavaClass[0];
+        }
+        Class[] params = new Class[paramTypes.length];
+        for (int i=0; i<paramTypes.length; i++) {
+            JavaClass jType = paramTypes[i];
+            if (jType != null) {
+                params[i] = ((JavaClassImpl) jType).getJavaClass();
+            }
+        }
+        try {
+            Constructor constructor = PrivilegedAccessHelper.getConstructorFor(jClass, params, true);
+            return new JavaConstructorImpl(constructor, javaModelImpl);
+        } catch (NoSuchMethodException nsme) {
+            return null;
+        }
+    }
+
+    public JavaConstructor getDeclaredConstructor(JavaClass[] paramTypes) {
+        if (paramTypes == null) {
+            paramTypes = new JavaClass[0];
+        }
+        Class[] params = new Class[paramTypes.length];
+        for (int i=0; i<paramTypes.length; i++) {
+            JavaClass jType = paramTypes[i];
+            if (jType != null) {
+                params[i] = ((JavaClassImpl) jType).getJavaClass();
+            }
+        }
+        try {
+            return new JavaConstructorImpl(PrivilegedAccessHelper.getDeclaredConstructorFor(this.jClass, params, true), javaModelImpl);
+        } catch (NoSuchMethodException nsme) {
+            return null;
+        }
+    }
+
+    public Collection getConstructors() {
+        Constructor[] constructors = this.jClass.getConstructors();
+        ArrayList<JavaConstructor> constructorCollection = new ArrayList(constructors.length);
+        for(Constructor next:constructors) {
+            constructorCollection.add(new JavaConstructorImpl(next, javaModelImpl));
+        }
+        return constructorCollection;
+    }
+
+    public Collection getDeclaredConstructors() {
+        Constructor[] constructors = this.jClass.getDeclaredConstructors();
+        ArrayList<JavaConstructor> constructorCollection = new ArrayList(constructors.length);
+        for(Constructor next:constructors) {
+            constructorCollection.add(new JavaConstructorImpl(next, javaModelImpl));
+        }
+        return constructorCollection;
+    }
+
+    public JavaField getField(String arg0) {
+        try {
+            Field field = PrivilegedAccessHelper.getField(jClass, arg0, true);
+            return getJavaField(field);
+        } catch (NoSuchFieldException nsfe) {
+            return null;
+        }
+    }
+
+    public Collection getFields() {
+        ArrayList<JavaField> fieldCollection = new ArrayList<JavaField>();
+        Field[] fields = PrivilegedAccessHelper.getFields(jClass);
+        for (Field field : fields) {
+            fieldCollection.add(getJavaField(field));
+        }
+        return fieldCollection;
+    }
+
+    public Class getJavaClass() {
+        return jClass;
+    }
+
+    /**
+     * Assumes JavaType[] contains JavaClassImpl instances
+     */
+    public JavaMethod getMethod(String arg0, JavaClass[] arg1) {
+        if (arg1 == null) {
+            arg1 = new JavaClass[0];
+        }
+        Class[] params = new Class[arg1.length];
+        for (int i=0; i<arg1.length; i++) {
+            JavaClass jType = arg1[i];
+            if (jType != null) {
+                params[i] = ((JavaClassImpl) jType).getJavaClass();
+            }
+        }
+        try {
+            Method method = PrivilegedAccessHelper.getMethod(jClass, arg0, params, true);
+            return getJavaMethod(method);
+        } catch (NoSuchMethodException nsme) {
+            return null;
+        }
+    }
+
+    public Collection getMethods() {
+        ArrayList<JavaMethod> methodCollection = new ArrayList<JavaMethod>();
+        Method[] methods = PrivilegedAccessHelper.getMethods(jClass);
+        for (Method method : methods) {
+            methodCollection.add(getJavaMethod(method));
+        }
+        return methodCollection;
+    }
+
+    public String getName() {
+        return jClass.getName();
+    }
+
+    public JavaPackage getPackage() {
+        return new JavaPackageImpl(jClass.getPackage(), javaModelImpl, isMetadataComplete);
+    }
+
+    public String getPackageName() {
+        if(jClass.getPackage() != null){
+            return jClass.getPackage().getName();
+        }else{
+            Class nonInnerClass = jClass;
+            Class enclosingClass = jClass.getEnclosingClass();
+            while(enclosingClass != null){
+                nonInnerClass = enclosingClass;
+                enclosingClass = nonInnerClass.getEnclosingClass();
+            }
+            String className = nonInnerClass.getCanonicalName();
+            if(className !=null){
+                int index = className.lastIndexOf('.');
+                if(index > -1){
+                    return className.substring(0, index);
+                }
+            }
+        }
+        return null;
+    }
+
+    public String getQualifiedName() {
+        return jClass.getName();
+    }
+
+    public String getRawName() {
+        return jClass.getCanonicalName();
+    }
+
+    public JavaClass getSuperclass() {
+        if(this.superClassOverride != null) {
+            return this.superClassOverride;
+        }
+        if(jClass.isInterface()) {
+            Class[] superInterfaces = jClass.getInterfaces();
+            if(superInterfaces != null) {
+                if(superInterfaces.length == 1) {
+                    return javaModelImpl.getClass(superInterfaces[0]);
+                } else {
+                    Class parent = null;
+                    for(Class next:superInterfaces) {
+                        if(!(next.getName().startsWith("java.")
+                                || next.getName().startsWith("javax.")
+                                || next.getName().startsWith("jakarta."))) {
+                            if(parent == null) {
+                                parent = next;
+                            } else {
+                                throw JAXBException.invalidInterface(jClass.getName());
+                            }
+                        }
+                    }
+                    return javaModelImpl.getClass(parent);
+                }
+            }
+        }
+        return javaModelImpl.getClass(jClass.getSuperclass());
+    }
+
+    @Override
+    public Type[] getGenericInterfaces() {
+        return jClass.getGenericInterfaces();
+    }
+
+    public Type getGenericSuperclass() {
+        return jClass.getGenericSuperclass();
+    }
+
+    public boolean hasActualTypeArguments() {
+        return getActualTypeArguments().size() > 0;
+    }
+
+    public JavaField getJavaField(Field field) {
+        return new JavaFieldImpl(field, javaModelImpl, isMetadataComplete);
+    }
+
+    public JavaMethod getJavaMethod(Method method) {
+        return new JavaMethodImpl(method, javaModelImpl, isMetadataComplete);
+    }
+
+    public JavaClass getOwningClass() {
+        return javaModelImpl.getClass(jClass.getEnclosingClass());
+    }
+
+    public boolean isAnnotation() {
+        return jClass.isAnnotation();
+    }
+
+    public boolean isArray() {
+        return jClass.isArray();
+    }
+
+    public AnnotatedElement getAnnotatedElement() {
+        return jClass;
+    }
+
+    public boolean isAssignableFrom(JavaClass arg0) {
+        if (!(arg0 instanceof JavaClassImpl)) {
+            return false;
+        }
+        if(hasCustomSuperClass(arg0)) {
+            return this.customIsAssignableFrom(arg0);
+        }
+        return jClass.isAssignableFrom(((JavaClassImpl) arg0).getJavaClass());
+    }
+
+    private boolean customIsAssignableFrom(JavaClass arg0) {
+        JavaClassImpl jClass = (JavaClassImpl)arg0;
+        Class cls = jClass.getJavaClass();
+
+        if(cls == this.jClass) {
+            return true;
+        }
+        Class[] interfaces = cls.getInterfaces();
+        for(Class nextInterface:interfaces) {
+            if(nextInterface == this.jClass) {
+                return true;
+            }
+            if(customIsAssignableFrom(javaModelImpl.getClass(nextInterface))) {
+                return true;
+            }
+        }
+
+        if(!(jClass.isInterface())) {
+            JavaClassImpl superJavaClass = (JavaClassImpl)jClass.getSuperclass();
+            if(superJavaClass.getName().equals("java.lang.Object")) {
+                return this.jClass == superJavaClass.getJavaClass();
+            }
+            return customIsAssignableFrom(superJavaClass);
+        }
+        return false;
+    }
+
+    private boolean hasCustomSuperClass(JavaClass arg0) {
+        if(arg0 == null) {
+            return false;
+        }
+        if(!this.javaModelImpl.hasXmlBindings()) {
+            return false;
+        }
+        if(!(arg0.getClass() == this.getClass())) {
+            return false;
+        }
+        if(arg0.getName().equals("java.lang.Object")) {
+            return false;
+        }
+        JavaClassImpl jClass = (JavaClassImpl)arg0;
+        return jClass.getSuperClassOverride() != null || hasCustomSuperClass(jClass.getSuperclass());
+    }
+
+    public boolean isEnum() {
+        return jClass.isEnum();
+    }
+
+    public boolean isInterface() {
+        return jClass.isInterface();
+    }
+
+    public boolean isMemberClass() {
+        return jClass.isMemberClass();
+    }
+
+    public boolean isPrimitive() {
+        return jClass.isPrimitive();
+    }
+
+    public boolean isAbstract() {
+        return Modifier.isAbstract(getModifiers());
+    }
+
+    public boolean isPrivate() {
+        return Modifier.isPrivate(getModifiers());
+    }
+
+    public boolean isProtected() {
+        return Modifier.isProtected(getModifiers());
+    }
+
+    public boolean isPublic() {
+        return Modifier.isPublic(getModifiers());
+    }
+
+    public boolean isStatic() {
+        return Modifier.isStatic(getModifiers());
+    }
+
+    public int getModifiers() {
+        return jClass.getModifiers();
+    }
+
+    public boolean isFinal() {
+        return Modifier.isFinal(getModifiers());
+    }
+
+    public boolean isSynthetic() {
+        return jClass.isSynthetic();
+    }
+
+    @Override
+    public JavaClassInstanceOf instanceOf() {
+        return JavaClassInstanceOf.JAVA_CLASS_IMPL;
+    }
+
+    public JavaClass getComponentType() {
+        if(!isArray()) {
+            return null;
+        }
+        return javaModelImpl.getClass(this.jClass.getComponentType());
+    }
+
+    public JavaClass getSuperClassOverride() {
+        return superClassOverride;
+    }
+
+    public void setSuperClassOverride(JavaClass superClassOverride) {
+        this.superClassOverride = superClassOverride;
+    }
+    /**
+     * Set the indicator for XML metadata complete - if true,
+     * annotations will be ignored.
+     *
+     * @param isMetadataComplete
+     */
+    void setIsMetadataComplete(Boolean isMetadataComplete) {
+       if(isMetadataComplete != null){
+            this.isMetadataComplete = isMetadataComplete;
+        }
+    }
+
+    public JavaAnnotation getDeclaredAnnotation(JavaClass arg0) {
+        // the only annotation we will return if isMetadataComplete == true is XmlRegistry
+        if (arg0 != null && (!isMetadataComplete || arg0.getQualifiedName().equals(XML_REGISTRY_CLASS_NAME))) {
+            Class annotationClass = ((JavaClassImpl) arg0).getJavaClass();
+            Annotation[] annotations = javaModelImpl.getAnnotationHelper().getDeclaredAnnotations(getAnnotatedElement());
+            for (Annotation annotation : annotations) {
+                if (annotation.annotationType().equals(annotationClass)) {
+                    return new JavaAnnotationImpl(annotation);
+                }
+            }
+        }
+        return null;
+    }
+
+    public Collection getDeclaredAnnotations() {
+        List<JavaAnnotation> annotationCollection = new ArrayList<JavaAnnotation>();
+        if (!isMetadataComplete) {
+            Annotation[] annotations = javaModelImpl.getAnnotationHelper().getDeclaredAnnotations(getAnnotatedElement());
+            for (Annotation annotation : annotations) {
+                annotationCollection.add(new JavaAnnotationImpl(annotation));
+            }
+        }
+        return annotationCollection;
+    }
+
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/eclipse/persistence/jaxb/rs/MOXyJsonProvider.java b/jakarta/src/patch/java/org/eclipse/persistence/jaxb/rs/MOXyJsonProvider.java
new file mode 100644
index 0000000..7bce14a
--- /dev/null
+++ b/jakarta/src/patch/java/org/eclipse/persistence/jaxb/rs/MOXyJsonProvider.java
@@ -0,0 +1,986 @@
+/*
+ * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+// Contributors:
+//     Blaise Doughan - 2.4 - initial implementation
+package org.eclipse.persistence.jaxb.rs;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import jakarta.activation.DataSource;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.Response.ResponseBuilder;
+import jakarta.ws.rs.core.Response.Status;
+import jakarta.ws.rs.core.StreamingOutput;
+import jakarta.ws.rs.ext.ContextResolver;
+import jakarta.ws.rs.ext.MessageBodyReader;
+import jakarta.ws.rs.ext.MessageBodyWriter;
+import jakarta.ws.rs.ext.Provider;
+import jakarta.ws.rs.ext.Providers;
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBElement;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.JAXBIntrospector;
+import jakarta.xml.bind.Marshaller;
+import jakarta.xml.bind.UnmarshalException;
+import jakarta.xml.bind.Unmarshaller;
+import javax.xml.namespace.QName;
+import javax.xml.transform.stream.StreamSource;
+
+import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
+import org.eclipse.persistence.internal.helper.Helper;
+import org.eclipse.persistence.internal.oxm.Constants;
+import org.eclipse.persistence.internal.queries.CollectionContainerPolicy;
+import org.eclipse.persistence.internal.queries.ContainerPolicy;
+import org.eclipse.persistence.jaxb.JAXBContextFactory;
+import org.eclipse.persistence.jaxb.MarshallerProperties;
+import org.eclipse.persistence.jaxb.UnmarshallerProperties;
+import org.eclipse.persistence.logging.AbstractSessionLog;
+import org.eclipse.persistence.logging.SessionLog;
+import org.eclipse.persistence.oxm.JSONWithPadding;
+
+/**
+ * <p>This is an implementation of <i>MessageBodyReader</i>/<i>MessageBodyWriter
+ * </i> that can be used to enable EclipseLink JAXB (MOXy) as the JSON
+ * provider.</p>
+ * <p>
+ * <b>Supported Media Type Patterns</b>
+ * <ul>
+ * <li>*&#47;json (i.e. application/json and text/json)</li>
+ * <li>*&#47;*+json</li>
+ * </ul>
+ *
+ * <p>Below are some different usage options.</p>
+ *
+ * <b>Option #1 - <i>MOXyJsonProvider</i> Default Behavior</b>
+ * <p>You can use the <i>Application</i> class to specify that
+ * <i>MOXyJsonProvider</i> should be used with your JAX-RS application.</p>
+ * <pre>
+ * package org.example;
+
+ * import java.util.*;
+ * import javax.ws.rs.core.Application;
+ * import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
+ *
+ * public class ExampleApplication  extends Application {
+ *
+ *     &#64;Override
+ *     public Set&lt;Class&lt;?&gt;&gt; getClasses() {
+ *         HashSet&lt;Class&lt;?&gt;&gt; set = new HashSet&lt;Class&lt;?&gt;&gt;(2);
+ *         set.add(MOXyJsonProvider.class);
+ *         set.add(ExampleService.class);
+ *         return set;
+ *     }
+ *
+ * }
+ * </pre>
+ *
+ * <b>Option #2 - Customize <i>MOXyJsonProvider</i></b>
+ * <p>You can use the <i>Application</i> class to specify a configured instance
+ * of <i>MOXyJsonProvider</i> should be used with your JAX-RS application.</p>
+ * <pre>
+ * package org.example;
+ *
+ * import java.util.*;
+ * import javax.ws.rs.core.Application;
+ * import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
+ *
+ * public class CustomerApplication  extends Application {
+ *
+ *     &#64;Override
+ *     public Set&lt;Class&lt;?&gt;&gt; getClasses() {
+ *         HashSet&lt;Class&lt;?&gt;&gt; set = new HashSet&lt;Class&lt;?&gt;&gt;(1);
+ *         set.add(ExampleService.class);
+ *         return set;
+ *     }
+
+ *     &#64;Override
+ *     public Set&lt;Object&gt; getSingletons() {
+ *         moxyJsonProvider moxyJsonProvider = new MOXyJsonProvider();
+ *         moxyJsonProvider.setFormattedOutput(true);
+ *         moxyJsonProvider.setIncludeRoot(true);
+ *
+ *         HashSet&lt;Object&gt; set = new HashSet&lt;Object&gt;(2);
+ *         set.add(moxyJsonProvider);
+ *         return set;
+ *     }
+ *
+ * }
+ * </pre>
+ * <b>Option #3 - Extend MOXyJsonProvider</b>
+ * <p>You can use MOXyJsonProvider for creating your own
+ * <i>MessageBodyReader</i>/<i>MessageBodyWriter</i>.</p>
+ * <pre>
+ * package org.example;
+ *
+ * import java.lang.annotation.Annotation;
+ * import java.lang.reflect.Type;
+ *
+ * import javax.ws.rs.*;
+ * import javax.ws.rs.core.*;
+ * import javax.ws.rs.ext.Provider;
+ * import javax.xml.bind.*;
+ *
+ * import org.eclipse.persistence.jaxb.MarshallerProperties;
+ * import org.eclipse.persistence.jaxb.rs.MOXyJsonProvider;
+ *
+ * &#64;Provider
+ * &#64;Produces(MediaType.APPLICATION_JSON)
+ * &#64;Consumes(MediaType.APPLICATION_JSON)
+ * public class CustomerJSONProvider extends MOXyJsonProvider {
+
+ *     &#64;Override
+ *     public boolean isReadable(Class&lt;?&gt; type, Type genericType,
+ *             Annotation[] annotations, MediaType mediaType) {
+ *         return getDomainClass(genericType) == Customer.class;
+ *     }
+ *
+ *     &#64;Override
+ *     public boolean isWriteable(Class&lt;?&gt; type, Type genericType,
+ *             Annotation[] annotations, MediaType mediaType) {
+ *         return isReadable(type, genericType, annotations, mediaType);
+ *     }
+ *
+ *     &#64;Override
+ *     protected void preReadFrom(Class&lt;Object&gt; type, Type genericType,
+ *             Annotation[] annotations, MediaType mediaType,
+ *             MultivaluedMap&lt;String, String&gt; httpHeaders,
+ *             Unmarshaller unmarshaller) throws JAXBException {
+ *         unmarshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, "$");
+ *     }
+ *
+ *     &#64;Override
+ *     protected void preWriteTo(Object object, Class&lt;?&gt; type, Type genericType,
+ *             Annotation[] annotations, MediaType mediaType,
+ *             MultivaluedMap&lt;String, Object&gt; httpHeaders, Marshaller marshaller)
+ *             throws JAXBException {
+ *         marshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, "$");
+ *     }
+ *
+ * }
+ * </pre>
+ * @since 2.4
+ */
+@Produces({MediaType.APPLICATION_JSON, MediaType.WILDCARD, "application/x-javascript"})
+@Consumes({MediaType.APPLICATION_JSON, MediaType.WILDCARD})
+public class MOXyJsonProvider implements MessageBodyReader<Object>, MessageBodyWriter<Object>{
+
+    private static final String APPLICATION_XJAVASCRIPT = "application/x-javascript";
+    private static final String CHARSET = "charset";
+    private static final QName EMPTY_STRING_QNAME = new QName("");
+    private static final String JSON = "json";
+    private static final String PLUS_JSON = "+json";
+
+    @Context
+    protected Providers providers;
+
+    private String attributePrefix = null;
+    private Map<Set<Class<?>>, JAXBContext> contextCache = new HashMap<Set<Class<?>>, JAXBContext>();
+    private boolean formattedOutput = false;
+    private boolean includeRoot = false;
+    private boolean marshalEmptyCollections = true;
+    private Map<String, String> namespacePrefixMapper;
+    private char namespaceSeperator = Constants.DOT;
+    private String valueWrapper;
+    private boolean wrapperAsArrayName = false;
+
+    /**
+     * The value that will be prepended to all keys that are mapped to an XML
+     * attribute.  By default there is no attribute prefix.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_ATTRIBUTE_PREFIX
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_ATTRIBUTE_PREFIX
+     */
+    public String getAttributePrefix() {
+        return attributePrefix;
+    }
+
+    /**
+     * A convenience method to get the domain class (i.e. <i>Customer</i> or <i>Foo, Bar</i>) from
+     * the parameter/return type (i.e. <i>Customer</i>, <i>List&lt;Customer&gt;</i>,
+     * <i>JAXBElement&lt;Customer&gt;</i>, <i>JAXBElement&lt;? extends Customer&gt;</i>,
+     * <i>List&lt;JAXBElement&lt;Customer&gt;&gt;</i>, or
+     * <i>List&lt;JAXBElement&lt;? extends Customer&gt;&gt;</i>
+     * <i>List&lt;Foo&lt;Bar&gt;&gt;</i>).
+     * @param genericType - The parameter/return type of the JAX-RS operation.
+     * @return The corresponding domain classes.
+     */
+    protected Set<Class<?>> getDomainClasses(Type genericType) {
+        if(null == genericType) {
+            return asSet(Object.class);
+        }
+        if(genericType instanceof Class && genericType != JAXBElement.class) {
+            Class<?> clazz = (Class<?>) genericType;
+            if(clazz.isArray()) {
+                return getDomainClasses(clazz.getComponentType());
+            }
+            return asSet(clazz);
+        } else if(genericType instanceof ParameterizedType) {
+            Set<Class<?>> result = new LinkedHashSet<Class<?>>();
+            result.add((Class<?>)((ParameterizedType) genericType).getRawType());
+            Type[] types = ((ParameterizedType) genericType).getActualTypeArguments();
+            if(types.length > 0){
+                for (Type upperType : types) {
+                    result.addAll(getDomainClasses(upperType));
+                }
+            }
+            return result;
+        } else if (genericType instanceof GenericArrayType) {
+            GenericArrayType genericArrayType = (GenericArrayType) genericType;
+            return getDomainClasses(genericArrayType.getGenericComponentType());
+        } else if(genericType instanceof WildcardType) {
+            Set<Class<?>> result = new LinkedHashSet<Class<?>>();
+            Type[] upperTypes = ((WildcardType)genericType).getUpperBounds();
+            if(upperTypes.length > 0){
+                for (Type upperType : upperTypes) {
+                    result.addAll(getDomainClasses(upperType));
+                }
+            } else {
+                result.add(Object.class);
+            }
+            return result;
+        } else {
+            return asSet(Object.class);
+        }
+    }
+
+    private Set<Class<?>> asSet(Class<?> clazz) {
+        Set<Class<?>> result = new LinkedHashSet<>();
+        result.add(clazz);
+        return result;
+    }
+
+    /**
+     * Return the <i>JAXBContext</i> that corresponds to the domain class.  This
+     * method does the following:
+     * <ol>
+     * <li>If an EclipseLink JAXB (MOXy) <i>JAXBContext</i> is available from
+     * a <i>ContextResolver</i> then use it.</li>
+     * <li>If an existing <i>JAXBContext</i> was not found in step one, then
+     * create a new one on the domain class.</li>
+     * </ol>
+     * @param domainClasses - The domain classes we need a <i>JAXBContext</i> for.
+     * @param annotations - The annotations corresponding to domain object.
+     * @param mediaType - The media type for the HTTP entity.
+     * @param httpHeaders - HTTP headers associated with HTTP entity.
+     * @return
+     * @throws JAXBException
+     */
+    protected JAXBContext getJAXBContext(Set<Class<?>> domainClasses, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, ?> httpHeaders) throws JAXBException {
+
+        JAXBContext jaxbContext = contextCache.get(domainClasses);
+        if(null != jaxbContext) {
+            return jaxbContext;
+        }
+
+        synchronized (contextCache) {
+            jaxbContext = contextCache.get(domainClasses);
+            if(null != jaxbContext) {
+                return jaxbContext;
+            }
+
+            ContextResolver<JAXBContext> resolver = null;
+            if(null != providers) {
+                resolver = providers.getContextResolver(JAXBContext.class, mediaType);
+            }
+
+            if (null != resolver && domainClasses.size() == 1) {
+                jaxbContext = resolver.getContext(domainClasses.iterator().next());
+            }
+
+            if(null == jaxbContext) {
+                jaxbContext = JAXBContextFactory.createContext(domainClasses.toArray(new Class[0]), null);
+                contextCache.put(domainClasses, jaxbContext);
+                return jaxbContext;
+            } else if (jaxbContext instanceof org.eclipse.persistence.jaxb.JAXBContext) {
+                return jaxbContext;
+            } else {
+                jaxbContext = JAXBContextFactory.createContext(domainClasses.toArray(new Class[0]), null);
+                contextCache.put(domainClasses, jaxbContext);
+                return jaxbContext;
+            }
+        }
+    }
+
+    private JAXBContext getJAXBContext(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        if(null == genericType) {
+            genericType = type;
+        }
+
+        try {
+            Set<Class<?>> domainClasses = getDomainClasses(genericType);
+            return getJAXBContext(domainClasses, annotations, mediaType, null);
+        } catch(JAXBException e) {
+            AbstractSessionLog.getLog().logThrowable(SessionLog.WARNING, SessionLog.MOXY, e);
+            return null;
+        }
+    }
+
+    /**
+     * By default the JSON-binding will ignore namespace qualification. If this
+     * property is set the portion of the key before the namespace separator
+     * will be used to determine the namespace URI.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#NAMESPACE_PREFIX_MAPPER
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_NAMESPACE_PREFIX_MAPPER
+     */
+    public Map<String, String> getNamespacePrefixMapper() {
+        return namespacePrefixMapper;
+    }
+
+    /**
+     * This character (default is '.') separates the prefix from the key name.
+     * It is only used if namespace qualification has been enabled be setting a
+     * namespace prefix mapper.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_NAMESPACE_SEPARATOR
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_NAMESPACE_SEPARATOR
+     */
+    public char getNamespaceSeparator() {
+        return this.namespaceSeperator;
+    }
+
+    /*
+     * @return -1 since the size of the JSON message is not known.
+     * @see javax.ws.rs.ext.MessageBodyWriter#getSize(java.lang.Object, java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType)
+     */
+    @Override
+    public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        return -1;
+    }
+
+    /**
+     * The key that will correspond to the property mapped with @XmlValue.  This
+     * key will only be used if there are other mapped properties.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_VALUE_WRAPPER
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_VALUE_WRAPPER
+     */
+    public String getValueWrapper() {
+        return valueWrapper;
+    }
+
+    /**
+     * @return true if the JSON output should be formatted (default is false).
+     */
+    public boolean isFormattedOutput() {
+        return formattedOutput;
+    }
+
+    /**
+     * @return true if the root node is included in the JSON message (default is
+     * false).
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_INCLUDE_ROOT
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_INCLUDE_ROOT
+     */
+    public boolean isIncludeRoot() {
+        return includeRoot;
+    }
+
+    /**
+     * If true empty collections will be marshalled as empty arrays, else the
+     * collection will not be marshalled to JSON (default is true).
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_MARSHAL_EMPTY_COLLECTIONS
+     */
+    public boolean isMarshalEmptyCollections() {
+        return marshalEmptyCollections;
+    }
+
+    /**
+     * @return true indicating that <i>MOXyJsonProvider</i> will
+     * be used for the JSON binding if the media type is of the following
+     * patterns *&#47;json or *&#47;*+json, and the type is not assignable from
+     * any of (or a Collection or JAXBElement of) the following:
+     * <ul>
+     * <li>byte[]</li>
+     * <li>java.io.File</li>
+     * <li>java.io.InputStream</li>
+     * <li>java.io.Reader</li>
+     * <li>java.lang.Object</li>
+     * <li>java.lang.String</li>
+     * <li>javax.activation.DataSource</li>
+     * </ul>
+     */
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        if(!supportsMediaType(mediaType)) {
+            return false;
+        } else if(CoreClassConstants.APBYTE == type || CoreClassConstants.STRING == type) {
+            return false;
+        } else if(Map.class.isAssignableFrom(type)) {
+            return false;
+        } else if(File.class.isAssignableFrom(type)) {
+            return false;
+        } else if(DataSource.class.isAssignableFrom(type)) {
+            return false;
+        } else if(InputStream.class.isAssignableFrom(type)) {
+            return false;
+        } else if(Reader.class.isAssignableFrom(type)) {
+            return false;
+        } else if(Object.class == type) {
+            return false;
+        } else if(type.isPrimitive()) {
+            return false;
+        } else if(type.isArray() && (type.getComponentType().isArray() || type.getComponentType().isPrimitive() || type.getComponentType().getPackage().getName().startsWith("java."))) {
+            return false;
+        } else if(JAXBElement.class.isAssignableFrom(type)) {
+            Set<Class<?>> domainClasses = getDomainClasses(genericType);
+            for (Class<?> domainClass : domainClasses) {
+                if (isReadable(domainClass, null, annotations, mediaType) || String.class == domainClass) {
+                    return true;
+                }
+            }
+            return false;
+        } else if(Collection.class.isAssignableFrom(type)) {
+            Set<Class<?>> domainClasses = getDomainClasses(genericType);
+            for (Class<?> domainClass : domainClasses) {
+                if (isReadable(domainClass, null, annotations, mediaType) || String.class == domainClass) {
+                    return true;
+                }
+            }
+            return false;
+        } else {
+            return null != getJAXBContext(type, genericType, annotations, mediaType);
+        }
+    }
+
+    /**
+     * If true the grouping element will be used as the JSON key.
+     *
+     * <p><b>Example</b></p>
+     * <p>Given the following class:</p>
+     * <pre>
+     * &#64;XmlAccessorType(XmlAccessType.FIELD)
+     * public class Customer {
+     *
+     *     &#64;XmlElementWrapper(name="phone-numbers")
+     *     &#64;XmlElement(name="phone-number")
+     *     private {@literal List<PhoneNumber>} phoneNumbers;
+     *
+     * }
+     * </pre>
+     * <p>If the property is set to false (the default) the JSON output will be:</p>
+     * <pre>
+     * {
+     *     "phone-numbers" : {
+     *         "phone-number" : [ {
+     *             ...
+     *         }, {
+     *             ...
+     *         }]
+     *     }
+     * }
+     * </pre>
+     * <p>And if the property is set to true, then the JSON output will be:</p>
+     * <pre>
+     * {
+     *     "phone-numbers" : [ {
+     *         ...
+     *     }, {
+     *         ...
+     *     }]
+     * }
+     * </pre>
+     * @since 2.4.2
+     * @see org.eclipse.persistence.jaxb.JAXBContextProperties#JSON_WRAPPER_AS_ARRAY_NAME
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_WRAPPER_AS_ARRAY_NAME
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_WRAPPER_AS_ARRAY_NAME
+     */
+    public boolean isWrapperAsArrayName() {
+        return wrapperAsArrayName;
+    }
+
+    /**
+     * @return true indicating that <i>MOXyJsonProvider</i> will
+     * be used for the JSON binding if the media type is of the following
+     * patterns *&#47;json or *&#47;*+json, and the type is not assignable from
+     * any of (or a Collection or JAXBElement of) the following:
+     * <ul>
+     * <li>byte[]</li>
+     * <li>java.io.File</li>
+     * <li>java.lang.Object</li>
+     * <li>java.lang.String</li>
+     * <li>jakarta.activation.DataSource</li>
+     * <li>jakarta.ws.rs.core.StreamingOutput</li>
+     * </ul>
+     */
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
+        if(type == JSONWithPadding.class && APPLICATION_XJAVASCRIPT.equals(mediaType.toString())) {
+            return true;
+        }
+        if(!supportsMediaType(mediaType)) {
+            return false;
+        } else if(CoreClassConstants.APBYTE == type || CoreClassConstants.STRING == type || type.isPrimitive()) {
+            return false;
+        } else if(Map.class.isAssignableFrom(type)) {
+            return false;
+        } else if(File.class.isAssignableFrom(type)) {
+            return false;
+        } else if(DataSource.class.isAssignableFrom(type)) {
+            return false;
+        } else if(StreamingOutput.class.isAssignableFrom(type)) {
+            return false;
+        } else if(Object.class == type) {
+            return false;
+        } else if(type.isPrimitive()) {
+            return false;
+        } else if(type.isArray() && (String.class.equals(type.getComponentType()) || type.getComponentType().isPrimitive() || Helper.isPrimitiveWrapper(type.getComponentType()))) {
+                return true;
+        } else if(type.isArray() && (type.getComponentType().isArray() || type.getComponentType().isPrimitive() || type.getComponentType().getPackage().getName().startsWith("java."))) {
+            return false;
+        } else if(JAXBElement.class.isAssignableFrom(type)) {
+            Set<Class<?>> domainClasses = getDomainClasses(genericType);
+
+            for (Class<?> domainClass : domainClasses) {
+                if (isWriteable(domainClass, null, annotations, mediaType) || domainClass == String.class) {
+                    return true;
+                }
+            }
+
+            return false;
+        } else if(Collection.class.isAssignableFrom(type)) {
+            Set<Class<?>> domainClasses = getDomainClasses(genericType);
+
+            //special case for List<JAXBElement<String>>
+            //this is quick fix, MOXyJsonProvider should be refactored as stated in issue #459541
+            if (domainClasses.size() == 3) {
+                Class[] domainArray = domainClasses.toArray(new Class[domainClasses.size()]);
+                if (JAXBElement.class.isAssignableFrom(domainArray[1]) && String.class == domainArray[2]) {
+                    return true;
+                }
+            }
+
+            for (Class<?> domainClass : domainClasses) {
+
+                if (String.class.equals(domainClass) || domainClass.isPrimitive() || Helper.isPrimitiveWrapper(domainClass)) {
+                    return true;
+                }
+
+                    String packageName = domainClass.getPackage().getName();
+                if(null == packageName || !packageName.startsWith("java.")) {
+                    if (isWriteable(domainClass, null, annotations, mediaType)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+         } else {
+             return null != getJAXBContext(type, genericType, annotations, mediaType);
+        }
+    }
+
+    /**
+     * Subclasses of <i>MOXyJsonProvider</i> can override this method to
+     * customize the instance of <i>Unmarshaller</i> that will be used to
+     * unmarshal the JSON message in the readFrom call.
+     * @param type - The Class to be unmarshalled (i.e. <i>Customer</i> or
+     * <i>List</i>)
+     * @param genericType - The type of object to be unmarshalled (i.e
+     * <i>Customer</i> or <i>List&lt;Customer&gt;</i>).
+     * @param annotations - The annotations corresponding to domain object.
+     * @param mediaType - The media type for the HTTP entity.
+     * @param httpHeaders - HTTP headers associated with HTTP entity.
+     * @param unmarshaller - The instance of <i>Unmarshaller</i> that will be
+     * used to unmarshal the JSON message.
+     * @throws JAXBException
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties
+     */
+    protected void preReadFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, Unmarshaller unmarshaller) throws JAXBException {
+    }
+
+    /**
+     * Subclasses of <i>MOXyJsonProvider</i> can override this method to
+     * customize the instance of <i>Marshaller</i> that will be used to marshal
+     * the domain objects to JSON in the writeTo call.
+     * @param object - The domain object that will be marshalled to JSON.
+     * @param type - The Class to be marshalled (i.e. <i>Customer</i> or
+     * <i>List</i>)
+     * @param genericType - The type of object to be marshalled (i.e
+     * <i>Customer</i> or <i>List&lt;Customer&gt;</i>).
+     * @param annotations - The annotations corresponding to domain object.
+     * @param mediaType - The media type for the HTTP entity.
+     * @param httpHeaders - HTTP headers associated with HTTP entity.
+     * @param marshaller - The instance of <i>Marshaller</i> that will be used
+     * to marshal the domain object to JSON.
+     * @throws JAXBException
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties
+     */
+    protected void preWriteTo(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, Marshaller marshaller) throws JAXBException {
+    }
+
+    /*
+     * @see javax.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.InputStream)
+     */
+    @Override
+    public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException {
+        try {
+            if(null == genericType) {
+                genericType = type;
+            }
+
+            Set<Class<?>> domainClasses = getDomainClasses(genericType);
+            JAXBContext jaxbContext = getJAXBContext(domainClasses, annotations, mediaType, httpHeaders);
+            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+            unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
+            unmarshaller.setProperty(UnmarshallerProperties.JSON_ATTRIBUTE_PREFIX, attributePrefix);
+            unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, includeRoot);
+            unmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_PREFIX_MAPPER, namespacePrefixMapper);
+            unmarshaller.setProperty(UnmarshallerProperties.JSON_NAMESPACE_SEPARATOR, namespaceSeperator);
+            if(null != valueWrapper) {
+                unmarshaller.setProperty(UnmarshallerProperties.JSON_VALUE_WRAPPER, valueWrapper);
+            }
+            unmarshaller.setProperty(UnmarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, wrapperAsArrayName);
+            preReadFrom(type, genericType, annotations, mediaType, httpHeaders, unmarshaller);
+
+            StreamSource jsonSource;
+            Map<String, String> mediaTypeParameters = null;
+            if(null != mediaType) {
+                mediaTypeParameters = mediaType.getParameters();
+            }
+            if(null != mediaTypeParameters && mediaTypeParameters.containsKey(CHARSET)) {
+                String charSet = mediaTypeParameters.get(CHARSET);
+                Reader entityReader = new InputStreamReader(entityStream, charSet);
+                jsonSource = new StreamSource(entityReader);
+            } else {
+                jsonSource = new StreamSource(entityStream);
+            }
+
+            Class<?> domainClass = getDomainClass(domainClasses);
+            JAXBElement<?> jaxbElement = unmarshaller.unmarshal(jsonSource, domainClass);
+            if(type.isAssignableFrom(JAXBElement.class)) {
+                return jaxbElement;
+            } else {
+                Object value = jaxbElement.getValue();
+                if(value instanceof ArrayList) {
+                    if(type.isArray()) {
+                        ArrayList<Object> arrayList = (ArrayList<Object>) value;
+                        int arrayListSize = arrayList.size();
+                        boolean wrapItemInJAXBElement = wrapItemInJAXBElement(genericType);
+                        Object array;
+                        if(wrapItemInJAXBElement) {
+                            array = Array.newInstance(JAXBElement.class, arrayListSize);
+                        } else {
+                            array = Array.newInstance(domainClass, arrayListSize);
+                        }
+                        for(int x=0; x<arrayListSize; x++) {
+                            Object element = handleJAXBElement(arrayList.get(x), domainClass, wrapItemInJAXBElement);
+                            Array.set(array, x, element);
+                        }
+                        return array;
+                    } else {
+                        ContainerPolicy containerPolicy;
+                        if(type.isAssignableFrom(List.class) || type.isAssignableFrom(ArrayList.class) || type.isAssignableFrom(Collection.class)) {
+                            containerPolicy = new CollectionContainerPolicy(ArrayList.class);
+                        } else if(type.isAssignableFrom(Set.class)) {
+                            containerPolicy = new CollectionContainerPolicy(HashSet.class);
+                        } else if(type.isAssignableFrom(Deque.class) || type.isAssignableFrom(Queue.class)) {
+                            containerPolicy = new CollectionContainerPolicy(LinkedList.class);
+                        } else if(type.isAssignableFrom(NavigableSet.class) || type.isAssignableFrom(SortedSet.class)) {
+                            containerPolicy = new CollectionContainerPolicy(TreeSet.class);
+                        } else {
+                            containerPolicy = new CollectionContainerPolicy(type);
+                        }
+                        Object container = containerPolicy.containerInstance();
+                        boolean wrapItemInJAXBElement = wrapItemInJAXBElement(genericType);
+                        for(Object element : (Collection<Object>) value) {
+                            element = handleJAXBElement(element, domainClass, wrapItemInJAXBElement);
+                            containerPolicy.addInto(element, container, null);
+                        }
+                        return container;
+                    }
+                } else {
+                    return value;
+                }
+            }
+        } catch(UnmarshalException unmarshalException) {
+            ResponseBuilder builder = Response.status(Status.BAD_REQUEST);
+            throw new WebApplicationException(unmarshalException, builder.build());
+        } catch(JAXBException jaxbException) {
+            throw new WebApplicationException(jaxbException);
+        }
+    }
+
+    /**
+     * Get first non java class if exists.
+     *
+     * @param domainClasses
+     * @return first domain class or first generic class or just the first class from the list
+     */
+    public Class<?> getDomainClass(Set<Class<?>> domainClasses) {
+
+        if (domainClasses.size() == 1) {
+            return domainClasses.iterator().next();
+        }
+
+        boolean isStringPresent = false;
+
+        for (Class<?> clazz : domainClasses) {
+            if (!clazz.getName().startsWith("java.")
+                    && !clazz.getName().startsWith("javax.")
+                    && !clazz.getName().startsWith("jakarta.")
+                    && !java.util.List.class.isAssignableFrom(clazz)) {
+                return clazz;
+            } else if (clazz == String.class) {
+                isStringPresent = true;
+            }
+        }
+
+        if (isStringPresent) {
+            return String.class;
+        }
+
+        //handle simple generic case
+        if (domainClasses.size() >= 2) {
+            Iterator<Class<?>> it = domainClasses.iterator();
+            it.next();
+            return it.next();
+        }
+
+        return domainClasses.iterator().next();
+    }
+
+    private boolean wrapItemInJAXBElement(Type genericType) {
+        if(genericType == JAXBElement.class) {
+            return true;
+        } else if(genericType instanceof GenericArrayType) {
+            return wrapItemInJAXBElement(((GenericArrayType) genericType).getGenericComponentType());
+        } else if(genericType instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType) genericType;
+            Type actualType = parameterizedType.getActualTypeArguments()[0];
+            return wrapItemInJAXBElement(parameterizedType.getOwnerType()) || wrapItemInJAXBElement(parameterizedType.getRawType()) || wrapItemInJAXBElement(actualType);
+        } else {
+            return false;
+        }
+    }
+
+    private Object handleJAXBElement(Object element, Class domainClass, boolean wrapItemInJAXBElement) {
+        if(wrapItemInJAXBElement) {
+            if(element instanceof JAXBElement) {
+                return element;
+            } else {
+                return new JAXBElement(EMPTY_STRING_QNAME, domainClass, element);
+            }
+        } else {
+            return JAXBIntrospector.getValue(element);
+        }
+    }
+
+    /**
+     * Specify a value that will be prepended to all keys that are mapped to an
+     * XML attribute.  By default there is no attribute prefix.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_ATTRIBUTE_PREFIX
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_ATTRIBUTE_PREFIX
+     */
+    public void setAttributePrefix(String attributePrefix) {
+        this.attributePrefix = attributePrefix;
+    }
+
+    /**
+     * Specify if the JSON output should be formatted (default is false).
+     * @param formattedOutput - true if the output should be formatted, else
+     * false.
+     */
+    public void setFormattedOutput(boolean formattedOutput) {
+        this.formattedOutput = formattedOutput;
+    }
+
+    /**
+     * Specify if the root node should be included in the JSON message (default
+     * is false).
+     * @param includeRoot - true if the message includes the root node, else
+     * false.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_INCLUDE_ROOT
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_INCLUDE_ROOT
+     */
+    public void setIncludeRoot(boolean includeRoot) {
+        this.includeRoot = includeRoot;
+    }
+
+    /**
+     * If true empty collections will be marshalled as empty arrays, else the
+     * collection will not be marshalled to JSON (default is true).
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_MARSHAL_EMPTY_COLLECTIONS
+     */
+    public void setMarshalEmptyCollections(boolean marshalEmptyCollections) {
+        this.marshalEmptyCollections = marshalEmptyCollections;
+    }
+
+   /**
+     * By default the JSON-binding will ignore namespace qualification. If this
+     * property is set then a prefix corresponding to the namespace URI and a
+     * namespace separator will be prefixed to the key.
+     * include it you can specify a Map of namespace URI to prefix.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#NAMESPACE_PREFIX_MAPPER
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_NAMESPACE_PREFIX_MAPPER
+     */
+    public void setNamespacePrefixMapper(Map<String, String> namespacePrefixMapper) {
+        this.namespacePrefixMapper = namespacePrefixMapper;
+    }
+
+    /**
+     * This character (default is '.') separates the prefix from the key name.
+     * It is only used if namespace qualification has been enabled be setting a
+     * namespace prefix mapper.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_NAMESPACE_SEPARATOR
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_NAMESPACE_SEPARATOR
+     */
+    public void setNamespaceSeparator(char namespaceSeparator) {
+        this.namespaceSeperator = namespaceSeparator;
+    }
+
+    /**
+     * If true the grouping element will be used as the JSON key.
+     *
+     * <p><b>Example</b></p>
+     * <p>Given the following class:</p>
+     * <pre>
+     * &#64;XmlAccessorType(XmlAccessType.FIELD)
+     * public class Customer {
+     *
+     *     &#64;XmlElementWrapper(name="phone-numbers")
+     *     &#64;XmlElement(name="phone-number")
+     *     private {@literal List<PhoneNumber>} phoneNumbers;
+     *
+     * }
+     * </pre>
+     * <p>If the property is set to false (the default) the JSON output will be:</p>
+     * <pre>
+     * {
+     *     "phone-numbers" : {
+     *         "phone-number" : [ {
+     *             ...
+     *         }, {
+     *             ...
+     *         }]
+     *     }
+     * }
+     * </pre>
+     * <p>And if the property is set to true, then the JSON output will be:</p>
+     * <pre>
+     * {
+     *     "phone-numbers" : [ {
+     *         ...
+     *     }, {
+     *         ...
+     *     }]
+     * }
+     * </pre>
+     * @since 2.4.2
+     * @see org.eclipse.persistence.jaxb.JAXBContextProperties#JSON_WRAPPER_AS_ARRAY_NAME
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_WRAPPER_AS_ARRAY_NAME
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_WRAPPER_AS_ARRAY_NAME
+     */
+    public void setWrapperAsArrayName(boolean wrapperAsArrayName) {
+        this.wrapperAsArrayName = wrapperAsArrayName;
+    }
+
+    /**
+     * Specify the key that will correspond to the property mapped with
+     * {@literal @XmlValue}.  This key will only be used if there are other mapped
+     * properties.
+     * @see org.eclipse.persistence.jaxb.MarshallerProperties#JSON_VALUE_WRAPPER
+     * @see org.eclipse.persistence.jaxb.UnmarshallerProperties#JSON_VALUE_WRAPPER
+     */
+    public void setValueWrapper(String valueWrapper) {
+        this.valueWrapper = valueWrapper;
+    }
+
+    /**
+     * @return true for all media types of the pattern *&#47;json and
+     * *&#47;*+json.
+     */
+    protected boolean supportsMediaType(MediaType mediaType) {
+        if(null == mediaType) {
+            return true;
+        }
+        String subtype = mediaType.getSubtype();
+        return subtype.equals(JSON) || subtype.endsWith(PLUS_JSON);
+    }
+
+    /**
+     * @see javax.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object, java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.OutputStream)
+     */
+    @Override
+    public void writeTo(Object object, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
+        try {
+            if(null == genericType) {
+                genericType = type;
+            }
+
+            Set<Class<?>> domainClasses = getDomainClasses(genericType);
+            JAXBContext jaxbContext = getJAXBContext(domainClasses, annotations, mediaType, httpHeaders);
+            Marshaller marshaller = jaxbContext.createMarshaller();
+            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, formattedOutput);
+            marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
+            marshaller.setProperty(MarshallerProperties.JSON_ATTRIBUTE_PREFIX, attributePrefix);
+            marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, includeRoot);
+            marshaller.setProperty(MarshallerProperties.JSON_MARSHAL_EMPTY_COLLECTIONS, marshalEmptyCollections);
+            marshaller.setProperty(MarshallerProperties.JSON_NAMESPACE_SEPARATOR, namespaceSeperator);
+            if(null != valueWrapper) {
+                marshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, valueWrapper);
+            }
+            marshaller.setProperty(MarshallerProperties.JSON_WRAPPER_AS_ARRAY_NAME, wrapperAsArrayName);
+            marshaller.setProperty(MarshallerProperties.NAMESPACE_PREFIX_MAPPER, namespacePrefixMapper);
+
+            Map<String, String> mediaTypeParameters = null;
+            if(null != mediaType) {
+                mediaTypeParameters = mediaType.getParameters();
+            }
+            if(null != mediaTypeParameters && mediaTypeParameters.containsKey(CHARSET)) {
+                String charSet = mediaTypeParameters.get(CHARSET);
+                marshaller.setProperty(Marshaller.JAXB_ENCODING, charSet);
+            }
+
+            preWriteTo(object, type, genericType, annotations, mediaType, httpHeaders, marshaller);
+            if (domainClasses.size() == 1) {
+                Class<?> domainClass = domainClasses.iterator().next();
+                if(!(List.class.isAssignableFrom(type) ||  type.isArray()) && domainClass.getPackage().getName().startsWith("java.")) {
+                    object = new JAXBElement(new QName((String) marshaller.getProperty(MarshallerProperties.JSON_VALUE_WRAPPER)), domainClass, object);
+                }
+            }
+
+            marshaller.marshal(object, entityStream);
+        } catch(JAXBException jaxbException) {
+            throw new WebApplicationException(jaxbException);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/jakarta/src/patch/java/org/eclipse/persistence/jpa/rs/PersistenceFactoryBase.java b/jakarta/src/patch/java/org/eclipse/persistence/jpa/rs/PersistenceFactoryBase.java
new file mode 100644
index 0000000..c83fdf6
--- /dev/null
+++ b/jakarta/src/patch/java/org/eclipse/persistence/jpa/rs/PersistenceFactoryBase.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+// Contributors:
+//         dclarke/tware - initial implementation
+//      gonural - version based persistence context
+//      Dmitry Kornilov - JPARS 2.0 related changes
+package org.eclipse.persistence.jpa.rs;
+
+import java.net.URI;
+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 jakarta.persistence.EntityManagerFactory;
+import jakarta.persistence.Persistence;
+
+import org.eclipse.persistence.config.PersistenceUnitProperties;
+import org.eclipse.persistence.dynamic.DynamicClassLoader;
+import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
+import org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl;
+import org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor;
+import org.eclipse.persistence.internal.jpa.deployment.SEPersistenceUnitInfo;
+import org.eclipse.persistence.jpa.Archive;
+import org.eclipse.persistence.jpa.rs.exceptions.JPARSException;
+import org.eclipse.persistence.jpa.rs.features.ServiceVersion;
+import org.eclipse.persistence.jpa.rs.util.JPARSLogger;
+
+/**
+ * Manages the PersistenceContexts that are used by a JPA-RS deployment.  Provides a single point to bootstrap
+ * and look up PersistenceContexts.
+ *
+ * @author tware
+ */
+public class PersistenceFactoryBase implements PersistenceContextFactory {
+    protected final Map<String, Set<PersistenceContext>> dynamicPersistenceContexts = new HashMap<>();
+
+    /**
+     * Bootstrap a PersistenceContext based on an pre-existing EntityManagerFactory.
+     *
+     * @param name      persistence context name
+     * @param emf       entity manager factory
+     * @param baseURI   base URI
+     * @param version   JPARS version. See {@link ServiceVersion} for more details.
+     * @param replace   Indicates that existing persistence context with given name and version must be replaced
+     *                  with the newly created one. If false passed the newly created context is not added to cache at all.
+     * @return newly created persistence context
+     */
+    public PersistenceContext bootstrapPersistenceContext(String name, EntityManagerFactory emf, URI baseURI, String version, boolean replace) {
+        final PersistenceContext persistenceContext = new PersistenceContext(name, (EntityManagerFactoryImpl) emf, baseURI, ServiceVersion.fromCode(version));
+
+        if (replace) {
+            addReplacePersistenceContext(persistenceContext);
+        }
+
+        return persistenceContext;
+    }
+
+    /**
+     * Stop the factory. Remove all the PersistenceContexts.
+     */
+    @Override
+    public void close() {
+        synchronized (this) {
+            for (Set<PersistenceContext> contextSet : dynamicPersistenceContexts.values()) {
+                if (contextSet != null) {
+                    for (PersistenceContext context : contextSet) {
+                        context.stop();
+                    }
+                }
+            }
+            dynamicPersistenceContexts.clear();
+        }
+    }
+
+    /**
+     * Close the PersistenceContext of a given name and clean it out of our list of PersistenceContexts.
+     *
+     * @param name name of the persistence context to close.
+     */
+    @Override
+    public void closePersistenceContext(String name) {
+        synchronized (this) {
+            Set<PersistenceContext> contextSet = dynamicPersistenceContexts.get(name);
+            if (contextSet != null) {
+                for (PersistenceContext context : contextSet) {
+                    context.stop();
+                }
+            }
+            dynamicPersistenceContexts.remove(name);
+        }
+    }
+
+    /**
+     * Close the PersistenceContext and clean it out of our list of PersistenceContexts.
+     *
+     * @param name name of the persistence context to close.
+     * @param version persistence context version
+     */
+    public void closePersistenceContext(String name, String version) {
+        synchronized (this) {
+            final Set<PersistenceContext> contextSet = dynamicPersistenceContexts.get(name);
+            if (contextSet != null) {
+                for (Iterator<PersistenceContext> iter = contextSet.iterator(); iter.hasNext();) {
+                    PersistenceContext context = iter.next();
+                    if (context.getVersion().equals(version)) {
+                        context.stop();
+                        iter.remove();
+                        break;
+                    }
+                }
+
+                if (contextSet.size() == 0) {
+                    dynamicPersistenceContexts.remove(name);
+                } else {
+                    dynamicPersistenceContexts.put(name, contextSet);
... 1168 lines suppressed ...