Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions src/main/java/org/spdx/core/ModelRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
*/
package org.spdx.core;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -38,6 +40,7 @@ public class ModelRegistry {
private static final ReadWriteLock lock = new ReentrantReadWriteLock();

private final Map<String, ISpdxModelInfo> registeredModels = new HashMap<>();
private final Map<String, Class<? extends CoreModelObject>> extensions = new HashMap<>();

/**
* Private constructor - singleton class
Expand Down Expand Up @@ -184,13 +187,43 @@ public CoreModelObject inflateModelObject(IModelStore modelStore, String objectU
if (!containsSpecVersion(specVersion)) {
throw new ModelRegistryException(specVersion + DOES_NOT_EXIST_MSG);
}
return registeredModels.get(specVersion).createModelObject(modelStore, objectUri,
type, copyManager, specVersion, create, idPrefix);
if (extensions.containsKey(type)) {
return inflateExtension(modelStore, objectUri, type, copyManager, specVersion, create, idPrefix);
} else {
return registeredModels.get(specVersion).createModelObject(modelStore, objectUri,
type, copyManager, specVersion, create, idPrefix);
}
} finally {
lock.readLock().unlock();
}
}

private CoreModelObject inflateExtension(IModelStore modelStore, String objectUri, String type,
IModelCopyManager copyManager, String specVersion,
boolean create, String idPrefix) throws InvalidSPDXAnalysisException {
try {
Constructor<?> con = extensions.get(type).getDeclaredConstructor(IModelStore.class, String.class,
IModelCopyManager.class, boolean.class, String.class, String.class);
return (CoreModelObject)con.newInstance(modelStore, objectUri, copyManager, create, specVersion, idPrefix);
} catch (NoSuchMethodException e) {
throw new InvalidSPDXAnalysisException("Could not create the extension type: "+type);
} catch (SecurityException e) {
throw new InvalidSPDXAnalysisException("Unexpected security exception for extension type: "+type, e);
} catch (InstantiationException e) {
throw new InvalidSPDXAnalysisException("Unexpected instantiation exception for extension type: "+type, e);
} catch (IllegalAccessException e) {
throw new InvalidSPDXAnalysisException("Unexpected illegal access exception for extension type: "+type, e);
} catch (IllegalArgumentException e) {
throw new InvalidSPDXAnalysisException("Unexpected illegal argument exception for extension type: "+type, e);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof InvalidSPDXAnalysisException) {
throw (InvalidSPDXAnalysisException)e.getTargetException();
} else {
throw new InvalidSPDXAnalysisException("Unexpected invocation target exception for extension type: "+type, e);
}
}
}

/**
* @param type String representation of the SPDX type
* @param specVersion version of the SPDX spec
Expand All @@ -205,7 +238,7 @@ public CoreModelObject inflateModelObject(IModelStore modelStore, String objectU
if (!registeredModels.containsKey(specVersion)) {
throw new ModelRegistryException("No implementation found for SPDX spec version "+specVersion);
}
return registeredModels.get(specVersion).getTypeToClassMap().get(type);
return registeredModels.get(specVersion).getTypeToClassMap().getOrDefault(type, extensions.get(type));
} finally {
lock.readLock().unlock();
}
Expand All @@ -218,6 +251,7 @@ public void clearAll() {
lock.writeLock().lock();
try {
registeredModels.clear();
extensions.clear();;
} finally {
lock.writeLock().unlock();
}
Expand Down Expand Up @@ -252,9 +286,30 @@ public boolean canBeExternal(Class<?> clazz, String specVersion) throws ModelReg
if (!containsSpecVersion(specVersion)) {
throw new ModelRegistryException(specVersion + DOES_NOT_EXIST_MSG);
}
if (extensions.containsValue(clazz)) {
return false;
}
return registeredModels.get(specVersion).canBeExternal(clazz);
} finally {
lock.readLock().unlock();
}
}

/**
* Registers an extension class that can be used to extend an SPDX model
* @param type type to be used
* @param clazz class which must be a subclass of ModelObject
* @return the class which was added to the registry
* @throws ModelRegistryException on missing model registry for the provided specVersion
*/
public Class<?> registerExtensionType(String type, Class<? extends CoreModelObject> clazz) throws ModelRegistryException {
Objects.requireNonNull(clazz, "Class can not be null to register extension type");
Objects.requireNonNull(type, "Type can not be null to register extension type");
lock.writeLock().lock();
try {
return extensions.put(type, clazz);
} finally {
lock.writeLock().unlock();
}
}
}
35 changes: 35 additions & 0 deletions src/test/java/org/spdx/core/MockExtension.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 Source Auditor Inc.
* SPDX-FileType: SOURCE
* SPDX-License-Identifier: Apache-2.0
* <p>
* Licensed 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.spdx.core;

