Skip to content

Commit 150b5e2

Browse files
authored
Merge pull request #205 from pmonks/issue-193
Addresses issue 193
2 parents b69b167 + 8a938b7 commit 150b5e2

8 files changed

Lines changed: 650 additions & 87 deletions

File tree

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,31 @@ The methods enterCriticalSection and leaveCritialSection are available to suppor
2020
The library is available in [Maven Central org.spdx:java-spdx-library](https://search.maven.org/artifact/org.spdx/java-spdx-library).
2121

2222
If you are using Maven, you can add the following dependency in your POM file:
23-
```
23+
```xml
2424
<dependency>
2525
<groupId>org.spdx</groupId>
2626
<artifactId>java-spdx-library</artifactId>
2727
<version>(,1.0]</version>
2828
</dependency>
2929
```
3030

31-
[API JavaDocs are available here.](https://spdx.github.io/Spdx-Java-Library/)
31+
[API JavaDocs are available here](https://spdx.github.io/Spdx-Java-Library/).
3232

3333
There are a couple of static classes that help common usage scenarios:
3434

35-
- org.spdx.library.SPDXModelFactory supports the creation of specific model objects
36-
- org.spdx.library.model.license.LicenseInfoFactory supports the parsing of SPDX license expressions, creation, and comparison of SPDX licenses
35+
- `org.spdx.library.SPDXModelFactory` supports the creation of specific model objects
36+
- `org.spdx.library.model.license.LicenseInfoFactory` supports the parsing of SPDX license expressions, creation, and comparison of SPDX licenses
37+
38+
## Configuration options
39+
40+
`Spdx-Java-Library` can be configured using either Java system properties or a Java properties file located in the runtime CLASSPATH at `/resources/spdx-java-library.properties`.
41+
42+
The library has these configuration options:
43+
1. `SPDXParser.OnlyUseLocalLicenses` - a boolean that controls whether the (potentially out of date) listed license information bundled inside the JAR is used (true), vs the library downloading the latest files from the SPDX website (false). Default is false (always download the latest files from the SPDX website).
44+
2. `org.spdx.storage.listedlicense.enableCache` - a boolean that enables or disables a local cache for downloaded listed license information. Defaults to `false` (the cache is disabled). The cache location is determined as per the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) (i.e. `${XDG_CACHE_HOME}/Spdx-Java-Library` or `${HOME}/.cache/Spdx-Java-Library`).
45+
3. `org.spdx.storage.listedlicense.cacheCheckIntervalSecs` - a long that controls how often each cache entry is rechecked for staleness, in units of seconds. Defaults to 86,400 seconds (24 hours). Set to 0 (zero) to have each cache entry checked every time (note: this will result in a lot more network I/O and negatively impact performance, albeit there is still a substantial performance saving vs not using the cache at all).
46+
47+
Note that these configuration options can only be modified prior to first use of Spdx-Java-Library. Once the library is initialized, subsequent changes will have no effect.
3748

3849
## Update for new properties or classes
3950
To update Spdx-Java-Library, the following is a very brief checklist:

resources/licenses.properties

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# if true, use licenses from stored set of licenses rather than from the spdx.org/licenses website
2+
SPDXParser.OnlyUseLocalLicenses=false
3+
4+
# if true, enable a local download cache for listed license files downloaded from the SPDX website
5+
org.spdx.storage.listedlicense.enableCache=false
6+
7+
# local download cache re-check interval, in seconds
8+
org.spdx.storage.listedlicense.cacheCheckIntervalSecs=86400
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/**
2+
* Copyright (c) 2023 Source Auditor Inc.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.spdx;
19+
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.util.Properties;
23+
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
26+
27+
/**
28+
* The configuration class for the Spdx-Java-Library. When a caller attempts to retrieve a configuration property, it
29+
* will first be checked in the Java system properties (i.e. set via `-D` command line options to the JVM, or by
30+
* programmatic calls to `System.setProperty()` in code), and will then fallback on a properties file in the classpath.
31+
* That file must be called `/resources/spdx-java-library.properties`.
32+
*
33+
* Please see the documentation for specifics on what configuration options Spdx-Java-Library supports, and how they
34+
* impact the library's behavior.
35+
*/
36+
public final class Configuration {
37+
private static final Logger logger = LoggerFactory.getLogger(Configuration.class.getName());
38+
private static final String PROPERTIES_DIR = "resources";
39+
private static final String CONFIGURATION_PROPERTIES_FILENAME = PROPERTIES_DIR + "/" + "spdx-java-library.properties";
40+
private static final String DEPRECATED_CONFIGURATION_PROPERTIES_FILENAME = PROPERTIES_DIR + "/" + "licenses.properties"; // Deprecated filename
41+
42+
private static Configuration singleton;
43+
private final Properties properties;
44+
45+
private Configuration() {
46+
Properties tmpProperties = loadProperties(CONFIGURATION_PROPERTIES_FILENAME);
47+
if (tmpProperties == null) {
48+
// This is to preserve backwards compatibility with version 1.1.7 of the library and earlier
49+
tmpProperties = loadProperties(DEPRECATED_CONFIGURATION_PROPERTIES_FILENAME);
50+
if (tmpProperties != null) {
51+
logger.warn("You are using a deprecated configuration properties filename ('" + DEPRECATED_CONFIGURATION_PROPERTIES_FILENAME + "'). Please consider migrating to the new name ('" + CONFIGURATION_PROPERTIES_FILENAME + "').");
52+
}
53+
}
54+
properties = tmpProperties;
55+
}
56+
57+
/**
58+
* @return The singleton instance of the Configuration class.
59+
*/
60+
public static Configuration getInstance() {
61+
if (singleton == null) {
62+
singleton = new Configuration();
63+
}
64+
return singleton;
65+
}
66+
67+
/**
68+
* @param propertyName The name of the configuration property to retrieve.
69+
* @return The value of the given property name, or null if it wasn't found.
70+
*/
71+
public String getProperty(final String propertyName) {
72+
return getProperty(propertyName, null);
73+
}
74+
75+
/**
76+
* @param propertyName The name of the configuration property to retrieve.
77+
* @param defaultValue The default value to return, if the property isn't found.
78+
* @return The value of the given property name, or defaultValue if it wasn't found.
79+
*/
80+
public String getProperty(final String propertyName, final String defaultValue) {
81+
return System.getProperty(propertyName, properties == null ? defaultValue : properties.getProperty(propertyName, defaultValue));
82+
}
83+
84+
/**
85+
* Tries to load properties from the CLASSPATH, using the provided filename, ignoring errors
86+
* encountered during the process (e.g., the properties file doesn't exist, etc.).
87+
*
88+
* @param propertiesFileName the name of the file to load, including path (if any)
89+
* @return a (possibly empty) set of properties, or null if the properties file doesn't exist on the CLASSPATH
90+
*/
91+
private static Properties loadProperties(final String propertiesFileName) {
92+
Properties result = null;
93+
if (propertiesFileName != null) {
94+
InputStream in = null;
95+
try {
96+
in = Configuration.class.getResourceAsStream("/" + propertiesFileName);
97+
if (in != null) {
98+
result = new Properties();
99+
result.load(in);
100+
}
101+
} catch (IOException e) {
102+
// Ignore it and fall through
103+
logger.warn("IO Exception reading configuration properties file '" + propertiesFileName + "': " + e.getMessage(), e);
104+
result = null;
105+
} finally {
106+
if (in != null) {
107+
try {
108+
in.close();
109+
} catch (IOException e) {
110+
// Ignore it and fall through
111+
logger.warn("Unable to close configuration properties file '" + propertiesFileName + "': " + e.getMessage(), e);
112+
}
113+
}
114+
}
115+
}
116+
return result;
117+
}
118+
119+
}

src/main/java/org/spdx/library/model/license/ListedLicenses.java

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import org.slf4j.Logger;
3030
import org.slf4j.LoggerFactory;
31+
import org.spdx.Configuration;
3132
import org.spdx.library.InvalidSPDXAnalysisException;
3233
import org.spdx.library.SpdxConstants;
3334
import org.spdx.library.model.SpdxModelFactory;
@@ -45,10 +46,7 @@
4546
public class ListedLicenses {
4647

4748
static final Logger logger = LoggerFactory.getLogger(ListedLicenses.class.getName());
48-
private static final String PROPERTIES_DIR = "resources";
49-
private static final String LISTED_LICENSE_PROPERTIES_FILENAME = PROPERTIES_DIR + "/" + "licenses.properties";
5049

51-
Properties licenseProperties;
5250
boolean onlyUseLocalLicenses;
5351
private IListedLicenseStore licenseModelStore;
5452
private static ListedLicenses listedLicenses = null;
@@ -61,46 +59,14 @@ public class ListedLicenses {
6159
* This constructor should only be called by the getListedLicenses method
6260
*/
6361
private ListedLicenses() {
64-
licenseProperties = loadLicenseProperties();
65-
onlyUseLocalLicenses = Boolean.parseBoolean(
66-
System.getProperty("SPDXParser.OnlyUseLocalLicenses", licenseProperties.getProperty("OnlyUseLocalLicenses", "false")));
62+
// Note: this code confusingly uses different property values depending on the source (Java system property vs properties file),
63+
// so we have to check both names in order to not break downstream consumers' legacy configurations. This is _NOT_ recommended for
64+
// any new code that leverages the Configuration class.
65+
onlyUseLocalLicenses = Boolean.parseBoolean(Configuration.getInstance().getProperty("SPDXParser.OnlyUseLocalLicenses",
66+
Configuration.getInstance().getProperty("OnlyUseLocalLicenses", "false")));
6767
initializeLicenseModelStore();
6868
}
69-
70-
/**
71-
* Tries to load properties from LISTED_LICENSE_PROPERTIES_FILENAME, ignoring errors
72-
* encountered during the process (e.g., the properties file doesn't exist, etc.).
73-
*
74-
* @return a (possibly empty) set of properties
75-
*/
76-
private static Properties loadLicenseProperties() {
77-
listedLicenseModificationLock.writeLock().lock();
78-
try {
79-
Properties licenseProperties = new Properties();
80-
InputStream in = null;
81-
try {
82-
in = ListedLicenses.class.getResourceAsStream("/" + LISTED_LICENSE_PROPERTIES_FILENAME);
83-
if (in != null) {
84-
licenseProperties.load(in);
85-
}
86-
} catch (IOException e) {
87-
// Ignore it and fall through
88-
logger.warn("IO Exception reading listed license properties file: " + e.getMessage(), e);
89-
} finally {
90-
if (in != null) {
91-
try {
92-
in.close();
93-
} catch (IOException e) {
94-
logger.warn("Unable to close listed license properties file: " + e.getMessage(), e);
95-
}
96-
}
97-
}
98-
return licenseProperties;
99-
} finally {
100-
listedLicenseModificationLock.writeLock().unlock();
101-
}
102-
}
103-
69+
10470
private void initializeLicenseModelStore() {
10571
listedLicenseModificationLock.writeLock().lock();
10672
try {
@@ -125,8 +91,6 @@ private void initializeLicenseModelStore() {
12591
}
12692
}
12793

128-
129-
13094
public static ListedLicenses getListedLicenses() {
13195

13296
ListedLicenses retval = null;

src/main/java/org/spdx/storage/listedlicense/SpdxListedLicenseWebStore.java

Lines changed: 10 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,61 +19,33 @@
1919

2020
import java.io.IOException;
2121
import java.io.InputStream;
22-
import java.net.HttpURLConnection;
2322
import java.net.URL;
24-
import java.util.Arrays;
25-
import java.util.Collections;
26-
import java.util.List;
27-
import java.util.Objects;
2823

24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
2926
import org.spdx.library.InvalidSPDXAnalysisException;
3027
import org.spdx.library.SpdxConstants;
28+
import org.spdx.utility.DownloadCache;
3129

3230
/**
33-
* @author gary
31+
* @author gary Original code
32+
* @author pmonks Optional caching of downloaded files
3433
*
3534
*/
3635
public class SpdxListedLicenseWebStore extends SpdxListedLicenseModelStore {
3736

38-
private static final int READ_TIMEOUT = 5000;
39-
40-
static final List<String> WHITE_LIST = Collections.unmodifiableList(Arrays.asList(
41-
"spdx.org", "spdx.dev", "spdx.com", "spdx.info")); // Allowed host names for the SPDX listed licenses
37+
private static final Logger logger = LoggerFactory.getLogger(SpdxListedLicenseModelStore.class);
38+
4239

4340
/**
4441
* @throws InvalidSPDXAnalysisException
4542
*/
4643
public SpdxListedLicenseWebStore() throws InvalidSPDXAnalysisException {
4744
super();
4845
}
49-
50-
private InputStream getUrlInputStream(URL url) throws IOException {
51-
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
52-
connection.setReadTimeout(READ_TIMEOUT);
53-
int status = connection.getResponseCode();
54-
if (status != HttpURLConnection.HTTP_OK &&
55-
(status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM
56-
|| status == HttpURLConnection.HTTP_SEE_OTHER)) {
57-
// redirect
58-
String redirectUrlStr = connection.getHeaderField("Location");
59-
if (Objects.isNull(redirectUrlStr) || redirectUrlStr.isEmpty()) {
60-
throw new IOException("Empty redirect URL response");
61-
}
62-
URL redirectUrl;
63-
try {
64-
redirectUrl = new URL(redirectUrlStr);
65-
} catch(Exception ex) {
66-
throw new IOException("Invalid redirect URL", ex);
67-
}
68-
if (!redirectUrl.getProtocol().toLowerCase().startsWith("http")) {
69-
throw new IOException("Invalid redirect protocol");
70-
}
71-
if (!WHITE_LIST.contains(redirectUrl.getHost())) {
72-
throw new IOException("Invalid redirect host - not on the allowed 'white list'");
73-
}
74-
connection = (HttpURLConnection)redirectUrl.openConnection();
75-
}
76-
return connection.getInputStream();
46+
47+
private InputStream getUrlInputStream(final URL url) throws IOException {
48+
return DownloadCache.getInstance().getUrlInputStream(url);
7749
}
7850

7951
@Override

0 commit comments

Comments
 (0)