• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3715

30 Nov 2023 08:55AM UTC coverage: 75.937% (+9.8%) from 66.106%
#3715

push

web-flow
Refactoring to reduce sonar code smell fixes (#4485)

---------

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>
Co-authored-by: Vladimir Kotal <vlada@kotalovi.cz>

397 of 478 new or added lines in 51 files covered. (83.05%)

50 existing lines in 22 files now uncovered.

44494 of 58593 relevant lines covered (75.94%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.42
/opengrok-indexer/src/main/java/org/opengrok/indexer/framework/PluginClassLoader.java
1
/*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License (the "License").
6
 * You may not use this file except in compliance with the License.
7
 *
8
 * See LICENSE.txt included in this distribution for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing Covered Code, include this CDDL HEADER in each
12
 * file and include the License file at LICENSE.txt.
13
 * If applicable, add the following below this CDDL HEADER, with the
14
 * fields enclosed by brackets "[]" replaced with your own identifying
15
 * information: Portions Copyright [yyyy] [name of copyright owner]
16
 *
17
 * CDDL HEADER END
18
 */
19

20
/*
21
 * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved.
22
 * Portions Copyright (c) 2020, Chris Fraire <cfraire@me.com>.
23
 */
24
package org.opengrok.indexer.framework;
25

26
import java.io.File;
27
import java.io.FileInputStream;
28
import java.io.IOException;
29
import java.io.InputStream;
30
import java.util.Arrays;
31
import java.util.HashMap;
32
import java.util.Map;
33
import java.util.Optional;
34
import java.util.jar.JarEntry;
35
import java.util.jar.JarFile;
36
import java.util.logging.Level;
37
import java.util.logging.Logger;
38
import org.opengrok.indexer.logger.LoggerFactory;
39

40
/**
41
 * Class loader for plugins from .class and .jar files.
42
 *
43
 * @author Krystof Tulinger
44
 */
45
public class PluginClassLoader extends ClassLoader {
46

47
    private final Map<String, Class<?>> cache = new HashMap<>();
1✔
48

49
    private static final Logger LOGGER = LoggerFactory.getLogger(PluginClassLoader.class);
1✔
50
    private static final String[] CLASS_WHITELIST = new String[]{
1✔
51
            "org.opengrok.indexer.configuration.Group",
52
            "org.opengrok.indexer.configuration.Project",
53
            "org.opengrok.indexer.configuration.RuntimeEnvironment",
54
            "org.opengrok.indexer.authorization.IAuthorizationPlugin",
55
            "org.opengrok.indexer.authorization.plugins.*",
56
            "org.opengrok.indexer.authorization.AuthorizationException",
57
            "org.opengrok.indexer.util.*",
58
            "org.opengrok.indexer.logger.*",
59
            "org.opengrok.indexer.Metrics"
60
    };
61

62
    private static final String[] PACKAGE_BLACKLIST = new String[]{
1✔
63
            "java",
64
            "javax",
65
            "org.w3c",
66
            "org.xml",
67
            "org.omg",
68
            "sun"
69
    };
70

71
    private static final String CLASS_SUFFIX = ".class";
72

73
    private final File directory;
74

75
    public PluginClassLoader(File directory) {
76
        super(PluginClassLoader.class.getClassLoader());
1✔
77
        this.directory = directory;
1✔
78
    }
1✔
79

80
    @SuppressWarnings("java:S1181")
81
    private Class<?> loadClassFromJar(String classname) throws ClassNotFoundException {
82
        File[] jars = directory.listFiles((dir, name) -> name.endsWith(".jar"));
1✔
83

84
        if (jars == null) {
1✔
85
            throw new ClassNotFoundException(
×
86
                    "Cannot load class " + classname,
87
                    new IOException("Directory " + directory + " is not accessible"));
88
        }
89

90
        for (File f : jars) {
1✔
91
            try (JarFile jar = new JarFile(f)) {
1✔
92
                // jar files always use / separator
93
                String filename = classname.replace('.', '/') + CLASS_SUFFIX;
1✔
94
                JarEntry entry = (JarEntry) jar.getEntry(filename);
1✔
95
                if (entry != null && entry.getName().endsWith(CLASS_SUFFIX)) {
1✔
96
                    try (InputStream is = jar.getInputStream(entry)) {
1✔
97
                        byte[] bytes = loadBytes(is);
1✔
98
                        Class<?> c = defineClass(classname, bytes, 0, bytes.length);
1✔
99
                        LOGGER.log(Level.FINE, "Class \"{0}\" found in file \"{1}\"",
1✔
100
                                new Object[]{
101
                                        classname,
102
                                        f.getAbsolutePath()
1✔
103
                                });
104
                        return c;
1✔
105
                    }
106
                }
107
            } catch (IOException ex) {
1✔
108
                LOGGER.log(Level.SEVERE, "Loading class threw an exception:", ex);
×
109
            } catch (Throwable ex) {
×
110
                LOGGER.log(Level.SEVERE, "Loading class threw an unknown exception", ex);
×
111
            }
1✔
112
        }
113
        throw new ClassNotFoundException("Class \"" + classname + "\" could not be found");
1✔
114
    }
115

116
    @SuppressWarnings("java:S1181")
117
    private Class<?> loadClassFromFile(String classname) throws ClassNotFoundException {
118
        try {
119
            String filename = classname.replace('.', File.separatorChar) + CLASS_SUFFIX;
1✔
120
            File f = new File(directory, filename);
1✔
121
            try (FileInputStream in = new FileInputStream(f)) {
×
122
                byte[] bytes = loadBytes(in);
×
123

124
                Class<?> c = defineClass(classname, bytes, 0, bytes.length);
×
125
                LOGGER.log(Level.FINEST, "Class \"{0}\" found in file \"{1}\"",
×
126
                        new Object[]{
127
                                classname,
128
                                f.getAbsolutePath()
×
129
                        });
130
                return c;
×
131
            }
132
        } catch (Throwable e) {
1✔
133
            throw new ClassNotFoundException(e.toString(), e);
1✔
134
        }
135
    }
136

137
    private byte[] loadBytes(InputStream in) throws IOException {
138
        byte[] bytes = new byte[in.available()];
1✔
139
        if (in.read(bytes) != bytes.length) {
1✔
140
            throw new IOException("unexpected truncated read");
×
141
        }
142
        return bytes;
1✔
143
    }
144

145
    private boolean checkWhiteList(String name) {
146
        for (String pattern : CLASS_WHITELIST) {
1✔
147
            pattern = pattern.replace(".", "\\.");
1✔
148
            pattern = pattern.replace("*", ".*");
1✔
149
            if (name.matches(pattern)) {
1✔
150
                return true;
1✔
151
            }
152
        }
153
        return false;
×
154
    }
155

156
    private void checkClassname(String name) throws SecurityException {
157
        if (name.startsWith("org.opengrok.")
1✔
158
                && !checkWhiteList(name)) {
1✔
159
            throw new SecurityException("Tried to load a blacklisted class \"" + name + "\"\n"
×
160
                    + "Allowed classes from opengrok package are only: "
161
                    + Arrays.toString(CLASS_WHITELIST));
×
162
        }
163
    }
1✔
164

165
    private void checkPackage(String name) throws SecurityException {
166
        for (String s : PACKAGE_BLACKLIST) {
1✔
167
            if (name.startsWith(s + ".")) {
1✔
168
                throw new SecurityException("Tried to load a class \"" + name
1✔
169
                        + "\" to a blacklisted package "
170
                        + "\"" + s + "\"\n"
171
                        + "Disabled packages are: "
172
                        + Arrays.toString(PACKAGE_BLACKLIST));
1✔
173
            }
174
        }
175
    }
1✔
176

177
    /**
178
     * Loads the class with given name.
179
     * <p>
180
     * Order of lookup:
181
     * <ol>
182
     * <li>already loaded classes </li>
183
     * <li>parent class loader</li>
184
     * <li>loading from .class files</li>
185
     * <li>loading from .jar files</li>
186
     * </ol>
187
     * <p>
188
     * Package blacklist: {@link #PACKAGE_BLACKLIST}.<br>
189
     * Classes whitelist: {@link #CLASS_WHITELIST}.
190
     *
191
     * @param name class name
192
     * @return loaded class or null
193
     * @throws ClassNotFoundException if class is not found
194
     * @throws SecurityException      if the loader cannot access the class
195
     */
196
    @Override
197
    public Class<?> loadClass(String name) throws ClassNotFoundException, SecurityException {
198
        return loadClass(name, true);
1✔
199
    }
200

201
    /**
202
     * Loads the class with given name.
203
     * <p>
204
     * Order of lookup:
205
     * <ol>
206
     * <li>already loaded classes </li>
207
     * <li>parent class loader</li>
208
     * <li>loading from .class files</li>
209
     * <li>loading from .jar files</li>
210
     * </ol>
211
     * <p>
212
     * Package blacklist: {@link #PACKAGE_BLACKLIST}.<br>
213
     * Classes whitelist: {@link #CLASS_WHITELIST}.
214
     *
215
     * @param name      class name
216
     * @param resolveIt if the class should be resolved
217
     * @return loaded class or null
218
     * @throws ClassNotFoundException if class is not found
219
     * @throws SecurityException      if the loader cannot access the class
220
     */
221
    @Override
222
    public Class<?> loadClass(String name, boolean resolveIt) throws ClassNotFoundException, SecurityException {
223

224
        var loadedClass = Optional.<Class<?>>ofNullable(cache.get(name))
1✔
225
                .or(() -> findAlreadyLoadedClass(name))
1✔
226
                .or(() -> findClassUsingParentClassLoader(name))
1✔
227
                .or(() -> findClassFromFile(name))
1✔
228
                .or(() -> findClassFromJar(name));
1✔
229
        loadedClass.ifPresent(clazz -> {
1✔
230
            cache.put(name, clazz);
1✔
231
            if (resolveIt) {
1✔
232
                resolveClass(clazz);
1✔
233
            }
234
        });
1✔
235
        return loadedClass
1✔
236
                .orElseThrow(() ->
1✔
237
                    new ClassNotFoundException("Class \"" + name + "\" was not found")
1✔
238
                );
239
    }
240
    private Optional<Class<?>> findAlreadyLoadedClass( String name) {
241
        checkClassname(name);
1✔
242
        return Optional.ofNullable(findLoadedClass(name));
1✔
243
    }
244

245
    private Optional<Class<?>> findClassUsingParentClassLoader( String name) {
246
        Class<?> clazz = null;
1✔
247
        if (this.getParent() != null) {
1✔
248
            try {
249
                clazz = this.getParent().loadClass(name);
1✔
250
            } catch (ClassNotFoundException ignored) {
1✔
251
                //ignore Class Not Found Exception
252
            }
1✔
253
        }
254
        return Optional.ofNullable(clazz);
1✔
255
    }
256

257
    private Optional<Class<?>> findClassFromFile( String name) {
258
        Class<?> clazz = null;
1✔
259
        try {
260
            checkPackage(name);
1✔
NEW
261
            clazz = loadClassFromFile(name);
×
262
        } catch (ClassNotFoundException ignored) {
1✔
263
            //ignore Class Not Found Exception
UNCOV
264
        }
×
265
        return Optional.ofNullable(clazz);
1✔
266
    }
267

268
    private Optional<Class<?>> findClassFromJar( String name) {
269
        Class<?> clazz = null;
1✔
270
        try {
271
            checkPackage(name);
1✔
272
            clazz = loadClassFromJar(name);
1✔
273
        } catch (ClassNotFoundException ignored) {
1✔
274
            //ignore Class Not Found Exception
275
        }
1✔
276
        return Optional.ofNullable(clazz);
1✔
277
    }
278

279
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc