From 5b56dad8eafb3d14a5529726675da6a6df39f599 Mon Sep 17 00:00:00 2001 From: Gary O'Neall Date: Thu, 6 Nov 2025 17:39:59 -0800 Subject: [PATCH] Add support for extensions in ModelRegistry Signed-off-by: Gary O'Neall --- .../java/org/spdx/core/ModelRegistry.java | 61 +++++++++++++++- .../java/org/spdx/core/MockExtension.java | 35 +++++++++ .../java/org/spdx/core/MockModelType.java | 6 ++ .../java/org/spdx/core/TestModelRegistry.java | 72 +++++++++++++++++-- 4 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 src/test/java/org/spdx/core/MockExtension.java diff --git a/src/main/java/org/spdx/core/ModelRegistry.java b/src/main/java/org/spdx/core/ModelRegistry.java index e0faa37..b0170ca 100644 --- a/src/main/java/org/spdx/core/ModelRegistry.java +++ b/src/main/java/org/spdx/core/ModelRegistry.java @@ -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; @@ -38,6 +40,7 @@ public class ModelRegistry { private static final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Map registeredModels = new HashMap<>(); + private final Map> extensions = new HashMap<>(); /** * Private constructor - singleton class @@ -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 @@ -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(); } @@ -218,6 +251,7 @@ public void clearAll() { lock.writeLock().lock(); try { registeredModels.clear(); + extensions.clear();; } finally { lock.writeLock().unlock(); } @@ -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 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(); + } + } } diff --git a/src/test/java/org/spdx/core/MockExtension.java b/src/test/java/org/spdx/core/MockExtension.java new file mode 100644 index 0000000..da5a910 --- /dev/null +++ b/src/test/java/org/spdx/core/MockExtension.java @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025 Source Auditor Inc. + * SPDX-FileType: SOURCE + * SPDX-License-Identifier: Apache-2.0 + *

+ * 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 + *

+ * 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.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; + } +} diff --git a/src/test/java/org/spdx/core/MockModelType.java b/src/test/java/org/spdx/core/MockModelType.java index 9a612fc..bf3d8fe 100644 --- a/src/test/java/org/spdx/core/MockModelType.java +++ b/src/test/java/org/spdx/core/MockModelType.java @@ -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); diff --git a/src/test/java/org/spdx/core/TestModelRegistry.java b/src/test/java/org/spdx/core/TestModelRegistry.java index 218342f..fe4d387 100644 --- a/src/test/java/org/spdx/core/TestModelRegistry.java +++ b/src/test/java/org/spdx/core/TestModelRegistry.java @@ -31,9 +31,7 @@ public class TestModelRegistry { MockModelStore modelStore; MockCopyManager copyManager; - /** - * @throws java.lang.Exception - */ + @Before public void setUp() { modelStore = new MockModelStore(); @@ -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 { @@ -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> uriToEnum = new HashMap<>(); + uriToEnum.put(MockEnum.ENUM1.getIndividualURI(), MockEnum.ENUM1); + Map uriToIndividual = new HashMap<>(); + MockIndividual individual = new MockIndividual(); + uriToIndividual.put(individual.getIndividualURI(), individual); + Map> classMap = new HashMap<>(); + classMap.put(MockModelType.TYPE, MockModelType.class); + ModelRegistry.getModelRegistry().registerModel(new ISpdxModelInfo() { + + @Override + public Map> getUriToEnumMap() { + return uriToEnum; + } + + @Override + public List 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> 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()); + + + } + }