Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1e4e54a
Major refactoring to support PIDs
mikesname Mar 12, 2026
b9d7fd9
Add Cypher function for generating appropriate ARK names
mikesname May 8, 2026
e9e33c9
Update GraphQL schema to contain pid and ARK
mikesname May 8, 2026
fc0be92
Break out reusable part of EadSync class to its own thing
mikesname May 18, 2026
6a86940
Add PIDs to country
mikesname May 19, 2026
99d52bc
Bump PID length to 12 and drop first character restriction.
mikesname May 21, 2026
1d75fa2
Add a WS method to generate pids (and update version data)
mikesname May 21, 2026
8d958d6
Add ARKs to EAC and Dublin Core XML exports
mikesname May 21, 2026
8b1f786
Add a migrate method to the repository resource.
mikesname May 22, 2026
fc4a6a9
Remove excessive debug from BundleManager TRACE output
mikesname May 27, 2026
02a1946
Bump version to 0.16.0
mikesname Jun 2, 2026
7b217c4
Miscellaneous cleanups, no functional change
mikesname Jun 2, 2026
363ea3e
Use a better method for iterating classes implementing an i-face
mikesname Jun 4, 2026
a295d25
Add pids to Link and Annotation entities
mikesname Jun 5, 2026
0ac2b5f
Slightly refactor retrieving generic items via API and WS
mikesname Jun 15, 2026
00e861b
Set version to a snapshot
mikesname Jun 15, 2026
a2ca65f
Add an extra WS endpoint for exporting ID->PID mapping
mikesname Jun 19, 2026
ba97604
FIXUP small cleanups
mikesname Jun 24, 2026
91fba1e
Improve handling of integrity errors
mikesname Jun 26, 2026
8200b7e
Add an extra code string field to WS mapped exceptions
mikesname Jun 26, 2026
fb18b9c
Only allow passing in the PID if a special header is set
mikesname Jun 26, 2026
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Docker file for EHRI backend web service
FROM docker.io/neo4j:3.5.28
FROM docker.io/neo4j:3.5.35

# Set git commit build revision as a label which
# we can inspect to figure out the image version.
Expand Down
4 changes: 2 additions & 2 deletions build/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>eu.ehri-project</groupId>
<artifactId>ehri-data</artifactId>
<version>0.15.5</version>
<version>0.16.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>build</artifactId>
Expand Down Expand Up @@ -70,7 +70,7 @@
<include>xerces:xercesImpl</include>
<include>xml-apis:xml-apis</include>
<include>com.typesafe:config</include>
<include>com.flipkart.zjsonpatch:zjsonpatch</include>
<include>io.github.vishwakarma:zjsonpatch</include>
<include>org.apache.commons:commons-collections4</include>

