Skip to content

Commit

Permalink
adds option to overwrite ID Attribute
Browse files Browse the repository at this point in the history
Signed-off-by: Juergen Albert <[email protected]>
  • Loading branch information
juergen-albert committed Sep 2, 2024
1 parent 5aa8af5 commit 83b3e11
Show file tree
Hide file tree
Showing 18 changed files with 208 additions and 57 deletions.
11 changes: 11 additions & 0 deletions .project
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>_</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
</buildSpec>
<natures>
</natures>
</projectDescription>
4 changes: 2 additions & 2 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ pipeline {
sh "cp -r cnf/release/* $JENKINS_HOME/repo.gecko/snapshot/org.gecko.persistence"
}
}
stage('Gitlab branch release') {
stage('Gitlab branch Build') {
when {
branch 'gitlab'
}
steps {
echo "I am building on ${env.JOB_NAME}"
sh "./gradlew clean build release --info --stacktrace -Dmaven.repo.local=${WORKSPACE}/.m2"
sh "./gradlew clean build release --info --stacktrace -Dmaven.repo.local=${WORKSPACE}/.m2 --no-daemon"
sh "mkdir -p $JENKINS_HOME/repo.gecko/snapshot/org.gecko.emf.persistence_gitlab"
sh "rm -rf $JENKINS_HOME/repo.gecko/snapshot/org.gecko.emf.persistence_gitlab/*"
sh "cp -r cnf/release/* $JENKINS_HOME/repo.gecko/snapshot/org.gecko.emf.persistence_gitlab"
Expand Down
4 changes: 2 additions & 2 deletions cnf/build.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
dimcBaselining: true
mavenCentral: true
github-project: org.gecko.emf.persistence
base-version: 6.1.1
repository-base-version: 9.3.0.${tstamp}-SNAPSHOT
base-version: 6.2.0
repository-base-version: 9.4.0.${tstamp}-SNAPSHOT

# Maven Central Group-Id
# For Geckoprojects the groupid must start with org.geckoprojects
Expand Down
4 changes: 2 additions & 2 deletions cnf/central.mvn
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ org.freemarker:freemarker:2.3.32
org.geckoprojects.bnd:org.gecko.bnd.dimc.library:1.5.0
org.geckoprojects.bnd:org.gecko.bnd.jacoco.library:1.5.0
org.geckoprojects.bnd:org.gecko.bnd.osgitest.library:1.5.0
org.geckoprojects.emf:org.gecko.emf.osgi.bnd.library.workspace:6.0.2
org.geckoprojects.emf:org.gecko.emf.osgi.example.model.basic:6.0.2
org.geckoprojects.emf:org.gecko.emf.osgi.bnd.library.workspace:6.2.0
org.geckoprojects.emf:org.gecko.emf.osgi.example.model.basic:6.2.0
org.geckoprojects.emf.utils:org.gecko.emf.collections:2.2.2
org.mongodb:mongo-java-driver:3.12.14
org.geckoprojects.emf:org.gecko.emf.pushstreams:1.0.15
Expand Down
15 changes: 9 additions & 6 deletions org.gecko.emf.mongo/launch.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@
org.apache.felix.gogo.runtime;version='[1.1.6,1.1.7)',\
org.apache.felix.gogo.shell;version='[1.1.4,1.1.5)',\
org.apache.felix.scr;version='[2.2.6,2.2.7)',\
org.eclipse.emf.common;version='[2.28.0,2.28.1)',\
org.eclipse.emf.ecore;version='[2.33.0,2.33.1)',\
org.eclipse.emf.ecore.xmi;version='[2.18.0,2.18.1)',\
org.gecko.emf.collections;version='[2.2.0,2.2.1)',\
org.gecko.emf.mongo.api;version=snapshot,\
org.gecko.emf.mongo.component;version=snapshot,\
org.gecko.emf.osgi.api;version='[6.0.0,6.0.1)',\
org.gecko.mongo.osgi.component;version=snapshot,\
org.mongodb.mongo-java-driver;version='[3.12.14,3.12.15)',\
org.osgi.service.component;version='[1.5.1,1.5.2)',\
org.osgi.service.log;version='[1.5.0,1.5.1)',\
org.osgi.service.metatype;version='[1.4.1,1.4.2)',\
org.osgi.util.function;version='[1.2.0,1.2.1)',\
org.osgi.util.promise;version='[1.3.0,1.3.1)'
org.osgi.util.promise;version='[1.3.0,1.3.1)',\
org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\
org.eclipse.emf.common;version='[2.29.0,2.29.1)',\
org.eclipse.emf.ecore;version='[2.35.0,2.35.1)',\
org.eclipse.emf.ecore.xmi;version='[2.36.0,2.36.1)',\
org.gecko.emf.collections;version='[2.2.2,2.2.3)',\
org.gecko.emf.osgi.component.minimal;version='[6.2.0,6.2.1)',\
org.osgi.service.cm;version='[1.6.1,1.6.2)',\
org.osgi.util.converter;version='[1.0.9,1.0.10)'

-runrequires: \
osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.shell)',\
Expand Down
6 changes: 3 additions & 3 deletions org.gecko.emf.mongo/src/org/gecko/emf/mongo/MongoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static Object getID(URI uri) throws IOException {
if (uri.segmentCount() != 3) {
throw new IOException("The URI is not of the form 'mongo:/database/collection/{id}");
}
String id = uri.segment(2);
String id = URI.decode(uri.segment(2));

// If the ID was specified in the URI, we first attempt to create a MongoDB ObjectId. If
// that fails, we assume that the client has specified a non ObjectId and return the raw data.
Expand All @@ -68,7 +68,7 @@ public static String getIDAsString(URI uri) throws IOException {
if (uri.segmentCount() != 3) {
throw new IOException("The URI is not of the form 'mongo:/database/collection/{id}");
}
String id = uri.segment(2);
String id = URI.decode(uri.segment(2));

return id;
}
Expand All @@ -82,7 +82,7 @@ public static String getIDAsString(URI uri) throws IOException {
*/
public static Object getIDWithValidURI(URI uri) {

String id = uri.segment(2);
String id = URI.decode(uri.segment(2));

// If the ID was specified in the URI, we first attempt to create a MongoDB ObjectId. If
// that fails, we assume that the client has specified a non ObjectId and return the raw data.
Expand Down
61 changes: 61 additions & 0 deletions org.gecko.emf.mongo/src/org/gecko/emf/mongo/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
*******************************************************************************/
package org.gecko.emf.mongo;

import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.Enumerator;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
Expand Down Expand Up @@ -310,6 +312,23 @@ public interface Options {
* </code>
*/
String OPTION_COLLECTION_NAME = "COLLECTION_NAME";

/**
* An {@link EAttribute} to use as primary key instead of the ID Attribute
*
* Value type: {@link EAttribute}
*/
String OVERWRITE_PRIMARY_KEY_EATTRIBUTE = "OVERWRITE_PRIMARY_KEY_EATTRIBUTE";

/**
* A List of potential {@link EAttribute}s to use as primary key instead of the ID Attribute.
* The first {@link EAttribute} that fits the {@link EClass} to store will be used.
*
* If OVERWRITE_PRIMARY_KEY_EATTRIBUTE is set at the same time, it will be evaluated first.
*
* Value type: {@link List} of {@link EAttribute}
*/
String OVERWRITE_PRIMARY_KEY_EATTRIBUTES = "OVERWRITE_PRIMARY_KEY_EATTRIBUTES";

/**
* Returns <code>true</code>, if the {@link Options#OPTION_USE_EXTENDED_METADATA}
Expand Down Expand Up @@ -433,4 +452,46 @@ public static String getCollectionName(Map<?, ?> options) {
return alias == null ? null : alias.toString();
}

public static EAttribute getIDAttribute(EClass eClass, Map<?, ?> options) {
if(options == null) {
return eClass.getEIDAttribute();
}

EAttribute idAttribute = (EAttribute) options.getOrDefault(OVERWRITE_PRIMARY_KEY_EATTRIBUTE, null);
if(idAttribute != null && idAttribute.getEContainingClass().isSuperTypeOf(eClass)) {
return idAttribute;
}

Object attributesObject = options.getOrDefault(OVERWRITE_PRIMARY_KEY_EATTRIBUTES, null);
if(attributesObject != null) {
@SuppressWarnings("unchecked")
List<EAttribute> attributes = (List<EAttribute>) attributesObject;
int firstHit = -1;
for (int i = 0; i < attributes.size(); i++) {
EAttribute eAttribute = attributes.get(i);
EClass containingEClass = eAttribute.getEContainingClass();
if(containingEClass == eClass) {
return eAttribute;
}
if(firstHit != -1 && eAttribute.getEContainingClass().isSuperTypeOf(eClass)) {
firstHit = i;
}
}
if(firstHit != -1) {
return attributes.get(firstHit);
}
}

return eClass.getEIDAttribute();
}

public static boolean useIdAttributeAsPrimaryKey(Map<?, ?> options) {
Object value = options.getOrDefault(OPTION_USE_ID_ATTRIBUTE_AS_PRIMARY_KEY, null);
if(value == null) {
return false;
} else if(value instanceof String) {
return Boolean.parseBoolean((String) value);
}
return (boolean) value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ private void writeMongoIdAttribute(BsonWriter writer, EObject eObject){
Boolean useIdAttributeAsPrimaryKey = (Boolean) options.get(Options.OPTION_USE_ID_ATTRIBUTE_AS_PRIMARY_KEY);
Object id = null;
if (useIdAttributeAsPrimaryKey == null || useIdAttributeAsPrimaryKey) {
EAttribute idAttribute = eObject.eClass().getEIDAttribute();
EAttribute idAttribute = Options.getIDAttribute(eObject.eClass(), options);
if (idAttribute != null) {
id = eObject.eGet(idAttribute);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ public EObject decodeObject(BsonReader reader, DecoderContext context, Resource
}
oid = oidValue.toString();
reader.readBsonType(); //set the reader to the state requried by the next step.
URI uri = baseUri.trimSegments(1).appendSegment(oid).trimQuery();
URI uri = baseUri.trimSegments(1).appendSegment(URI.encodeSegment(oid, true)).trimQuery();
if(!resource.getURI().equals(uri)){
loadResource = factory.createResource(uri);
if(resourceCache != null){
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
@org.osgi.annotation.versioning.Version("5.0.0")
@org.osgi.annotation.versioning.Version("5.1.0")
@org.gecko.emf.mongo.annotations.RequireMongoEMF
package org.gecko.emf.mongo;
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ public MongoOutputStream(ConverterService converterService, MongoCollection<Docu
this.uri = uri;
this.idFactories = idProviders;
normalizeOptions(options);
Boolean useIdAttributeAsPrimaryKey = (Boolean) options.get(Options.OPTION_USE_ID_ATTRIBUTE_AS_PRIMARY_KEY);
this.useIdAttributeAsPrimaryKey = (useIdAttributeAsPrimaryKey == null || useIdAttributeAsPrimaryKey);
this.useIdAttributeAsPrimaryKey = Options.useIdAttributeAsPrimaryKey(options);
this.forceInsert = Boolean.TRUE.equals(options.get(Options.OPTION_FORCE_INSERT));
this.clearResourceAfterInsert = !options.containsKey(Options.OPTION_CLEAR_RESOURCE_AFTER_BATCH_INSERT) || Boolean.TRUE.equals(options.get(Options.OPTION_CLEAR_RESOURCE_AFTER_BATCH_INSERT));
}
Expand All @@ -111,7 +110,7 @@ public void close() throws IOException {
saveMultipleObjects(curCollection);
} else {
EObject eObject = resource.getContents().get(0);
EAttribute idAttribute = eObject.eClass().getEIDAttribute();
EAttribute idAttribute = Options.getIDAttribute(eObject.eClass(), mergedOptions);
String uriId = MongoUtils.getIDAsString(uri);
if(idAttribute == null && useIdAttributeAsPrimaryKey){
throw new IllegalStateException("EObject has no ID Attribute to be used together with option " + Options.OPTION_USE_ID_ATTRIBUTE_AS_PRIMARY_KEY);
Expand Down Expand Up @@ -140,7 +139,7 @@ public void close() throws IOException {
}
}
}
saveSingleObject(curCollection);
saveSingleObject(curCollection, idAttribute);
}
}

Expand Down Expand Up @@ -170,7 +169,7 @@ private void saveMultipleObjects(MongoCollection<EObject> collection) throws IOE

List<WriteModel<EObject>> bulk = new ArrayList<>(contents.size());
for (EObject eObject : contents) {
EAttribute idAttribute = eObject.eClass().getEIDAttribute();
EAttribute idAttribute = Options.getIDAttribute(eObject.eClass(), mergedOptions);

if(idAttribute == null && useIdAttributeAsPrimaryKey){
throw new IllegalStateException("EObjects have no ID Attribute to be used together with option " + Options.OPTION_USE_ID_ATTRIBUTE_AS_PRIMARY_KEY);
Expand All @@ -186,7 +185,7 @@ private void saveMultipleObjects(MongoCollection<EObject> collection) throws IOE
if(forceInsert){
bulk.add(new InsertOneModel<EObject>(eObject));
} else {
Bson updateFilter = createUpdateFilter(eObject);
Bson updateFilter = createUpdateFilter(eObject, idAttribute);
bulk.add(new ReplaceOneModel<EObject>(updateFilter, eObject, ReplaceOptions.createReplaceOptions(UPDATE_OPTIONS)));
}
}
Expand Down Expand Up @@ -216,14 +215,13 @@ private void saveMultipleObjects(MongoCollection<EObject> collection) throws IOE
* @param collection the collection to save the object for
* @throws IOException thrown on errors during saving
*/
private void saveSingleObject(MongoCollection<EObject> collection) throws IOException {
private void saveSingleObject(MongoCollection<EObject> collection, EAttribute idAttribute) throws IOException {
EObject eObject = resource.getContents().get(0);
if(forceInsert){
collection.insertOne(eObject);
} else {
Bson updateFilter = createUpdateFilter(eObject);
Bson updateFilter = createUpdateFilter(eObject, idAttribute);
FindOneAndReplaceOptions farOptions = new FindOneAndReplaceOptions().upsert(true).returnDocument(ReturnDocument.AFTER);
EAttribute idAttribute = eObject.eClass().getEIDAttribute();
// Minimize the load by just adding projection for minimum set of attributes
if (idAttribute != null) {
String eClassKey = Options.getEClassKey((Map<?, ?>) mergedOptions);
Expand All @@ -244,15 +242,14 @@ private void saveSingleObject(MongoCollection<EObject> collection) throws IOExce
}
}

private Bson createUpdateFilter(EObject eObject) throws IOException {
private Bson createUpdateFilter(EObject eObject, EAttribute idAttribute) throws IOException {
String idKey = "_id";
Object id = null;
if (!useIdAttributeAsPrimaryKey) {
String pkId = MongoUtils.getIDAsString(resource.getURI());
if (pkId != null && !pkId.isEmpty()) {
id = normalizeMongoId(pkId);
} else {
EAttribute idAttribute = eObject.eClass().getEIDAttribute();
idKey = idAttribute == null ? "_id" : idAttribute.getName();
id = EcoreUtil.getID(eObject);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.impl.EPackageImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.gecko.emf.mongo.Options;
import org.gecko.emf.osgi.ResourceSetFactory;
import org.gecko.emf.osgi.constants.EMFNamespaces;
import org.gecko.emf.osgi.example.model.basic.BasicFactory;
Expand Down Expand Up @@ -159,6 +164,66 @@ public void testEMFMongoRepository(@InjectService(cardinality = 0) ServiceAware<
assertTrue(mdpAware.isEmpty());
assertTrue(rsfAware.isEmpty());
}

@Test
public void testEMFMongoRepositoryOptionOverwritePrimaryKey(@InjectService(cardinality = 0) ServiceAware<MongoClientProvider> mcpAware,
@InjectService(cardinality = 0) ServiceAware<MongoDatabaseProvider> mdpAware,
@InjectService(cardinality = 0, filter = "(" + EMFRepository.PROP_ID + "=test1.test)") ServiceAware<EMFRepository> repoAware,
@InjectService(cardinality = 0, filter = "(&(" + EMFNamespaces.EMF_CONFIGURATOR_NAME + "=mongo)(" + EMFNamespaces.EMF_CONFIGURATOR_NAME + "=test1.test))") ServiceAware<ResourceSetFactory> rsfAware) throws BundleException, InvalidSyntaxException, IOException, InterruptedException {
/**
* mongo.instances=test1
* test1.baseUris=mongodb://localhost
* test1.databases=test
*/

Dictionary<String, Object> configProperties = new Hashtable<>();
configProperties.put("mongo.instances", "test1");
configProperties.put("test1.baseUris", "mongodb://" + mongoHost);
configProperties.put("test1.databases", "test");

// String clientId = "test1.test";

// defaultCheck();

assertTrue(repoAware.isEmpty());
assertTrue(mcpAware.isEmpty());
assertTrue(mdpAware.isEmpty());
assertTrue(rsfAware.isEmpty());

Configuration repositoryConfig = configAdmin.createFactoryConfiguration(EMFMongoConfiguratorConstants.EMF_MONGO_REPOSITORY_CONFIGURATOR_CONFIGURATION_NAME, "?");
repositoryConfig.update(configProperties);

rsfAware.waitForService(2000l);
mcpAware.waitForService(2000l);
mdpAware.waitForService(2000l);

EMFRepository repository = repoAware.waitForService(2000l);
URI expected = URI.createURI("mongodb://test1/test/EPackage").appendSegment(URI.encodeSegment(BasicPackage.eINSTANCE.getNsURI(), true)).appendFragment(URI.encodeFragment(BasicPackage.eINSTANCE.getNsURI(), true));

URI uri = repository.createUri(BasicPackage.eINSTANCE, Collections.singletonMap(Options.OVERWRITE_PRIMARY_KEY_EATTRIBUTE, EcorePackage.Literals.EPACKAGE__NS_URI));
System.out.println("EAttribute URI");
System.out.println("=========================================================");
System.out.println(uri);
assertEquals(expected, uri);

uri = repository.createUri(BasicPackage.eINSTANCE, Collections.singletonMap(Options.OVERWRITE_PRIMARY_KEY_EATTRIBUTES, Collections.singletonList(EcorePackage.Literals.EPACKAGE__NS_URI)));
System.out.println("EAttribute URI2");
System.out.println("=========================================================");
System.out.println(uri);
assertEquals(expected, uri);

EObject basicPackage = EcoreUtil.copy(BasicPackage.eINSTANCE);
repository.save(basicPackage, Collections.singletonMap(Options.OVERWRITE_PRIMARY_KEY_EATTRIBUTE, EcorePackage.Literals.EPACKAGE__NS_URI));
repository.detach(basicPackage);

EObject eObject = repository.getEObject(EcorePackage.Literals.EPACKAGE, BasicPackage.eINSTANCE.getNsURI(), Collections.singletonMap(Options.OVERWRITE_PRIMARY_KEY_EATTRIBUTE, EcorePackage.Literals.EPACKAGE__NS_URI));

assertNotNull(eObject);
assertEquals(EcorePackage.Literals.EPACKAGE, eObject.eClass());
assertEquals(BasicPackage.eINSTANCE.getNsURI(), ((EPackageImpl) eObject).getNsURI());
assertNotEquals(basicPackage, eObject);
assertNotEquals(BasicPackage.eINSTANCE, eObject);
}

// @Test
// public void testEMFMongoRepositoryPrototypeInstance() throws BundleException, InvalidSyntaxException, IOException, InterruptedException {
Expand Down
Loading

0 comments on commit 83b3e11

Please sign in to comment.