From 1f99f9548b7dc9ffd04ebd035d3f482c2b702353 Mon Sep 17 00:00:00 2001 From: Tony Date: Fri, 26 Jan 2024 14:31:38 +0000 Subject: [PATCH 1/5] Checkpoint --- .../FunctionalProgrammingExample.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java diff --git a/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java new file mode 100644 index 00000000..9a74474b --- /dev/null +++ b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java @@ -0,0 +1,89 @@ +package uk.gov.gchq.magmacore.examples.functional; + +import java.util.function.Function; + +import org.junit.Test; + +import uk.gov.gchq.magmacore.service.MagmaCoreService; +import uk.gov.gchq.magmacore.service.MagmaCoreServiceFactory; + +/** + * A Functional Programming example for using MagmaCore. + */ +public class FunctionalProgrammingExample { + + private Function beginWriteTransaction = db -> { + db.service.beginWrite(); + return db; + }; + + private Function commitTransaction = db -> { + db.service.commit(); + return db; + }; + + @Test + public void test() { + + final String ttl = + new DatabaseCreator() + .andThen(beginWriteTransaction) + .andThen(new RefDataFinder()) + .andThen(createPerson) + .andThen(createResearchActivity) + .andThen(addPersonAsParticipantInActivity) + .andThen(exportTtl) + .andThen(commitTransaction) + .apply(Void.instance); + + } + + private static class Void { + public static Void instance = new Void(); + + private Void() {} + } + + private static class DatabaseCreator implements Function { + + @Override + public Database apply(final Void v) { + return new Database(MagmaCoreServiceFactory.createWithJenaDatabase()); + } + } + + private static class Database { + + private MagmaCoreService service; + + public MagmaCoreService getService() { + return service; + } + + public Database(final MagmaCoreService service) { + this.service = service; + } + } + + private static class RefDataFinder implements Function { + + @Override + public RefData apply(final Database database) { + return new RefData(database); + } + + } + + private static class RefData { + private final Database database; + + public Database getDatabase() { + return database; + } + + public RefData(final Database database) { + this.database = database; + } + } +} + From aafe7b3f0834ad37f3c81320d44524522878e208 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 27 Jan 2024 15:54:18 +0000 Subject: [PATCH 2/5] Fleshed out the functional programmoing example. --- .../FunctionalProgrammingExample.java | 326 +++++++++++++++--- 1 file changed, 280 insertions(+), 46 deletions(-) diff --git a/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java index 9a74474b..26d7085a 100644 --- a/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java +++ b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java @@ -1,89 +1,323 @@ package uk.gov.gchq.magmacore.examples.functional; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.time.Instant; +import java.util.UUID; import java.util.function.Function; import org.junit.Test; +import uk.gov.gchq.magmacore.hqdm.model.Activity; +import uk.gov.gchq.magmacore.hqdm.model.ClassOfPerson; +import uk.gov.gchq.magmacore.hqdm.model.KindOfActivity; +import uk.gov.gchq.magmacore.hqdm.model.Person; +import uk.gov.gchq.magmacore.hqdm.model.PointInTime; +import uk.gov.gchq.magmacore.hqdm.model.Role; +import uk.gov.gchq.magmacore.hqdm.model.StateOfPerson; +import uk.gov.gchq.magmacore.hqdm.rdf.iri.HQDM; +import uk.gov.gchq.magmacore.hqdm.rdf.iri.IRI; +import uk.gov.gchq.magmacore.hqdm.rdf.iri.IriBase; +import uk.gov.gchq.magmacore.hqdm.rdf.iri.RDFS; +import uk.gov.gchq.magmacore.hqdm.services.ClassServices; +import uk.gov.gchq.magmacore.hqdm.services.SpatioTemporalExtentServices; import uk.gov.gchq.magmacore.service.MagmaCoreService; import uk.gov.gchq.magmacore.service.MagmaCoreServiceFactory; /** * A Functional Programming example for using MagmaCore. + * + *

+ * The purpose of this example is to try to write a program that is more readable than the other examples. + * The {@link #test() test} method shows a clear sequence of steps carried out by the use case, and it + * should be possible to write the steps as reusable and composable functions. + *

*/ public class FunctionalProgrammingExample { - private Function beginWriteTransaction = db -> { - db.service.beginWrite(); - return db; - }; + /** + * An IRI prefix for the test. + */ + private static final IriBase TEST_BASE = new IriBase("test", "http://example.com/test#"); - private Function commitTransaction = db -> { - db.service.commit(); - return db; - }; + /** + * A class name for collecting together persons who are reseaechers. + */ + private static final String RESEARCHERS_CLASS_ENTITY_NAME = "Researchers"; + + /** + * The name of a kind of activity. + */ + private static final String RESEARCH_ACTIVITY_KIND_ENTITY_NAME = "Research Activities"; + + /** + * The name of a role for participants of research activities. + */ + private static final String RESEARCHER_ROLE_ENTITY_NAME = "Researcher Role"; + /** + * A unit test showing how to use functional programming with MagmaCore. + */ @Test public void test() { - final String ttl = - new DatabaseCreator() + /* + * Use function composition to build up a program that we will run later. + * The program will use a Context object to keep track of entities that + * are created. All functions in the processing chain accept a Context, + * mutate it, then return it. Ideally a `record` would be used instead + * and Lombok could be used to add 'wither' methods so that the Context + * could be immutable. + */ + final Function program = + + /* + * First create a MagmaCoreService in the Context. + */ + createMagmaCoreService + + /* + * The first transaction will populate the Reference Data needed by this test. + * Normally such data will already exist in the database and this step will not + * be necessary. + */ .andThen(beginWriteTransaction) - .andThen(new RefDataFinder()) + .andThen(populateRefData) + .andThen(commitTransaction) + + /* + * Often a program will need to look up some existing entities for Reference + * Data needed by the use case. In this case they are stored in the Context. + */ + .andThen(beginReadTransaction) + .andThen(findRefData) + .andThen(commitTransaction) + + /* + * New entities can be created making use of the Reference Data. No transaction + * is needed since the entities will be persisted at the end. + */ .andThen(createPerson) .andThen(createResearchActivity) - .andThen(addPersonAsParticipantInActivity) - .andThen(exportTtl) + .andThen(createPersonAsParticipantInActivity) + + /* + * The last transaction persists all of the new entities. + */ + .andThen(beginWriteTransaction) + .andThen(creatEntities) .andThen(commitTransaction) - .apply(Void.instance); + + /* + * Export to TTL for this unit test. + */ + .andThen(exportTtl); - } + /* + * Now execute the program created above. + */ + final Context ctx = program.apply(new Context()); - private static class Void { - public static Void instance = new Void(); + /* + * Check that the results are as expected. + */ + assertNotNull(ctx); + assertNotNull(ctx.ttlResult); + assertTrue(ctx.ttlResult.length() > 0); - private Void() {} + // TODO: Do some more checks rather than dumping to stdout. + System.out.println(ctx.ttlResult); } - private static class DatabaseCreator implements Function { + /** + * A function to populate Reference Data for the test. + */ + private Function populateRefData = ctx -> { - @Override - public Database apply(final Void v) { - return new Database(MagmaCoreServiceFactory.createWithJenaDatabase()); - } - } + /* + * Create a Class of Person. + */ + final ClassOfPerson cop = ClassServices.createClassOfPerson(randomIri()); + cop.addValue(HQDM.ENTITY_NAME, RESEARCHERS_CLASS_ENTITY_NAME); + ctx.magmaCore.create(cop); + + /* + * Create a Kind of Activity. + */ + final KindOfActivity koa = ClassServices.createKindOfActivity(randomIri()); + koa.addValue(HQDM.ENTITY_NAME, RESEARCH_ACTIVITY_KIND_ENTITY_NAME); + ctx.magmaCore.create(koa); - private static class Database { + /* + * Create a Role. + */ + final Role role = ClassServices.createRole(randomIri()); + role.addValue(HQDM.ENTITY_NAME, RESEARCHER_ROLE_ENTITY_NAME); + ctx.magmaCore.create(role); - private MagmaCoreService service; + return ctx; + }; + + /** + * A class to hold the entities created and referenced by the use case. + * + *

+ * All fields are public for ease of access and to reduce clutter from + * adding getters and setters. Lombok could be used to generate them + * without adding clutter. + *

+ */ + private static class Context { + public MagmaCoreService magmaCore; + public String ttlResult; - public MagmaCoreService getService() { - return service; - } + // New entities to be created. + public Person person; + public Activity researchActivity; + public StateOfPerson stateOfPerson; + public PointInTime startOfResearch; - public Database(final MagmaCoreService service) { - this.service = service; - } + // Ref data items. + public ClassOfPerson researchersClass; + public KindOfActivity researchActivityKind; + public Role researchRole; } - private static class RefDataFinder implements Function { + /** + * A function to create the MagmaCoreService. In this case it is an in-memory + * database for the unit test. + */ + private static final Function createMagmaCoreService = ctx -> { + ctx.magmaCore = MagmaCoreServiceFactory.createWithJenaDatabase(); + return ctx; + }; - @Override - public RefData apply(final Database database) { - return new RefData(database); - } + /** + * A function to export the database as TTL at the end of the use case. + */ + private static final Function exportTtl = ctx -> { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final var buffer = new PrintStream(baos); - } + ctx.magmaCore.exportTtl(buffer); + + ctx.ttlResult = baos.toString(); + + return ctx; + }; - private static class RefData { - private final Database database; + /** + * A function to persist the new entities in the database. + */ + private static final Function creatEntities = ctx -> { + ctx.magmaCore.create(ctx.person); + ctx.magmaCore.create(ctx.researchActivity); + ctx.magmaCore.create(ctx.stateOfPerson); + ctx.magmaCore.create(ctx.startOfResearch); + return ctx; + }; - public Database getDatabase() { - return database; - } + /** + * A function to find the Reference Data required by this use case. + */ + private static final Function findRefData = ctx -> { + ctx.researchersClass = ctx.magmaCore.findByEntityName(RESEARCHERS_CLASS_ENTITY_NAME); + ctx.researchActivityKind = ctx.magmaCore.findByEntityName(RESEARCH_ACTIVITY_KIND_ENTITY_NAME); + ctx.researchRole = ctx.magmaCore.findByEntityName(RESEARCHER_ROLE_ENTITY_NAME); + return ctx; + }; - public RefData(final Database database) { - this.database = database; - } + /** + * A utility function to generate random IRI values. + */ + private static final IRI randomIri() { + return new IRI(TEST_BASE, UUID.randomUUID().toString()); } + + /** + * A function to create a Person. + */ + private static final Function createPerson = ctx -> { + + /* + * Create a Person. + */ + ctx.person = SpatioTemporalExtentServices + .createPerson(randomIri()); + ctx.person.addValue(HQDM.MEMBER_OF, ctx.researchersClass.getId()); + return ctx; + }; + + /** + * A function to create an Activity. + */ + private static final Function createResearchActivity = ctx -> { + /* + * Create a timestamp for use as the beginning of the axctivity. + */ + final String now = Instant.now().toString(); + + /* + * Create a PointInTime event. + */ + ctx.startOfResearch = SpatioTemporalExtentServices + .createPointInTime(randomIri()); + ctx.startOfResearch.addValue(HQDM.VALUE_, now); + + /* + * Create the Activity. + */ + ctx.researchActivity = SpatioTemporalExtentServices + .createActivity(randomIri()); + ctx.researchActivity.addValue(HQDM.MEMBER_OF_KIND, ctx.researchActivityKind.getId()); + ctx.researchActivity.addValue(HQDM.BEGINNING, ctx.startOfResearch.getId()); + return ctx; + }; + + /** + * A function to add a state of person as a participant in the research activity. + */ + private static final Function createPersonAsParticipantInActivity = ctx -> { + /* + * The state of person will be a participant in the research activity. + */ + ctx.stateOfPerson = SpatioTemporalExtentServices + .createStateOfPerson(randomIri()); + ctx.stateOfPerson.addValue(HQDM.TEMPORAL_PART_OF, ctx.person.getId()); + ctx.stateOfPerson.addValue(HQDM.PARTICIPANT_IN, ctx.researchActivity.getId()); + ctx.stateOfPerson.addValue(HQDM.MEMBER_OF_KIND, ctx.researchRole.getId()); + ctx.stateOfPerson.addValue(HQDM.BEGINNING, ctx.startOfResearch.getId()); + ctx.stateOfPerson.addValue(RDFS.RDF_TYPE, HQDM.PARTICIPANT); + + return ctx; + }; + + /** + * A function to begin a read transaction. + */ + private final Function beginReadTransaction = magmaCore -> { + magmaCore.magmaCore.beginWrite(); + return magmaCore; + }; + + /** + * A function to begin a write transaction. + */ + private final Function beginWriteTransaction = magmaCore -> { + magmaCore.magmaCore.beginWrite(); + return magmaCore; + }; + + /** + * A function to commit a transaction. + */ + private final Function commitTransaction = magmaCore -> { + magmaCore.magmaCore.commit(); + return magmaCore; + }; + } From b9d074f471bdab767234847b09f147e0a85c8205 Mon Sep 17 00:00:00 2001 From: Tony Date: Sat, 27 Jan 2024 16:14:44 +0000 Subject: [PATCH 3/5] Finished the functional programming example. --- .../FunctionalProgrammingExample.java | 163 ++++++++---------- 1 file changed, 69 insertions(+), 94 deletions(-) diff --git a/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java index 26d7085a..9a4b33a4 100644 --- a/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java +++ b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java @@ -1,10 +1,7 @@ package uk.gov.gchq.magmacore.examples.functional; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; import java.time.Instant; import java.util.UUID; import java.util.function.Function; @@ -38,26 +35,6 @@ */ public class FunctionalProgrammingExample { - /** - * An IRI prefix for the test. - */ - private static final IriBase TEST_BASE = new IriBase("test", "http://example.com/test#"); - - /** - * A class name for collecting together persons who are reseaechers. - */ - private static final String RESEARCHERS_CLASS_ENTITY_NAME = "Researchers"; - - /** - * The name of a kind of activity. - */ - private static final String RESEARCH_ACTIVITY_KIND_ENTITY_NAME = "Research Activities"; - - /** - * The name of a role for participants of research activities. - */ - private static final String RESEARCHER_ROLE_ENTITY_NAME = "Researcher Role"; - /** * A unit test showing how to use functional programming with MagmaCore. */ @@ -109,12 +86,7 @@ public void test() { */ .andThen(beginWriteTransaction) .andThen(creatEntities) - .andThen(commitTransaction) - - /* - * Export to TTL for this unit test. - */ - .andThen(exportTtl); + .andThen(commitTransaction); /* * Now execute the program created above. @@ -124,43 +96,12 @@ public void test() { /* * Check that the results are as expected. */ - assertNotNull(ctx); - assertNotNull(ctx.ttlResult); - assertTrue(ctx.ttlResult.length() > 0); - - // TODO: Do some more checks rather than dumping to stdout. - System.out.println(ctx.ttlResult); + assertNotNull(ctx.magmaCore.get(ctx.person.getId())); + assertNotNull(ctx.magmaCore.get(ctx.researchActivity.getId())); + assertNotNull(ctx.magmaCore.get(ctx.startOfResearch.getId())); + assertNotNull(ctx.magmaCore.get(ctx.stateOfPerson.getId())); } - /** - * A function to populate Reference Data for the test. - */ - private Function populateRefData = ctx -> { - - /* - * Create a Class of Person. - */ - final ClassOfPerson cop = ClassServices.createClassOfPerson(randomIri()); - cop.addValue(HQDM.ENTITY_NAME, RESEARCHERS_CLASS_ENTITY_NAME); - ctx.magmaCore.create(cop); - - /* - * Create a Kind of Activity. - */ - final KindOfActivity koa = ClassServices.createKindOfActivity(randomIri()); - koa.addValue(HQDM.ENTITY_NAME, RESEARCH_ACTIVITY_KIND_ENTITY_NAME); - ctx.magmaCore.create(koa); - - /* - * Create a Role. - */ - final Role role = ClassServices.createRole(randomIri()); - role.addValue(HQDM.ENTITY_NAME, RESEARCHER_ROLE_ENTITY_NAME); - ctx.magmaCore.create(role); - - return ctx; - }; - /** * A class to hold the entities created and referenced by the use case. * @@ -172,13 +113,12 @@ public void test() { */ private static class Context { public MagmaCoreService magmaCore; - public String ttlResult; // New entities to be created. - public Person person; public Activity researchActivity; - public StateOfPerson stateOfPerson; + public Person person; public PointInTime startOfResearch; + public StateOfPerson stateOfPerson; // Ref data items. public ClassOfPerson researchersClass; @@ -186,6 +126,26 @@ private static class Context { public Role researchRole; } + /** + * An IRI prefix for the test. + */ + private static final IriBase TEST_BASE = new IriBase("test", "http://example.com/test#"); + + /** + * A class name for collecting together persons who are reseaechers. + */ + private static final String RESEARCHERS_CLASS_ENTITY_NAME = "Researchers"; + + /** + * The name of a kind of activity. + */ + private static final String RESEARCH_ACTIVITY_KIND_ENTITY_NAME = "Research Activities"; + + /** + * The name of a role for participants of research activities. + */ + private static final String RESEARCHER_ROLE_ENTITY_NAME = "Researcher Role"; + /** * A function to create the MagmaCoreService. In this case it is an in-memory * database for the unit test. @@ -195,28 +155,14 @@ private static class Context { return ctx; }; - /** - * A function to export the database as TTL at the end of the use case. - */ - private static final Function exportTtl = ctx -> { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - final var buffer = new PrintStream(baos); - - ctx.magmaCore.exportTtl(buffer); - - ctx.ttlResult = baos.toString(); - - return ctx; - }; - /** * A function to persist the new entities in the database. */ private static final Function creatEntities = ctx -> { ctx.magmaCore.create(ctx.person); ctx.magmaCore.create(ctx.researchActivity); - ctx.magmaCore.create(ctx.stateOfPerson); ctx.magmaCore.create(ctx.startOfResearch); + ctx.magmaCore.create(ctx.stateOfPerson); return ctx; }; @@ -224,19 +170,12 @@ private static class Context { * A function to find the Reference Data required by this use case. */ private static final Function findRefData = ctx -> { - ctx.researchersClass = ctx.magmaCore.findByEntityName(RESEARCHERS_CLASS_ENTITY_NAME); ctx.researchActivityKind = ctx.magmaCore.findByEntityName(RESEARCH_ACTIVITY_KIND_ENTITY_NAME); ctx.researchRole = ctx.magmaCore.findByEntityName(RESEARCHER_ROLE_ENTITY_NAME); + ctx.researchersClass = ctx.magmaCore.findByEntityName(RESEARCHERS_CLASS_ENTITY_NAME); return ctx; }; - /** - * A utility function to generate random IRI values. - */ - private static final IRI randomIri() { - return new IRI(TEST_BASE, UUID.randomUUID().toString()); - } - /** * A function to create a Person. */ @@ -272,8 +211,8 @@ private static final IRI randomIri() { */ ctx.researchActivity = SpatioTemporalExtentServices .createActivity(randomIri()); - ctx.researchActivity.addValue(HQDM.MEMBER_OF_KIND, ctx.researchActivityKind.getId()); ctx.researchActivity.addValue(HQDM.BEGINNING, ctx.startOfResearch.getId()); + ctx.researchActivity.addValue(HQDM.MEMBER_OF_KIND, ctx.researchActivityKind.getId()); return ctx; }; @@ -286,15 +225,51 @@ private static final IRI randomIri() { */ ctx.stateOfPerson = SpatioTemporalExtentServices .createStateOfPerson(randomIri()); - ctx.stateOfPerson.addValue(HQDM.TEMPORAL_PART_OF, ctx.person.getId()); - ctx.stateOfPerson.addValue(HQDM.PARTICIPANT_IN, ctx.researchActivity.getId()); - ctx.stateOfPerson.addValue(HQDM.MEMBER_OF_KIND, ctx.researchRole.getId()); ctx.stateOfPerson.addValue(HQDM.BEGINNING, ctx.startOfResearch.getId()); + ctx.stateOfPerson.addValue(HQDM.MEMBER_OF_KIND, ctx.researchRole.getId()); + ctx.stateOfPerson.addValue(HQDM.PARTICIPANT_IN, ctx.researchActivity.getId()); + ctx.stateOfPerson.addValue(HQDM.TEMPORAL_PART_OF, ctx.person.getId()); ctx.stateOfPerson.addValue(RDFS.RDF_TYPE, HQDM.PARTICIPANT); return ctx; }; + /** + * A utility function to generate random IRI values. + */ + private static final IRI randomIri() { + return new IRI(TEST_BASE, UUID.randomUUID().toString()); + } + + /** + * A function to populate Reference Data for the test. + */ + private Function populateRefData = ctx -> { + + /* + * Create a Class of Person. + */ + final ClassOfPerson cop = ClassServices.createClassOfPerson(randomIri()); + cop.addValue(HQDM.ENTITY_NAME, RESEARCHERS_CLASS_ENTITY_NAME); + ctx.magmaCore.create(cop); + + /* + * Create a Kind of Activity. + */ + final KindOfActivity koa = ClassServices.createKindOfActivity(randomIri()); + koa.addValue(HQDM.ENTITY_NAME, RESEARCH_ACTIVITY_KIND_ENTITY_NAME); + ctx.magmaCore.create(koa); + + /* + * Create a Role. + */ + final Role role = ClassServices.createRole(randomIri()); + role.addValue(HQDM.ENTITY_NAME, RESEARCHER_ROLE_ENTITY_NAME); + ctx.magmaCore.create(role); + + return ctx; + }; + /** * A function to begin a read transaction. */ From 34dc9f692da49a70c4fde0853d26af27d5b7d613 Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 13 Feb 2024 09:53:30 +0000 Subject: [PATCH 4/5] Fixed some review issues. --- .../FunctionalProgrammingExample.java | 298 ------------------ 1 file changed, 298 deletions(-) delete mode 100644 examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java diff --git a/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java deleted file mode 100644 index 9a4b33a4..00000000 --- a/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java +++ /dev/null @@ -1,298 +0,0 @@ -package uk.gov.gchq.magmacore.examples.functional; - -import static org.junit.Assert.assertNotNull; - -import java.time.Instant; -import java.util.UUID; -import java.util.function.Function; - -import org.junit.Test; - -import uk.gov.gchq.magmacore.hqdm.model.Activity; -import uk.gov.gchq.magmacore.hqdm.model.ClassOfPerson; -import uk.gov.gchq.magmacore.hqdm.model.KindOfActivity; -import uk.gov.gchq.magmacore.hqdm.model.Person; -import uk.gov.gchq.magmacore.hqdm.model.PointInTime; -import uk.gov.gchq.magmacore.hqdm.model.Role; -import uk.gov.gchq.magmacore.hqdm.model.StateOfPerson; -import uk.gov.gchq.magmacore.hqdm.rdf.iri.HQDM; -import uk.gov.gchq.magmacore.hqdm.rdf.iri.IRI; -import uk.gov.gchq.magmacore.hqdm.rdf.iri.IriBase; -import uk.gov.gchq.magmacore.hqdm.rdf.iri.RDFS; -import uk.gov.gchq.magmacore.hqdm.services.ClassServices; -import uk.gov.gchq.magmacore.hqdm.services.SpatioTemporalExtentServices; -import uk.gov.gchq.magmacore.service.MagmaCoreService; -import uk.gov.gchq.magmacore.service.MagmaCoreServiceFactory; - -/** - * A Functional Programming example for using MagmaCore. - * - *

- * The purpose of this example is to try to write a program that is more readable than the other examples. - * The {@link #test() test} method shows a clear sequence of steps carried out by the use case, and it - * should be possible to write the steps as reusable and composable functions. - *

- */ -public class FunctionalProgrammingExample { - - /** - * A unit test showing how to use functional programming with MagmaCore. - */ - @Test - public void test() { - - /* - * Use function composition to build up a program that we will run later. - * The program will use a Context object to keep track of entities that - * are created. All functions in the processing chain accept a Context, - * mutate it, then return it. Ideally a `record` would be used instead - * and Lombok could be used to add 'wither' methods so that the Context - * could be immutable. - */ - final Function program = - - /* - * First create a MagmaCoreService in the Context. - */ - createMagmaCoreService - - /* - * The first transaction will populate the Reference Data needed by this test. - * Normally such data will already exist in the database and this step will not - * be necessary. - */ - .andThen(beginWriteTransaction) - .andThen(populateRefData) - .andThen(commitTransaction) - - /* - * Often a program will need to look up some existing entities for Reference - * Data needed by the use case. In this case they are stored in the Context. - */ - .andThen(beginReadTransaction) - .andThen(findRefData) - .andThen(commitTransaction) - - /* - * New entities can be created making use of the Reference Data. No transaction - * is needed since the entities will be persisted at the end. - */ - .andThen(createPerson) - .andThen(createResearchActivity) - .andThen(createPersonAsParticipantInActivity) - - /* - * The last transaction persists all of the new entities. - */ - .andThen(beginWriteTransaction) - .andThen(creatEntities) - .andThen(commitTransaction); - - /* - * Now execute the program created above. - */ - final Context ctx = program.apply(new Context()); - - /* - * Check that the results are as expected. - */ - assertNotNull(ctx.magmaCore.get(ctx.person.getId())); - assertNotNull(ctx.magmaCore.get(ctx.researchActivity.getId())); - assertNotNull(ctx.magmaCore.get(ctx.startOfResearch.getId())); - assertNotNull(ctx.magmaCore.get(ctx.stateOfPerson.getId())); - } - - /** - * A class to hold the entities created and referenced by the use case. - * - *

- * All fields are public for ease of access and to reduce clutter from - * adding getters and setters. Lombok could be used to generate them - * without adding clutter. - *

- */ - private static class Context { - public MagmaCoreService magmaCore; - - // New entities to be created. - public Activity researchActivity; - public Person person; - public PointInTime startOfResearch; - public StateOfPerson stateOfPerson; - - // Ref data items. - public ClassOfPerson researchersClass; - public KindOfActivity researchActivityKind; - public Role researchRole; - } - - /** - * An IRI prefix for the test. - */ - private static final IriBase TEST_BASE = new IriBase("test", "http://example.com/test#"); - - /** - * A class name for collecting together persons who are reseaechers. - */ - private static final String RESEARCHERS_CLASS_ENTITY_NAME = "Researchers"; - - /** - * The name of a kind of activity. - */ - private static final String RESEARCH_ACTIVITY_KIND_ENTITY_NAME = "Research Activities"; - - /** - * The name of a role for participants of research activities. - */ - private static final String RESEARCHER_ROLE_ENTITY_NAME = "Researcher Role"; - - /** - * A function to create the MagmaCoreService. In this case it is an in-memory - * database for the unit test. - */ - private static final Function createMagmaCoreService = ctx -> { - ctx.magmaCore = MagmaCoreServiceFactory.createWithJenaDatabase(); - return ctx; - }; - - /** - * A function to persist the new entities in the database. - */ - private static final Function creatEntities = ctx -> { - ctx.magmaCore.create(ctx.person); - ctx.magmaCore.create(ctx.researchActivity); - ctx.magmaCore.create(ctx.startOfResearch); - ctx.magmaCore.create(ctx.stateOfPerson); - return ctx; - }; - - /** - * A function to find the Reference Data required by this use case. - */ - private static final Function findRefData = ctx -> { - ctx.researchActivityKind = ctx.magmaCore.findByEntityName(RESEARCH_ACTIVITY_KIND_ENTITY_NAME); - ctx.researchRole = ctx.magmaCore.findByEntityName(RESEARCHER_ROLE_ENTITY_NAME); - ctx.researchersClass = ctx.magmaCore.findByEntityName(RESEARCHERS_CLASS_ENTITY_NAME); - return ctx; - }; - - /** - * A function to create a Person. - */ - private static final Function createPerson = ctx -> { - - /* - * Create a Person. - */ - ctx.person = SpatioTemporalExtentServices - .createPerson(randomIri()); - ctx.person.addValue(HQDM.MEMBER_OF, ctx.researchersClass.getId()); - return ctx; - }; - - /** - * A function to create an Activity. - */ - private static final Function createResearchActivity = ctx -> { - /* - * Create a timestamp for use as the beginning of the axctivity. - */ - final String now = Instant.now().toString(); - - /* - * Create a PointInTime event. - */ - ctx.startOfResearch = SpatioTemporalExtentServices - .createPointInTime(randomIri()); - ctx.startOfResearch.addValue(HQDM.VALUE_, now); - - /* - * Create the Activity. - */ - ctx.researchActivity = SpatioTemporalExtentServices - .createActivity(randomIri()); - ctx.researchActivity.addValue(HQDM.BEGINNING, ctx.startOfResearch.getId()); - ctx.researchActivity.addValue(HQDM.MEMBER_OF_KIND, ctx.researchActivityKind.getId()); - return ctx; - }; - - /** - * A function to add a state of person as a participant in the research activity. - */ - private static final Function createPersonAsParticipantInActivity = ctx -> { - /* - * The state of person will be a participant in the research activity. - */ - ctx.stateOfPerson = SpatioTemporalExtentServices - .createStateOfPerson(randomIri()); - ctx.stateOfPerson.addValue(HQDM.BEGINNING, ctx.startOfResearch.getId()); - ctx.stateOfPerson.addValue(HQDM.MEMBER_OF_KIND, ctx.researchRole.getId()); - ctx.stateOfPerson.addValue(HQDM.PARTICIPANT_IN, ctx.researchActivity.getId()); - ctx.stateOfPerson.addValue(HQDM.TEMPORAL_PART_OF, ctx.person.getId()); - ctx.stateOfPerson.addValue(RDFS.RDF_TYPE, HQDM.PARTICIPANT); - - return ctx; - }; - - /** - * A utility function to generate random IRI values. - */ - private static final IRI randomIri() { - return new IRI(TEST_BASE, UUID.randomUUID().toString()); - } - - /** - * A function to populate Reference Data for the test. - */ - private Function populateRefData = ctx -> { - - /* - * Create a Class of Person. - */ - final ClassOfPerson cop = ClassServices.createClassOfPerson(randomIri()); - cop.addValue(HQDM.ENTITY_NAME, RESEARCHERS_CLASS_ENTITY_NAME); - ctx.magmaCore.create(cop); - - /* - * Create a Kind of Activity. - */ - final KindOfActivity koa = ClassServices.createKindOfActivity(randomIri()); - koa.addValue(HQDM.ENTITY_NAME, RESEARCH_ACTIVITY_KIND_ENTITY_NAME); - ctx.magmaCore.create(koa); - - /* - * Create a Role. - */ - final Role role = ClassServices.createRole(randomIri()); - role.addValue(HQDM.ENTITY_NAME, RESEARCHER_ROLE_ENTITY_NAME); - ctx.magmaCore.create(role); - - return ctx; - }; - - /** - * A function to begin a read transaction. - */ - private final Function beginReadTransaction = magmaCore -> { - magmaCore.magmaCore.beginWrite(); - return magmaCore; - }; - - /** - * A function to begin a write transaction. - */ - private final Function beginWriteTransaction = magmaCore -> { - magmaCore.magmaCore.beginWrite(); - return magmaCore; - }; - - /** - * A function to commit a transaction. - */ - private final Function commitTransaction = magmaCore -> { - magmaCore.magmaCore.commit(); - return magmaCore; - }; - -} - From 5f038eec0b4fe626669a78df892ca7231716b224 Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 13 Feb 2024 09:57:48 +0000 Subject: [PATCH 5/5] Fixed some review issues. --- .../FunctionalProgrammingExample.java | 301 ++++++++++++++++++ .../FunctionalProgrammingExampleTest.java | 18 ++ 2 files changed, 319 insertions(+) create mode 100644 examples/src/main/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java create mode 100644 examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExampleTest.java diff --git a/examples/src/main/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java b/examples/src/main/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java new file mode 100644 index 00000000..fbe42d1c --- /dev/null +++ b/examples/src/main/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExample.java @@ -0,0 +1,301 @@ +package uk.gov.gchq.magmacore.examples.functional; + +import java.time.Instant; +import java.util.UUID; +import java.util.function.Function; + +import uk.gov.gchq.magmacore.hqdm.model.Activity; +import uk.gov.gchq.magmacore.hqdm.model.ClassOfPerson; +import uk.gov.gchq.magmacore.hqdm.model.KindOfActivity; +import uk.gov.gchq.magmacore.hqdm.model.Person; +import uk.gov.gchq.magmacore.hqdm.model.PointInTime; +import uk.gov.gchq.magmacore.hqdm.model.Role; +import uk.gov.gchq.magmacore.hqdm.model.StateOfPerson; +import uk.gov.gchq.magmacore.hqdm.rdf.iri.HQDM; +import uk.gov.gchq.magmacore.hqdm.rdf.iri.IRI; +import uk.gov.gchq.magmacore.hqdm.rdf.iri.IriBase; +import uk.gov.gchq.magmacore.hqdm.rdf.iri.RDFS; +import uk.gov.gchq.magmacore.hqdm.services.ClassServices; +import uk.gov.gchq.magmacore.hqdm.services.SpatioTemporalExtentServices; +import uk.gov.gchq.magmacore.service.MagmaCoreService; +import uk.gov.gchq.magmacore.service.MagmaCoreServiceFactory; + +/** + * A Functional Programming example for using MagmaCore. + * + *

+ * The purpose of this example is to try to write a program that is more readable than the other examples. + * The {@link #main() main} method shows a clear sequence of steps carried out by the use case, and it + * should be possible to write the steps as reusable and composable functions. + *

+ */ +public class FunctionalProgrammingExample { + + /** + * A program showing how to use functional programming with MagmaCore. + */ + public static void main(final String[] args) { + + /* + * Use function composition to build up a program that we will run later. + * The program will use a Context object to keep track of entities that + * are created. All functions in the processing chain accept a Context, + * mutate it, then return it. Ideally a `record` would be used instead + * and Lombok could be used to add 'wither' methods so that the Context + * could be immutable. + */ + final Function program = + + /* + * First create a MagmaCoreService in the Context. + */ + createMagmaCoreService + + /* + * The first transaction will populate the Reference Data needed by this example. + * Normally such data will already exist in the database and this step will not + * be necessary. + */ + .andThen(beginWriteTransaction) + .andThen(populateRefData) + .andThen(commitTransaction) + + /* + * Often a program will need to look up some existing entities for Reference + * Data needed by the use case. In this case they are stored in the Context. + */ + .andThen(beginReadTransaction) + .andThen(findRefData) + .andThen(commitTransaction) + + /* + * New entities can be created making use of the Reference Data. No transaction + * is needed since the entities will be persisted at the end. + */ + .andThen(createPerson) + .andThen(createResearchActivity) + .andThen(createPersonAsParticipantInActivity) + + /* + * The last transaction persists all of the new entities. + */ + .andThen(beginWriteTransaction) + .andThen(creatEntities) + .andThen(commitTransaction); + + /* + * Now execute the program created above. + */ + final Context ctx = program.apply(new Context()); + + /* + * Check that the results are as expected. + */ + if (ctx.magmaCore.get(ctx.person.getId()) == null) { + System.err.println("Cannot find person object with IRI: " + ctx.person.getId()); + } + if (ctx.magmaCore.get(ctx.researchActivity.getId()) == null) { + System.err.println("Cannot find researchActivity object with IRI: " + ctx.researchActivity.getId()); + } + if (ctx.magmaCore.get(ctx.startOfResearch.getId()) == null) { + System.err.println("Cannot find startOfResearch object with IRI: " + ctx.startOfResearch.getId()); + } + if (ctx.magmaCore.get(ctx.stateOfPerson.getId()) == null) { + System.err.println("Cannot find stateOfPerson object with IRI: " + ctx.stateOfPerson.getId()); + } + } + + /** + * A class to hold the entities created and referenced by the use case. + * + *

+ * All fields are public for ease of access and to reduce clutter from + * adding getters and setters. Lombok could be used to generate them + * without adding clutter. + *

+ */ + private static class Context { + public MagmaCoreService magmaCore; + + // New entities to be created. + public Activity researchActivity; + public Person person; + public PointInTime startOfResearch; + public StateOfPerson stateOfPerson; + + // Ref data items. + public ClassOfPerson researchersClass; + public KindOfActivity researchActivityKind; + public Role researchRole; + } + + /** + * An IRI prefix for the example. + */ + private static final IriBase TEST_BASE = new IriBase("test", "http://example.com/test#"); + + /** + * A class name for collecting together persons who are reseaechers. + */ + private static final String RESEARCHERS_CLASS_ENTITY_NAME = "Researchers"; + + /** + * The name of a kind of activity. + */ + private static final String RESEARCH_ACTIVITY_KIND_ENTITY_NAME = "Research Activities"; + + /** + * The name of a role for participants of research activities. + */ + private static final String RESEARCHER_ROLE_ENTITY_NAME = "Researcher Role"; + + /** + * A function to create the MagmaCoreService. In this case it is an in-memory + * database for the example. + */ + private static final Function createMagmaCoreService = ctx -> { + ctx.magmaCore = MagmaCoreServiceFactory.createWithJenaDatabase(); + return ctx; + }; + + /** + * A function to persist the new entities in the database. + */ + private static final Function creatEntities = ctx -> { + ctx.magmaCore.create(ctx.person); + ctx.magmaCore.create(ctx.researchActivity); + ctx.magmaCore.create(ctx.startOfResearch); + ctx.magmaCore.create(ctx.stateOfPerson); + return ctx; + }; + + /** + * A function to find the Reference Data required by this use case. + */ + private static final Function findRefData = ctx -> { + ctx.researchActivityKind = ctx.magmaCore.findByEntityName(RESEARCH_ACTIVITY_KIND_ENTITY_NAME); + ctx.researchRole = ctx.magmaCore.findByEntityName(RESEARCHER_ROLE_ENTITY_NAME); + ctx.researchersClass = ctx.magmaCore.findByEntityName(RESEARCHERS_CLASS_ENTITY_NAME); + return ctx; + }; + + /** + * A function to create a Person. + */ + private static final Function createPerson = ctx -> { + + /* + * Create a Person. + */ + ctx.person = SpatioTemporalExtentServices + .createPerson(randomIri()); + ctx.person.addValue(HQDM.MEMBER_OF, ctx.researchersClass.getId()); + return ctx; + }; + + /** + * A function to create an Activity. + */ + private static final Function createResearchActivity = ctx -> { + /* + * Create a timestamp for use as the beginning of the axctivity. + */ + final String now = Instant.now().toString(); + + /* + * Create a PointInTime event. + */ + ctx.startOfResearch = SpatioTemporalExtentServices + .createPointInTime(randomIri()); + ctx.startOfResearch.addValue(HQDM.VALUE_, now); + + /* + * Create the Activity. + */ + ctx.researchActivity = SpatioTemporalExtentServices + .createActivity(randomIri()); + ctx.researchActivity.addValue(HQDM.BEGINNING, ctx.startOfResearch.getId()); + ctx.researchActivity.addValue(HQDM.MEMBER_OF_KIND, ctx.researchActivityKind.getId()); + return ctx; + }; + + /** + * A function to add a state of person as a participant in the research activity. + */ + private static final Function createPersonAsParticipantInActivity = ctx -> { + /* + * The state of person will be a participant in the research activity. + */ + ctx.stateOfPerson = SpatioTemporalExtentServices + .createStateOfPerson(randomIri()); + ctx.stateOfPerson.addValue(HQDM.BEGINNING, ctx.startOfResearch.getId()); + ctx.stateOfPerson.addValue(HQDM.MEMBER_OF_KIND, ctx.researchRole.getId()); + ctx.stateOfPerson.addValue(HQDM.PARTICIPANT_IN, ctx.researchActivity.getId()); + ctx.stateOfPerson.addValue(HQDM.TEMPORAL_PART_OF, ctx.person.getId()); + ctx.stateOfPerson.addValue(RDFS.RDF_TYPE, HQDM.PARTICIPANT); + + return ctx; + }; + + /** + * A utility function to generate random IRI values. + */ + private static final IRI randomIri() { + return new IRI(TEST_BASE, UUID.randomUUID().toString()); + } + + /** + * A function to populate Reference Data for the example. + */ + private static Function populateRefData = ctx -> { + + /* + * Create a Class of Person. + */ + final ClassOfPerson cop = ClassServices.createClassOfPerson(randomIri()); + cop.addValue(HQDM.ENTITY_NAME, RESEARCHERS_CLASS_ENTITY_NAME); + ctx.magmaCore.create(cop); + + /* + * Create a Kind of Activity. + */ + final KindOfActivity koa = ClassServices.createKindOfActivity(randomIri()); + koa.addValue(HQDM.ENTITY_NAME, RESEARCH_ACTIVITY_KIND_ENTITY_NAME); + ctx.magmaCore.create(koa); + + /* + * Create a Role. + */ + final Role role = ClassServices.createRole(randomIri()); + role.addValue(HQDM.ENTITY_NAME, RESEARCHER_ROLE_ENTITY_NAME); + ctx.magmaCore.create(role); + + return ctx; + }; + + /** + * A function to begin a read transaction. + */ + private static final Function beginReadTransaction = magmaCore -> { + magmaCore.magmaCore.beginWrite(); + return magmaCore; + }; + + /** + * A function to begin a write transaction. + */ + private static final Function beginWriteTransaction = magmaCore -> { + magmaCore.magmaCore.beginWrite(); + return magmaCore; + }; + + /** + * A function to commit a transaction. + */ + private static final Function commitTransaction = magmaCore -> { + magmaCore.magmaCore.commit(); + return magmaCore; + }; + +} + diff --git a/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExampleTest.java b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExampleTest.java new file mode 100644 index 00000000..41833990 --- /dev/null +++ b/examples/src/test/java/uk/gov/gchq/magmacore/examples/functional/FunctionalProgrammingExampleTest.java @@ -0,0 +1,18 @@ +package uk.gov.gchq.magmacore.examples.functional; + +import org.junit.Test; + +/** + * Execute the Functional Programming example for using MagmaCore. + */ +public class FunctionalProgrammingExampleTest { + + /** + * A unit test to ensure that the FunctionalProgrammingExample code is executed as part of a build. + */ + @Test + public void test() { + FunctionalProgrammingExample.main(null); + } +} +