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 08:53:30 UTC
[tomee-jakarta] 01/01: Initial commit
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-jakarta.git
commit 8fa9ff1b25896056b38680edd70798e85dddb68d
Author: Jonathan Gallimore <jo...@jrg.me.uk>
AuthorDate: Fri Jun 19 09:53:11 2020 +0100
Initial commit
---
.gitignore | 23 +
pom.xml | 385 +++
src/main/assembly/tomee-microprofile.xml | 38 +
src/main/assembly/tomee-plume.xml | 38 +
src/main/assembly/tomee-plus.xml | 38 +
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 ++++++++
22 files changed, 11056 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..da38c0a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+target
+.classpath
+.project
+.settings
+.idea
+*.ipr
+*.iml
+*.log
+*.vscode
+*~
+quick.bat
+/tomee/tomee-plume-webapp/overlays/
+/tomee/tomee-plus-webapp/overlays/
+/temp
+/report.txt
+nb-configuration.xml
+.factorypath
+.history
+**/test/key
+tck/**/temp
+examples/jaxrs-json-provider-jettison/temp/
+transformer/jakartaee-prototype/
+transformer/transformer-0.1.0-SNAPSHOT/
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..fc38d34
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,385 @@
+<?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$ $Date$ -->
+
+<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">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache</groupId>
+ <artifactId>apache</artifactId>
+ <version>21</version>
+ <relativePath><!--Resolve on repository--></relativePath>
+ </parent>
+
+ <groupId>org.apache.tomee</groupId>
+ <artifactId>apache-tomee</artifactId>
+ <packaging>jar</packaging>
+ <version>9.0.0-M1-SNAPSHOT</version>
+
+ <name>TomEE :: TomEE :: Apache TomEE 9</name>
+ <description>Jakarta EE 9 version of Apache TomEE</description>
+ <url>http://tomee.apache.org</url>
+ <inceptionYear>1999</inceptionYear>
+
+ <prerequisites>
+ <maven>3.2.5</maven>
+ </prerequisites>
+
+ <scm>
+ <connection>scm:git:git@github.com:apache/tomee-jakarta.git</connection>
+ <url>scm:git:git@github.com:apache/tomee-jakarta.git</url>
+ <developerConnection>scm:git:git@github.com:apache/tomee-jakarta.git</developerConnection>
+ <tag>HEAD</tag>
+ </scm>
+
+ <issueManagement>
+ <system>jira</system>
+ <url>http://issues.apache.org/jira/browse/TOMEE</url>
+ </issueManagement>
+
+ <mailingLists>
+ <mailingList>
+ <name>TomEE Commits List</name>
+ <subscribe>commits-subscribe@tomee.apache.org</subscribe>
+ <unsubscribe>commits-unsubscribe@tomee.apache.org</unsubscribe>
+ <post>commits@tomee.apache.org</post>
+ <archive>http://mail-archives.apache.org/mod_mbox/tomee-commits/</archive>
+ </mailingList>
+ <mailingList>
+ <name>TomEE Developer List</name>
+ <subscribe>dev-subscribe@tomee.apache.org</subscribe>
+ <unsubscribe>dev-unsubscribe@tomee.apache.org</unsubscribe>
+ <post>dev@tomee.apache.org</post>
+ <archive>http://mail-archives.apache.org/mod_mbox/tomee-dev/</archive>
+ </mailingList>
+ <mailingList>
+ <name>TomEE Users List</name>
+ <subscribe>users-subscribe@tomee.apache.org</subscribe>
+ <unsubscribe>users-unsubscribe@tomee.apache.org</unsubscribe>
+ <post>users@tomee.apache.org</post>
+ <archive>http://mail-archives.apache.org/mod_mbox/tomee-users/</archive>
+ </mailingList>
+ </mailingLists>
+
+
+
+ <properties>
+ <tomee.version>8.0.3-SNAPSHOT</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-SNAPSHOT</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/src/main/assembly/tomee-microprofile.xml b/src/main/assembly/tomee-microprofile.xml
new file mode 100644
index 0000000..6a1e49b
--- /dev/null
+++ b/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/src/main/assembly/tomee-plume.xml b/src/main/assembly/tomee-plume.xml
new file mode 100644
index 0000000..bb71e05
--- /dev/null
+++ b/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/src/main/assembly/tomee-plus.xml b/src/main/assembly/tomee-plus.xml
new file mode 100644
index 0000000..e0c5708
--- /dev/null
+++ b/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/src/main/assembly/tomee-webprofile.xml b/src/main/assembly/tomee-webprofile.xml
new file mode 100644
index 0000000..1777653
--- /dev/null
+++ b/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/src/patch/java/org/apache/catalina/loader/WebappClassLoaderBase.java b/src/patch/java/org/apache/catalina/loader/WebappClassLoaderBase.java
new file mode 100644
index 0000000..2e75d4c
--- /dev/null
+++ b/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/src/patch/java/org/apache/cxf/jaxb/JAXBContextInitializer.java b/src/patch/java/org/apache/cxf/jaxb/JAXBContextInitializer.java
new file mode 100644
index 0000000..37b2d07
--- /dev/null
+++ b/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/src/patch/java/org/apache/openjpa/enhance/PCClassFileTransformer.java b/src/patch/java/org/apache/openjpa/enhance/PCClassFileTransformer.java
new file mode 100644
index 0000000..ee2cdac
--- /dev/null
+++ b/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/src/patch/java/org/apache/openjpa/lib/meta/ClassMetaDataIterator.java b/src/patch/java/org/apache/openjpa/lib/meta/ClassMetaDataIterator.java
new file mode 100644
index 0000000..b972512
--- /dev/null
+++ b/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.<suffix>".
+ *
+ * @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/src/patch/java/org/apache/openjpa/lib/util/TemporaryClassLoader.java b/src/patch/java/org/apache/openjpa/lib/util/TemporaryClassLoader.java
new file mode 100644
index 0000000..dc5a93c
--- /dev/null
+++ b/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/src/patch/java/org/apache/openjpa/meta/AbstractMetaDataDefaults.java b/src/patch/java/org/apache/openjpa/meta/AbstractMetaDataDefaults.java
new file mode 100644
index 0000000..03cd4fc
--- /dev/null
+++ b/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/src/patch/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java b/src/patch/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsIntrospectionSource.java
new file mode 100644
index 0000000..0c59207
--- /dev/null
+++ b/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/src/patch/java/org/apache/webbeans/proxy/AbstractProxyFactory.java b/src/patch/java/org/apache/webbeans/proxy/AbstractProxyFactory.java
new file mode 100644
index 0000000..eec5ba5
--- /dev/null
+++ b/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/src/patch/java/org/eclipse/persistence/internal/jpa/deployment/JavaSECMPInitializer.java b/src/patch/java/org/eclipse/persistence/internal/jpa/deployment/JavaSECMPInitializer.java
new file mode 100644
index 0000000..3b773b3
--- /dev/null
+++ b/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/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataAsmFactory.java b/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataAsmFactory.java
new file mode 100644
index 0000000..04fce33
--- /dev/null
+++ b/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/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataClass.java b/src/patch/java/org/eclipse/persistence/internal/jpa/metadata/accessors/objects/MetadataClass.java
new file mode 100644
index 0000000..0670e30
--- /dev/null
+++ b/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/src/patch/java/org/eclipse/persistence/jaxb/javamodel/Helper.java b/src/patch/java/org/eclipse/persistence/jaxb/javamodel/Helper.java
new file mode 100644
index 0000000..a8318eb
--- /dev/null
+++ b/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/src/patch/java/org/eclipse/persistence/jaxb/javamodel/reflection/JavaClassImpl.java b/src/patch/java/org/eclipse/persistence/jaxb/javamodel/reflection/JavaClassImpl.java
new file mode 100644
index 0000000..a1c74ba
--- /dev/null
+++ b/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/src/patch/java/org/eclipse/persistence/jaxb/rs/MOXyJsonProvider.java b/src/patch/java/org/eclipse/persistence/jaxb/rs/MOXyJsonProvider.java
new file mode 100644
index 0000000..7bce14a
--- /dev/null
+++ b/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>*/json (i.e. application/json and text/json)</li>
+ * <li>*/*+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 {
+ *
+ * @Override
+ * public Set<Class<?>> getClasses() {
+ * HashSet<Class<?>> set = new HashSet<Class<?>>(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 {
+ *
+ * @Override
+ * public Set<Class<?>> getClasses() {
+ * HashSet<Class<?>> set = new HashSet<Class<?>>(1);
+ * set.add(ExampleService.class);
+ * return set;
+ * }
+
+ * @Override
+ * public Set<Object> getSingletons() {
+ * moxyJsonProvider moxyJsonProvider = new MOXyJsonProvider();
+ * moxyJsonProvider.setFormattedOutput(true);
+ * moxyJsonProvider.setIncludeRoot(true);
+ *
+ * HashSet<Object> set = new HashSet<Object>(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;
+ *
+ * @Provider
+ * @Produces(MediaType.APPLICATION_JSON)
+ * @Consumes(MediaType.APPLICATION_JSON)
+ * public class CustomerJSONProvider extends MOXyJsonProvider {
+
+ * @Override
+ * public boolean isReadable(Class<?> type, Type genericType,
+ * Annotation[] annotations, MediaType mediaType) {
+ * return getDomainClass(genericType) == Customer.class;
+ * }
+ *
+ * @Override
+ * public boolean isWriteable(Class<?> type, Type genericType,
+ * Annotation[] annotations, MediaType mediaType) {
+ * return isReadable(type, genericType, annotations, mediaType);
+ * }
+ *
+ * @Override
+ * protected void preReadFrom(Class<Object> type, Type genericType,
+ * Annotation[] annotations, MediaType mediaType,
+ * MultivaluedMap<String, String> httpHeaders,
+ * Unmarshaller unmarshaller) throws JAXBException {
+ * unmarshaller.setProperty(MarshallerProperties.JSON_VALUE_WRAPPER, "$");
+ * }
+ *
+ * @Override
+ * protected void preWriteTo(Object object, Class<?> type, Type genericType,
+ * Annotation[] annotations, MediaType mediaType,
+ * MultivaluedMap<String, Object> 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<Customer></i>,
+ * <i>JAXBElement<Customer></i>, <i>JAXBElement<? extends Customer></i>,
+ * <i>List<JAXBElement<Customer>></i>, or
+ * <i>List<JAXBElement<? extends Customer>></i>
+ * <i>List<Foo<Bar>></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 */json or */*+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>
+ * @XmlAccessorType(XmlAccessType.FIELD)
+ * public class Customer {
+ *
+ * @XmlElementWrapper(name="phone-numbers")
+ * @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 */json or */*+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<Customer></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<Customer></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>
+ * @XmlAccessorType(XmlAccessType.FIELD)
+ * public class Customer {
+ *
+ * @XmlElementWrapper(name="phone-numbers")
+ * @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 */json and
+ * */*+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/src/patch/java/org/eclipse/persistence/jpa/rs/PersistenceFactoryBase.java b/src/patch/java/org/eclipse/persistence/jpa/rs/PersistenceFactoryBase.java
new file mode 100644
index 0000000..c83fdf6
--- /dev/null
+++ b/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.
+ *
... 1232 lines suppressed ...