1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */

17 package org.apache.tomcat.util.compat;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.lang.reflect.AccessibleObject;
22 import java.lang.reflect.Constructor;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.net.MalformedURLException;
26 import java.net.URI;
27 import java.net.URL;
28 import java.net.URLConnection;
29 import java.util.Deque;
30 import java.util.Set;
31 import java.util.jar.JarFile;
32 import java.util.zip.ZipFile;
33
34 import javax.net.ssl.SSLEngine;
35 import javax.net.ssl.SSLParameters;
36
37 import org.apache.juli.logging.Log;
38 import org.apache.juli.logging.LogFactory;
39 import org.apache.tomcat.util.res.StringManager;
40
41 class Jre9Compat extends JreCompat {
42
43     private static final Log log = LogFactory.getLog(Jre9Compat.class);
44     private static final StringManager sm = StringManager.getManager(Jre9Compat.class);
45
46     private static final Class<?> inaccessibleObjectExceptionClazz;
47     private static final Method setApplicationProtocolsMethod;
48     private static final Method getApplicationProtocolMethod;
49     private static final Method setDefaultUseCachesMethod;
50     private static final Method bootMethod;
51     private static final Method configurationMethod;
52     private static final Method modulesMethod;
53     private static final Method referenceMethod;
54     private static final Method locationMethod;
55     private static final Method isPresentMethod;
56     private static final Method getMethod;
57     private static final Constructor<JarFile> jarFileConstructor;
58     private static final Method isMultiReleaseMethod;
59     private static final Object RUNTIME_VERSION;
60     private static final int RUNTIME_MAJOR_VERSION;
61     private static final Method canAccessMethod;
62     private static final Method getModuleMethod;
63     private static final Method isExportedMethod;
64
65     static {
66         Class<?> c1 = null;
67         Method m2 = null;
68         Method m3 = null;
69         Method m4 = null;
70         Method m5 = null;
71         Method m6 = null;
72         Method m7 = null;
73         Method m8 = null;
74         Method m9 = null;
75         Method m10 = null;
76         Method m11 = null;
77         Constructor<JarFile> c12 = null;
78         Method m13 = null;
79         Object o14 = null;
80         Object o15 = null;
81         Method m16 = null;
82         Method m17 = null;
83         Method m18 = null;
84
85         try {
86             // Order is important for the error handling below.
87             // Must look up c1 first.
88             c1 = Class.forName("java.lang.reflect.InaccessibleObjectException");
89
90             Class<?> moduleLayerClazz = Class.forName("java.lang.ModuleLayer");
91             Class<?> configurationClazz = Class.forName("java.lang.module.Configuration");
92             Class<?> resolvedModuleClazz = Class.forName("java.lang.module.ResolvedModule");
93             Class<?> moduleReferenceClazz = Class.forName("java.lang.module.ModuleReference");
94             Class<?> optionalClazz = Class.forName("java.util.Optional");
95             Class<?> versionClazz = Class.forName("java.lang.Runtime$Version");
96             Method runtimeVersionMethod = JarFile.class.getMethod("runtimeVersion");
97             Method majorMethod = versionClazz.getMethod("major");
98
99             m2 = SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
100             m3 = SSLEngine.class.getMethod("getApplicationProtocol");
101             m4 = URLConnection.class.getMethod("setDefaultUseCaches", String.classboolean.class);
102             m5 = moduleLayerClazz.getMethod("boot");
103             m6 = moduleLayerClazz.getMethod("configuration");
104             m7 = configurationClazz.getMethod("modules");
105             m8 = resolvedModuleClazz.getMethod("reference");
106             m9 = moduleReferenceClazz.getMethod("location");
107             m10 = optionalClazz.getMethod("isPresent");
108             m11 = optionalClazz.getMethod("get");
109             c12 = JarFile.class.getConstructor(File.classboolean.classint.class, versionClazz);
110             m13 = JarFile.class.getMethod("isMultiRelease");
111             o14 = runtimeVersionMethod.invoke(null);
112             o15 = majorMethod.invoke(o14);
113             m16 = AccessibleObject.class.getMethod("canAccess"new Class<?>[] { Object.class });
114             m17 = Class.class.getMethod("getModule");
115             Class<?> moduleClass = Class.forName("java.lang.Module");
116             m18 = moduleClass.getMethod("isExported", String.class);
117
118         } catch (ClassNotFoundException e) {
119             if (c1 == null) {
120                 // Must be pre-Java 9
121                 log.debug(sm.getString("jre9Compat.javaPre9"), e);
122             } else {
123                 // Should never happen - signature error in lookup?
124                 log.error(sm.getString("jre9Compat.unexpected"), e);
125             }
126         } catch (ReflectiveOperationException | IllegalArgumentException e) {
127             // Should never happen
128             log.error(sm.getString("jre9Compat.unexpected"), e);
129         }
130
131         inaccessibleObjectExceptionClazz = c1;
132         setApplicationProtocolsMethod = m2;
133         getApplicationProtocolMethod = m3;
134         setDefaultUseCachesMethod = m4;
135         bootMethod = m5;
136         configurationMethod = m6;
137         modulesMethod = m7;
138         referenceMethod = m8;
139         locationMethod = m9;
140         isPresentMethod = m10;
141         getMethod = m11;
142         jarFileConstructor = c12;
143         isMultiReleaseMethod = m13;
144
145         RUNTIME_VERSION = o14;
146         if (o15 != null) {
147             RUNTIME_MAJOR_VERSION = ((Integer) o15).intValue();
148         } else {
149             // Must be Java 8
150             RUNTIME_MAJOR_VERSION = 8;
151         }
152
153         canAccessMethod = m16;
154         getModuleMethod = m17;
155         isExportedMethod = m18;
156     }
157
158
159     static boolean isSupported() {
160         return inaccessibleObjectExceptionClazz != null;
161     }
162
163
164     @Override
165     public boolean isInstanceOfInaccessibleObjectException(Throwable t) {
166         if (t == null) {
167             return false;
168         }
169
170         return inaccessibleObjectExceptionClazz.isAssignableFrom(t.getClass());
171     }
172
173
174     @Override
175     public void setApplicationProtocols(SSLParameters sslParameters, String[] protocols) {
176         try {
177             setApplicationProtocolsMethod.invoke(sslParameters, (Object) protocols);
178         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
179             throw new UnsupportedOperationException(e);
180         }
181     }
182
183
184     @Override
185     public String getApplicationProtocol(SSLEngine sslEngine) {
186         try {
187             return (String) getApplicationProtocolMethod.invoke(sslEngine);
188         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
189             throw new UnsupportedOperationException(e);
190         }
191     }
192
193
194     @Override
195     public void disableCachingForJarUrlConnections() throws IOException {
196         try {
197             setDefaultUseCachesMethod.invoke(null"JAR", Boolean.FALSE);
198         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
199             throw new UnsupportedOperationException(e);
200         }
201     }
202
203
204     @Override
205     public void addBootModulePath(Deque<URL> classPathUrlsToProcess) {
206         try {
207             Object bootLayer = bootMethod.invoke(null);
208             Object bootConfiguration = configurationMethod.invoke(bootLayer);
209             Set<?> resolvedModules = (Set<?>) modulesMethod.invoke(bootConfiguration);
210             for (Object resolvedModule : resolvedModules) {
211                 Object moduleReference = referenceMethod.invoke(resolvedModule);
212                 Object optionalURI = locationMethod.invoke(moduleReference);
213                 Boolean isPresent = (Boolean) isPresentMethod.invoke(optionalURI);
214                 if (isPresent.booleanValue()) {
215                     URI uri = (URI) getMethod.invoke(optionalURI);
216                     try {
217                         URL url = uri.toURL();
218                         classPathUrlsToProcess.add(url);
219                     } catch (MalformedURLException e) {
220                         log.warn(sm.getString("jre9Compat.invalidModuleUri", uri), e);
221                     }
222                 }
223             }
224         } catch (ReflectiveOperationException e) {
225             throw new UnsupportedOperationException(e);
226         }
227     }
228
229
230     @Override
231     public JarFile jarFileNewInstance(File f) throws IOException {
232         try {
233             return jarFileConstructor.newInstance(
234                     f, Boolean.TRUE, Integer.valueOf(ZipFile.OPEN_READ), RUNTIME_VERSION);
235         } catch (ReflectiveOperationException | IllegalArgumentException e) {
236             throw new IOException(e);
237         }
238     }
239
240
241     @Override
242     public boolean jarFileIsMultiRelease(JarFile jarFile) {
243         try {
244             return ((Boolean) isMultiReleaseMethod.invoke(jarFile)).booleanValue();
245         } catch (ReflectiveOperationException | IllegalArgumentException e) {
246             return false;
247         }
248     }
249
250
251     @Override
252     public int jarFileRuntimeMajorVersion() {
253         return RUNTIME_MAJOR_VERSION;
254     }
255
256
257     @Override
258     public boolean canAcccess(Object base, AccessibleObject accessibleObject) {
259         try {
260             return ((Boolean) canAccessMethod.invoke(accessibleObject, base)).booleanValue();
261         } catch (ReflectiveOperationException | IllegalArgumentException e) {
262             return false;
263         }
264     }
265
266
267     @Override
268     public boolean isExported(Class<?> type) {
269         try {
270             String packageName = type.getPackage().getName();
271             Object module = getModuleMethod.invoke(type);
272             return ((Boolean) isExportedMethod.invoke(module, packageName)).booleanValue();
273         } catch (ReflectiveOperationException e) {
274             return false;
275         }
276     }
277 }
278