<!-- GraphQL deps -->
Expand Down
2 changes: 1 addition & 1 deletion ehri-cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>eu.ehri-project</groupId>
<artifactId>ehri-data</artifactId>
<version>0.15.5</version>
<version>0.16.0-SNAPSHOT</version>
</parent>
<artifactId>ehri-cli</artifactId>
<name>Command Line Tools</name>
Expand Down
16 changes: 11 additions & 5 deletions ehri-cli/src/main/java/eu/ehri/project/commands/ImportCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.tinkerpop.frames.FramedGraph;
import eu.ehri.project.IdGeneratorFactory;
import eu.ehri.project.acl.SystemScope;
import eu.ehri.project.core.GraphManager;
import eu.ehri.project.core.GraphManagerFactory;
import eu.ehri.project.importers.ImportLog;
import eu.ehri.project.importers.ImportOptions;
import eu.ehri.project.importers.PreImportCallback;
import eu.ehri.project.importers.base.ItemImporter;
import eu.ehri.project.importers.base.SaxXmlHandler;
import eu.ehri.project.importers.managers.SaxImportManager;
import eu.ehri.project.models.UserProfile;
import eu.ehri.project.models.base.PermissionScope;
import eu.ehri.project.models.idgen.RandomIdGenerator;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
Expand All @@ -51,6 +54,8 @@ public abstract class ImportCommand extends BaseCommand {
private final Class<? extends SaxXmlHandler> handler;
private final Class<? extends ItemImporter<?, ?>> importer;

private static final RandomIdGenerator idGenerator = IdGeneratorFactory.getIdGenerator();

public ImportCommand(Class<? extends SaxXmlHandler> handler, Class<? extends ItemImporter<?, ?>> importer) {
this.handler = handler;
this.importer = importer;
Expand Down Expand Up @@ -114,7 +119,7 @@ protected void setCustomOptions(Options options) {

@Override
public int execWithOptions(FramedGraph<?> graph,
CommandLine cmdLine) throws Exception {
CommandLine cmdLine) throws Exception {

GraphManager manager = GraphManagerFactory.getInstance(graph);

Expand Down Expand Up @@ -159,10 +164,11 @@ public int execWithOptions(FramedGraph<?> graph,
.withLang(lang);

ImportLog log = SaxImportManager.create(graph, scope, user,
importer,
handler,
options,
Lists.newArrayList())
importer,
handler,
options
)
.withPreCallback(PreImportCallback.generatePid(idGenerator))
.importFiles(filePaths, logMessage);
System.out.println(log);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class EntityAddTest extends AbstractFixtureTest {
@Test
public void testEntityAdd() throws Exception {
String[] args = new String[]{EntityClass.COUNTRY.getName(),
"-P", "name=Elbonia", "-P", "identifier=el",
"-P", "name=Elbonia", "-P", "identifier=el", "-P", "__pid=el-1234",
"--user", "mike"};
EntityAdd ua = new EntityAdd();
CommandLine cmdLine = ua.getCmdLine(args);
Expand All @@ -46,7 +46,7 @@ public void testEntityAdd() throws Exception {
@Test
public void testEntityAddWithBadType() throws Exception {
String[] args = new String[]{EntityClass.ADDRESS.getName(),
"-P", "name=Elbonia", "-P", "identifier=el",
"-P", "name=Elbonia", "-P", "identifier=el", "-P", "__pid=el-1234",
"--user", "mike"};
EntityAdd ua = new EntityAdd();
CommandLine cmdLine = ua.getCmdLine(args);
Expand Down
18 changes: 17 additions & 1 deletion ehri-core/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
ehri-core
=========

Classes defining models and handling business logic such as persistence, access control, and permissions validation.
Classes defining models and handling business logic such as persistence, access control, and permissions validation.

There are some semantics for managing generic property-graph data that this package uses:

There are two reserved properties for system use:

* `__id`
* `__type`

Other properties beginning with two underscores (`__`) are "initialisation properties" and can
only be set via generic means when the vertex is first created. When a vertex is updated, all
initialisation properties are ignored. When a vertex is serialized to a `Bundle` object, all
initialisation properties are included in the data section (except reserved properties) but
are ignored in object comparison.

Properties beginning with a single underscore are considered metadata and are ignored in object
comparison, but are otherwise handled like regular vertex properties.
6 changes: 3 additions & 3 deletions ehri-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>eu.ehri-project</groupId>
<artifactId>ehri-data</artifactId>
<version>0.15.5</version>
<version>0.16.0-SNAPSHOT</version>
</parent>
<artifactId>ehri-core</artifactId>
<packaging>jar</packaging>
Expand Down Expand Up @@ -167,9 +167,9 @@
<version>1.21</version>
</dependency>
<dependency>
<groupId>com.flipkart.zjsonpatch</groupId>
<groupId>io.github.vishwakarma</groupId>
<artifactId>zjsonpatch</artifactId>
<version>0.4.11</version>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>eu.ehri-project</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ private boolean isInScope(PermissionGrant grant) {
/**
* Get the content type with the given id.
*/
private ContentTypes getContentType(EntityClass type) {
public ContentTypes getContentType(EntityClass type) {
try {
return ContentTypes.withName(type.getName());
} catch (NoSuchElementException e) {
Expand Down
24 changes: 23 additions & 1 deletion ehri-core/src/main/java/eu/ehri/project/api/Api.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ interface BatchCallback {
void onItemDeleted(int itemNumber, String id);
}

/**
* Get any item by ID or PID.
*
* @param id the item's ID
* @param usePid if the ID is a PID
* @return an {@link Accessible} item
* @throws ItemNotFound if the item is not found
* @throws InvalidIdentifierError if the item is not a PID type
*/
Accessible getAny(String id, boolean usePid) throws ItemNotFound, InvalidIdentifierError;

/**
* Fetch an item, as a user. This only provides access control.
*
Expand All @@ -80,6 +91,17 @@ interface BatchCallback {
*/
<E extends Accessible> E get(String id, Class<E> cls) throws ItemNotFound;

/**
* Fetch an item by PID, as a user. This only provides access control.
*
* @param pid the item persistent ID
* @param cls the item's class
* @param <E> the type of entity
* @return the given framed vertex
* @throws ItemNotFound if the item does not exist
*/
<E extends Accessible> E getByPid(String pid, Class<E> cls) throws ItemNotFound, InvalidIdentifierError;

/**
* Update an object bundle, also updating dependent items.
*
Expand Down Expand Up @@ -456,7 +478,7 @@ InheritedItemPermissionSet setItemPermissions(Accessible item, Accessor accessor
/**
* Set members of a given group, removing existing members not in this set.
*
* @param group the group
* @param group the group
* @param usersOrGroups the users or groups to add
* @throws PermissionDenied if the grant action is not available to the user
*/
Expand Down
60 changes: 51 additions & 9 deletions ehri-core/src/main/java/eu/ehri/project/api/impl/ApiImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.frames.FramedGraph;
import eu.ehri.project.acl.*;
import eu.ehri.project.api.*;
Expand Down Expand Up @@ -134,16 +135,52 @@ public <E extends Accessible> E get(String id, Class<E> cls) throws ItemNotFound
}
return item;
} catch (ItemNotFound e) {
// If the item has been deleted, augment the exception with a deletion time...
Optional<Version> versionOpt = versionManager.versionAtDeletion(id);
if (versionOpt.isPresent()) {
Version version = versionOpt.get();
SystemEvent event = version.getTriggeringEvent();
throw e.withDeletedAt(event.getTimestamp());
} else {
throw e;
throw augmentNotFoundInfo(id, false, e);
}
}

@Override
public <E extends Accessible> E getByPid(String pid, Class<E> cls) throws ItemNotFound, InvalidIdentifierError {
try {
Optional<Vertex> opt = manager.getVertex(Ontology.PID_KEY, pid);
if (!opt.isPresent()) {
throw new ItemNotFound(pid);
}
E item = graph.frame(opt.get(), cls);
if (!PersistentIdentifiable.class.isAssignableFrom(manager.getEntityClass(item).getJavaClass())) {
throw new InvalidIdentifierError(pid);
}
if (!aclManager.canAccess(item, accessor)) {
throw new ItemNotFound(pid);
}
return item;
} catch (ItemNotFound e) {
throw augmentNotFoundInfo(pid, true, e);
}
}

@Override
public Accessible getAny(String id, boolean usePid) throws ItemNotFound, InvalidIdentifierError {
try {
Accessible item = usePid ? getByPid(id, Accessible.class) : get(id, Accessible.class);
if (!Accessible.class.isAssignableFrom(manager.getEntityClass(item).getJavaClass())) {
throw new ItemNotFound(id);
} else if (!aclManager.getContentTypeFilterFunction().compute(item.asVertex())) {
throw new ItemNotFound(id);
} else if (!aclManager.canAccess(item, accessor)) {
throw new ItemNotFound(id);
}
return item;
} catch (ItemNotFound e) {
throw augmentNotFoundInfo(id, usePid, e);
}

}

private ItemNotFound augmentNotFoundInfo(String id, boolean usePid, ItemNotFound e) {
// If the item has been deleted, augment the exception with a deletion time...
Optional<Version> versionOpt = versionManager.versionAtDeletion(id, usePid);
return versionOpt.map(v -> addTimestamp(e, v)).orElse(e);
}

@Override
Expand Down Expand Up @@ -511,7 +548,7 @@ public List<String> deleteChildren(String parentId, boolean all, boolean version
Iterable<Accessible> children = all
? scope.getAllContainedItems()
: scope.getContainedItems();
List<String> ids = StreamSupport
final List<String> ids = StreamSupport
.stream(children.spliterator(), false)
.map(Entity::getId)
.sorted()
Expand Down Expand Up @@ -662,4 +699,9 @@ private void ensureCanModifyGroupMembership(Group group, Accessor user, Accessor
helper.checkEntityPermission(group, grantee, PermissionType.UPDATE);
}
}

private ItemNotFound addTimestamp(ItemNotFound e, Version version) {
SystemEvent event = version.getTriggeringEvent();
return e.withDeletedAt(event.getTimestamp());
}
}
21 changes: 21 additions & 0 deletions ehri-core/src/main/java/eu/ehri/project/core/GraphManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import eu.ehri.project.models.base.Entity;

import java.util.Map;
import java.util.Optional;

/**
* An abstraction over the basic {@link FramedGraph} class
Expand Down Expand Up @@ -100,6 +101,15 @@ public interface GraphManager {
*/
Vertex getVertex(String id) throws ItemNotFound;

/**
* Get a node with the given key/value pair.
*
* @param key The vertex's property
* @param value The vertex's property value
* @return The vertex, if found
*/
Optional<Vertex> getVertex(String key, Object value);

/**
* Get a node with the given ID, and frame it with the given interface
* class.
Expand Down Expand Up @@ -154,6 +164,17 @@ public interface GraphManager {
*/
CloseableIterable<Vertex> getVertices(Iterable<String> ids);

/**
* Get a CloseableIterable of vertices where the given property has the
* provided values. If a vertex is not found with an input value the
* result in the returned iterable will be null.
*
* @param key the property key
* @param values property values
* @return an iterable of vertices with the given values for the provided property
*/
CloseableIterable<Vertex> getVertices(String key, Iterable<?> values);

/**
* Get a CloseableIterable of vertices with the given type, and the given
* key/value property.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;

/**
* Implementation of GraphManager that uses a single index to manage all nodes.
Expand Down Expand Up @@ -145,6 +146,16 @@ public Vertex getVertex(String id) throws ItemNotFound {
}
}

@Override
public Optional<Vertex> getVertex(String key, Object value) {
Preconditions.checkNotNull(key, "attempt to fetch vertex with a null property key");
try {
return Optional.of(graph.getVertices(key, value).iterator().next());
} catch (NoSuchElementException e) {
return Optional.empty();
}
}

@Override
public CloseableIterable<Vertex> getVertices(EntityClass type) {
return new WrappingCloseableIterable<>(
Expand All @@ -163,6 +174,13 @@ public CloseableIterable<Vertex> getVertices(Iterable<String> ids) {
return new WrappingCloseableIterable<>(verts);
}

@Override
public CloseableIterable<Vertex> getVertices(String key, Iterable<?> values) {
Iterable<Vertex> verts = Iterables
.transform(values, value -> getVertex(key, value).orElse(null));
return new WrappingCloseableIterable<>(verts);
}

@Override
public CloseableIterable<Vertex> getVertices(String key, Object value, EntityClass type) {
// NB: This is rather annoying.
Expand All @@ -178,10 +196,9 @@ public CloseableIterable<Vertex> getVertices(String key, Object value, EntityCla
@Override
public Vertex createVertex(String id, EntityClass type,
Map<String, ?> data) throws IntegrityError {
Preconditions
.checkNotNull(id, "null vertex ID given for item creation");
Preconditions.checkNotNull(id, "null vertex ID given for item creation");
Map<String, ?> indexData = getVertexData(id, type, data);
checkExists(id);
assertDoesNotExist(id);
Vertex node = graph.addVertex(null);
for (Map.Entry<String, ?> entry : indexData.entrySet()) {
if (entry.getValue() == null)
Expand Down Expand Up @@ -262,9 +279,9 @@ private <E extends Element> void addProperties(E item, Map<String, ?> data) {
}
}

private void checkExists(String id) throws IntegrityError {
private void assertDoesNotExist(String id) throws IntegrityError {
if (exists(id)) {
throw new IntegrityError(id);
throw new IntegrityError("Integrity error for id value: " + id);
}
}

Expand Down
Loading
Loading