import org.spdx.storage.IModelStore;

public class MockExtension extends MockModelType {

public static final String MOCK_EXTENSION_TYPE = "extensiontype";

public MockExtension(IModelStore modelStore, String objectUri, IModelCopyManager copyManager, boolean create, String specVersion, String idPrefix) throws InvalidSPDXAnalysisException {
super(modelStore, objectUri, copyManager, create, specVersion, idPrefix);
}

@Override
public String getType() {
return MOCK_EXTENSION_TYPE;
}
}
6 changes: 6 additions & 0 deletions src/test/java/org/spdx/core/MockModelType.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public MockModelType(IModelStore modelStore, String objectUri,
throws InvalidSPDXAnalysisException {
super(modelStore, objectUri, copyManager, create, specVersion, null);
}

public MockModelType(IModelStore modelStore, String objectUri,
IModelCopyManager copyManager, boolean create, String specVersion, String idPrefix)
throws InvalidSPDXAnalysisException {
super(modelStore, objectUri, copyManager, create, specVersion, idPrefix);
}

public MockModelType(CoreModelObjectBuilder builder, String specVersion) throws InvalidSPDXAnalysisException {
super(builder,specVersion);
Expand Down
72 changes: 68 additions & 4 deletions src/test/java/org/spdx/core/TestModelRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ public class TestModelRegistry {
MockModelStore modelStore;
MockCopyManager copyManager;

/**
* @throws java.lang.Exception
*/

@Before
public void setUp() {
modelStore = new MockModelStore();
Expand All @@ -42,7 +40,7 @@ public void setUp() {

/**
* Test method for {@link org.spdx.core.ModelRegistry#containsSpecVersion(java.lang.String)}.
* @throws InvalidSPDXAnalysisException
* @throws InvalidSPDXAnalysisException on error
*/
@Test
public void testAll() throws InvalidSPDXAnalysisException {
Expand Down Expand Up @@ -113,4 +111,70 @@ public boolean canBeExternal(Class<?> clazz) {
assertEquals(individual.getIndividualURI(), ((MockIndividual)iResult).getIndividualURI());
}

@Test
public void testExtensions() throws InvalidSPDXAnalysisException {
ModelRegistry.getModelRegistry().clearAll();
assertFalse(ModelRegistry.getModelRegistry().containsSpecVersion("3.0.0"));
Map<String, Enum<?>> uriToEnum = new HashMap<>();
uriToEnum.put(MockEnum.ENUM1.getIndividualURI(), MockEnum.ENUM1);
Map<String, Object> uriToIndividual = new HashMap<>();
MockIndividual individual = new MockIndividual();
uriToIndividual.put(individual.getIndividualURI(), individual);
Map<String, Class<?>> classMap = new HashMap<>();
classMap.put(MockModelType.TYPE, MockModelType.class);
ModelRegistry.getModelRegistry().registerModel(new ISpdxModelInfo() {

@Override
public Map<String, Enum<?>> getUriToEnumMap() {
return uriToEnum;
}

@Override
public List<String> getSpecVersions() {
return Arrays.asList(new String[] {"3.0.0"});
}

@Override
public CoreModelObject createExternalElement(IModelStore store,
String uri, IModelCopyManager copyManager, Class<?> type,
String specVersion) throws InvalidSPDXAnalysisException {
return new MockModelType(store, uri, copyManager, true, specVersion);
}

@Override
public CoreModelObject createModelObject(IModelStore modelStore,
String objectUri, String type,
IModelCopyManager copyManager, String specVersion,
boolean create, String idPrefix) throws InvalidSPDXAnalysisException {
return new MockModelType(modelStore, objectUri, copyManager, create, specVersion);
}

@Override
public Map<String, Class<?>> getTypeToClassMap() {
return classMap;
}

@Override
public Object uriToIndividual(String uri, @Nullable Class<?> type) {
return uriToIndividual.get(uri);
}

@Override
public boolean canBeExternal(Class<?> clazz) {
return false;
}

});

ModelRegistry.getModelRegistry().registerExtensionType(MockExtension.MOCK_EXTENSION_TYPE, MockExtension.class);
assertEquals(MockExtension.class,
ModelRegistry.getModelRegistry().typeToClass(MockExtension.MOCK_EXTENSION_TYPE, "3.0.0"));
CoreModelObject result = ModelRegistry.getModelRegistry().inflateModelObject(modelStore, "https://my.uri",
MockExtension.MOCK_EXTENSION_TYPE, copyManager, "3.0.0", true, "prefix");
assertTrue(result instanceof MockExtension);
assertEquals(MockExtension.MOCK_EXTENSION_TYPE, result.getType());


}

}