diff --git a/src/test/resources/test_agent_body_sub.ttl b/src/test/resources/test_agent_body_sub.ttl index e210758f..3922063c 100644 --- a/src/test/resources/test_agent_body_sub.ttl +++ b/src/test/resources/test_agent_body_sub.ttl @@ -7,11 +7,14 @@ @prefix js: . @prefix saref: . - a td:Thing, hmas:Artifact; + a td:Thing, , + hmas:Artifact; td:title "test_agent"; td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme - ] . - - hmas:isContainedIn . + ]; + hmas:isContainedIn ; + . a hmas:Workspace . + + a hmas:Agent . diff --git a/src/test/resources/test_agent_body_test.ttl b/src/test/resources/test_agent_body_test.ttl index e9b00dd9..fa6d1789 100644 --- a/src/test/resources/test_agent_body_test.ttl +++ b/src/test/resources/test_agent_body_test.ttl @@ -7,11 +7,14 @@ @prefix js: . @prefix saref: . - a td:Thing, hmas:Artifact; + a td:Thing, , + hmas:Artifact; td:title "test_agent"; td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme - ] . - - hmas:isContainedIn . + ]; + hmas:isContainedIn ; + . a hmas:Workspace . + + a hmas:Agent . diff --git a/yggdrasil-cartago/src/test/resources/test_agent_body.ttl b/yggdrasil-cartago/src/test/resources/test_agent_body.ttl index b43800e3..ad3b2a99 100644 --- a/yggdrasil-cartago/src/test/resources/test_agent_body.ttl +++ b/yggdrasil-cartago/src/test/resources/test_agent_body.ttl @@ -6,7 +6,8 @@ @prefix js: . @prefix hmas: . - a td:Thing, hmas:Artifact; + a td:Thing, , + hmas:Artifact; td:title "test"; td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] . diff --git a/yggdrasil-core/src/main/java/org/hyperagents/yggdrasil/http/HttpEntityHandler.java b/yggdrasil-core/src/main/java/org/hyperagents/yggdrasil/http/HttpEntityHandler.java index 96686c37..d7287b10 100644 --- a/yggdrasil-core/src/main/java/org/hyperagents/yggdrasil/http/HttpEntityHandler.java +++ b/yggdrasil-core/src/main/java/org/hyperagents/yggdrasil/http/HttpEntityHandler.java @@ -313,8 +313,8 @@ public void handleJoinWorkspace(final RoutingContext routingContext) { )) .compose(response -> this.rdfStoreMessagebox - .sendMessage(new RdfStoreMessage.CreateArtifact( - this.httpConfig.getAgentBodiesUri(workspaceName) + "/", + .sendMessage(new RdfStoreMessage.CreateBody( + workspaceName, this.getAgentNameFromId(agentId), response.body() )) diff --git a/yggdrasil-core/src/main/java/org/hyperagents/yggdrasil/store/RdfStoreVerticle.java b/yggdrasil-core/src/main/java/org/hyperagents/yggdrasil/store/RdfStoreVerticle.java index 219393f7..54b9e14b 100644 --- a/yggdrasil-core/src/main/java/org/hyperagents/yggdrasil/store/RdfStoreVerticle.java +++ b/yggdrasil-core/src/main/java/org/hyperagents/yggdrasil/store/RdfStoreVerticle.java @@ -10,13 +10,13 @@ import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.regex.Pattern; import java.util.stream.Stream; import org.apache.commons.lang3.function.Failable; import org.apache.http.HttpStatus; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.rio.RDFFormat; import org.hyperagents.yggdrasil.eventbus.messageboxes.HttpNotificationDispatcherMessagebox; @@ -25,6 +25,7 @@ import org.hyperagents.yggdrasil.eventbus.messages.HttpNotificationDispatcherMessage; import org.hyperagents.yggdrasil.eventbus.messages.RdfStoreMessage; import org.hyperagents.yggdrasil.store.impl.RdfStoreFactory; +import org.hyperagents.yggdrasil.utils.HttpInterfaceConfig; import org.hyperagents.yggdrasil.utils.JsonObjectUtils; import org.hyperagents.yggdrasil.utils.RdfModelUtils; import org.hyperagents.yggdrasil.utils.impl.HttpInterfaceConfigImpl; @@ -39,12 +40,13 @@ public class RdfStoreVerticle extends AbstractVerticle { private static final String CONTAINS_HMAS_IRI = "https://purl.org/hmas/core/contains"; private Messagebox dispatcherMessagebox; + private HttpInterfaceConfig httpConfig; private RdfStore store; @SuppressWarnings("PMD.SwitchStmtsShouldHaveDefault") @Override public void start(final Promise startPromise) { - final var httpConfig = new HttpInterfaceConfigImpl(this.config()); + this.httpConfig = new HttpInterfaceConfigImpl(this.config()); this.dispatcherMessagebox = new HttpNotificationDispatcherMessagebox(this.vertx.eventBus()); final var ownMessagebox = new RdfStoreMessagebox(this.vertx.eventBus()); ownMessagebox.init(); @@ -80,6 +82,7 @@ public void start(final Promise startPromise) { String responseContentType ) -> this.handleQuery(query, defaultGraphUris, namedGraphUris, responseContentType, message); + case RdfStoreMessage.CreateBody content -> this.handleCreateBody(content, message); } } catch (final IllegalArgumentException e) { LOGGER.error(e); @@ -111,11 +114,11 @@ public void start(final Promise startPromise) { } })) .orElse(RdfStoreFactory.createInMemoryStore()); - final var platformIri = RdfModelUtils.createIri(httpConfig.getBaseUri() + "/"); + final var platformIri = RdfModelUtils.createIri(this.httpConfig.getBaseUri() + "/"); this.store.addEntityModel( platformIri, RdfModelUtils.stringToModel( - new RepresentationFactoryImpl(httpConfig).createPlatformRepresentation(), + new RepresentationFactoryImpl(this.httpConfig).createPlatformRepresentation(), platformIri, RDFFormat.TURTLE ) @@ -147,11 +150,58 @@ private void handleGetEntity( } } + /** + * Creates a body artifact and adds it to the store. + */ + private void handleCreateBody( + final RdfStoreMessage.CreateBody content, + final Message message + ) throws IOException { + final var bodyIri = this.httpConfig.getAgentBodyUri( + content.workspaceName(), + content.agentName() + ); + final var entityIri = RdfModelUtils.createIri(bodyIri); + Optional + .ofNullable(content.bodyRepresentation()) + .filter(s -> !s.isEmpty()) + // Replace all null relative IRIs with the IRI generated for this entity + .map(s -> s.replaceAll("<>", "<" + bodyIri + ">")) + .ifPresentOrElse( + Failable.asConsumer(s -> { + final var entityModel = RdfModelUtils.stringToModel(s, entityIri, RDFFormat.TURTLE); + final var workspaceIri = + RdfModelUtils.createIri(this.httpConfig.getWorkspaceUri(content.workspaceName())); + this.enrichArtifactGraphWithWorkspace(entityIri, entityModel, workspaceIri); + final var agentIri = + RdfModelUtils.createIri(this.httpConfig.getAgentUri(content.agentName())); + entityModel.add( + entityIri, + RdfModelUtils.createIri("https://example.org/isBodyOf"), + agentIri + ); + entityModel.add( + agentIri, + RDF.TYPE, + RdfModelUtils.createIri("https://purl.org/hmas/core/Agent") + ); + this.store.addEntityModel(entityIri, entityModel); + final var stringGraphResult = + RdfModelUtils.modelToString(entityModel, RDFFormat.TURTLE); + this.dispatcherMessagebox.sendMessage( + new HttpNotificationDispatcherMessage.EntityCreated( + this.httpConfig.getAgentBodiesUri(content.workspaceName()) + "/", + stringGraphResult + ) + ); + this.replyWithPayload(message, stringGraphResult); + }), + () -> this.replyFailed(message) + ); + } + /** * Creates an artifact and adds it to the store. - * - * @param requestIri IRI where the request originated from - * @param message Request */ private void handleCreateArtifact( final IRI requestIri, @@ -159,58 +209,21 @@ private void handleCreateArtifact( final Message message ) throws IOException { // Create IRI for new entity - final var entityIriString = + final var artifactIri = this.generateEntityIri(requestIri.toString(), content.artifactName()); - final var entityIri = RdfModelUtils.createIri(entityIriString); + final var entityIri = RdfModelUtils.createIri(artifactIri); Optional .ofNullable(content.artifactRepresentation()) .filter(s -> !s.isEmpty()) // Replace all null relative IRIs with the IRI generated for this entity - .map(s -> s.replaceAll("<>", "<" + entityIriString + ">")) + .map(s -> s.replaceAll("<>", "<" + artifactIri + ">")) .ifPresentOrElse( Failable.asConsumer(s -> { final var entityModel = RdfModelUtils.stringToModel(s, entityIri, RDFFormat.TURTLE); - final var artifactIri = entityIri.toString(); - final var workspaceIri = - RdfModelUtils.createIri( - Pattern.compile("^(https?://.*?:[0-9]+/workspaces/.*?)/(?:artifacts|agents)/.*?$") - .matcher(artifactIri) - .results() - .findFirst() - .orElseThrow() - .group(1) - ); - entityModel.add( - entityIri, - RdfModelUtils.createIri("https://purl.org/hmas/core/isContainedIn"), - workspaceIri + final var workspaceIri = RdfModelUtils.createIri( + artifactIri.substring(0, artifactIri.indexOf("/artifacts/")) ); - entityModel.add( - workspaceIri, - RdfModelUtils.createIri(RDF.TYPE.toString()), - RdfModelUtils.createIri(WORKSPACE_HMAS_IRI) - ); - this.store - .getEntityModel(workspaceIri) - .ifPresent(Failable.asConsumer(workspaceModel -> { - workspaceModel.add( - workspaceIri, - RdfModelUtils.createIri(CONTAINS_HMAS_IRI), - entityIri - ); - workspaceModel.add( - entityIri, - RdfModelUtils.createIri(RDF.TYPE.toString()), - RdfModelUtils.createIri("https://purl.org/hmas/core/Artifact") - ); - this.store.replaceEntityModel(workspaceIri, workspaceModel); - this.dispatcherMessagebox.sendMessage( - new HttpNotificationDispatcherMessage.EntityChanged( - workspaceIri.toString(), - RdfModelUtils.modelToString(workspaceModel, RDFFormat.TURTLE) - ) - ); - })); + this.enrichArtifactGraphWithWorkspace(entityIri, entityModel, workspaceIri); this.store.addEntityModel(entityIri, entityModel); final var stringGraphResult = RdfModelUtils.modelToString(entityModel, RDFFormat.TURTLE); @@ -226,6 +239,44 @@ private void handleCreateArtifact( ); } + private void enrichArtifactGraphWithWorkspace( + final IRI entityIri, + final Model entityModel, + final IRI workspaceIri + ) throws IOException { + entityModel.add( + entityIri, + RdfModelUtils.createIri("https://purl.org/hmas/core/isContainedIn"), + workspaceIri + ); + entityModel.add( + workspaceIri, + RDF.TYPE, + RdfModelUtils.createIri(WORKSPACE_HMAS_IRI) + ); + this.store + .getEntityModel(workspaceIri) + .ifPresent(Failable.asConsumer(workspaceModel -> { + workspaceModel.add( + workspaceIri, + RdfModelUtils.createIri(CONTAINS_HMAS_IRI), + entityIri + ); + workspaceModel.add( + entityIri, + RDF.TYPE, + RdfModelUtils.createIri("https://purl.org/hmas/core/Artifact") + ); + this.store.replaceEntityModel(workspaceIri, workspaceModel); + this.dispatcherMessagebox.sendMessage( + new HttpNotificationDispatcherMessage.EntityChanged( + workspaceIri.toString(), + RdfModelUtils.modelToString(workspaceModel, RDFFormat.TURTLE) + ) + ); + })); + } + /** * Creates an entity and adds it to the store. * @@ -238,14 +289,14 @@ private void handleCreateWorkspace( final Message message ) throws IllegalArgumentException, IOException { // Create IRI for new entity - final var entityIriString = + final var workspaceIri = this.generateEntityIri(requestIri.toString(), content.workspaceName()); - final var entityIri = RdfModelUtils.createIri(entityIriString); + final var entityIri = RdfModelUtils.createIri(workspaceIri); Optional .ofNullable(content.workspaceRepresentation()) .filter(s -> !s.isEmpty()) // Replace all null relative IRIs with the IRI generated for this entity - .map(s -> s.replaceAll("<>", "<" + entityIriString + ">")) + .map(s -> s.replaceAll("<>", "<" + workspaceIri + ">")) .ifPresentOrElse( Failable.asConsumer(s -> { final var entityModel = RdfModelUtils.stringToModel(s, entityIri, RDFFormat.TURTLE); @@ -258,7 +309,7 @@ private void handleCreateWorkspace( ); entityModel.add( parentIri, - RdfModelUtils.createIri(RDF.TYPE.toString()), + RDF.TYPE, RdfModelUtils.createIri(WORKSPACE_HMAS_IRI) ); this.store @@ -271,7 +322,7 @@ private void handleCreateWorkspace( ); parentModel.add( entityIri, - RdfModelUtils.createIri(RDF.TYPE.toString()), + RDF.TYPE, RdfModelUtils.createIri(WORKSPACE_HMAS_IRI) ); this.store.replaceEntityModel(parentIri, parentModel); @@ -283,7 +334,6 @@ private void handleCreateWorkspace( ); })); } else { - final var workspaceIri = entityIri.toString(); final var platformIri = RdfModelUtils.createIri( workspaceIri.substring(0, workspaceIri.indexOf("workspaces")) ); @@ -294,7 +344,7 @@ private void handleCreateWorkspace( ); entityModel.add( platformIri, - RdfModelUtils.createIri(RDF.TYPE.toString()), + RDF.TYPE, RdfModelUtils.createIri("https://purl.org/hmas/core/HypermediaMASPlatform") ); this.store @@ -307,7 +357,7 @@ private void handleCreateWorkspace( ); platformModel.add( entityIri, - RdfModelUtils.createIri(RDF.TYPE.toString()), + RDF.TYPE, RdfModelUtils.createIri(WORKSPACE_HMAS_IRI) ); this.store.replaceEntityModel(platformIri, platformModel); @@ -388,7 +438,7 @@ private void handleDeleteEntity(final IRI requestIri, final Message . @prefix td: . @prefix htv: . @prefix hctl: . @prefix wotsec: . @prefix dct: . @prefix js: . -@prefix saref: . +@prefix hmas: . - a td:Thing, hmas:Artifact; - td:title "test_agent"; + a td:Thing, , + hmas:Artifact; + td:title "test"; td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme ] . + diff --git a/yggdrasil-core/src/test/resources/test_agent_body_full.ttl b/yggdrasil-core/src/test/resources/test_agent_body_full.ttl index e210758f..8d012695 100644 --- a/yggdrasil-core/src/test/resources/test_agent_body_full.ttl +++ b/yggdrasil-core/src/test/resources/test_agent_body_full.ttl @@ -1,17 +1,19 @@ -@prefix hmas: . @prefix td: . @prefix htv: . @prefix hctl: . @prefix wotsec: . @prefix dct: . @prefix js: . -@prefix saref: . +@prefix hmas: . - a td:Thing, hmas:Artifact; - td:title "test_agent"; + a td:Thing, , + hmas:Artifact; + td:title "test"; td:hasSecurityConfiguration [ a wotsec:NoSecurityScheme - ] . - - hmas:isContainedIn . + ]; + hmas:isContainedIn ; + . a hmas:Workspace . + + a hmas:Agent . diff --git a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/MessageFields.java b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/MessageFields.java index 5f71f3e1..b67f5dda 100644 --- a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/MessageFields.java +++ b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/MessageFields.java @@ -5,6 +5,7 @@ enum MessageFields { REQUEST_URI("requestUri"), ENTITY_URI_HINT("slug"), AGENT_ID("agentID"), + AGENT_NAME("agentName"), WORKSPACE_NAME("workspaceName"), SUB_WORKSPACE_NAME("subWorkspaceName"), ARTIFACT_NAME("artifactName"), diff --git a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/MessageRequestMethods.java b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/MessageRequestMethods.java index 80c1aabf..37f85ad5 100644 --- a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/MessageRequestMethods.java +++ b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/MessageRequestMethods.java @@ -14,6 +14,7 @@ enum MessageRequestMethods { LEAVE_WORKSPACE("leaveWorkspace"), FOCUS("focus"), CREATE_ARTIFACT("createArtifact"), + CREATE_BODY("createBody"), DO_ACTION("performAction"), QUERY("query"); diff --git a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/RdfStoreMessageMarshaller.java b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/RdfStoreMessageMarshaller.java index 1d1e5bf6..0a85e11d 100644 --- a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/RdfStoreMessageMarshaller.java +++ b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/codecs/RdfStoreMessageMarshaller.java @@ -92,6 +92,15 @@ public JsonElement serialize( json.add(MessageFields.NAMED_GRAPH_URIS.getName(), encodedNamedGraphUris); json.addProperty(MessageFields.CONTENT_TYPE.getName(), responseContentType); } + case RdfStoreMessage.CreateBody m -> { + json.addProperty( + MessageFields.REQUEST_METHOD.getName(), + MessageRequestMethods.CREATE_BODY.getName() + ); + json.addProperty(MessageFields.WORKSPACE_NAME.getName(), m.workspaceName()); + json.addProperty(MessageFields.AGENT_NAME.getName(), m.agentName()); + json.addProperty(MessageFields.ENTITY_REPRESENTATION.getName(), m.bodyRepresentation()); + } } return json; } @@ -127,6 +136,11 @@ public RdfStoreMessage deserialize( : Optional.of(jsonObject.get(MessageFields.PARENT_WORKSPACE_URI.getName()).getAsString()), jsonObject.get(MessageFields.ENTITY_REPRESENTATION.getName()).getAsString() ); + case CREATE_BODY -> new RdfStoreMessage.CreateBody( + jsonObject.get(MessageFields.WORKSPACE_NAME.getName()).getAsString(), + jsonObject.get(MessageFields.AGENT_NAME.getName()).getAsString(), + jsonObject.get(MessageFields.ENTITY_REPRESENTATION.getName()).getAsString() + ); case UPDATE_ENTITY -> new RdfStoreMessage.UpdateEntity( jsonObject.get(MessageFields.REQUEST_URI.getName()).getAsString(), jsonObject.get(MessageFields.ENTITY_REPRESENTATION.getName()).getAsString() diff --git a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/messageboxes/RdfStoreMessagebox.java b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/messageboxes/RdfStoreMessagebox.java index ff15f3f5..6f57094a 100644 --- a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/messageboxes/RdfStoreMessagebox.java +++ b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/messageboxes/RdfStoreMessagebox.java @@ -22,6 +22,13 @@ public void init() { RdfStoreMessage.GetEntity.class, new GenericMessageCodec<>(RdfStoreMessage.GetEntity.class, new RdfStoreMessageMarshaller()) ); + this.eventBus.registerDefaultCodec( + RdfStoreMessage.CreateBody.class, + new GenericMessageCodec<>( + RdfStoreMessage.CreateBody.class, + new RdfStoreMessageMarshaller() + ) + ); this.eventBus.registerDefaultCodec( RdfStoreMessage.CreateArtifact.class, new GenericMessageCodec<>( diff --git a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/messages/RdfStoreMessage.java b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/messages/RdfStoreMessage.java index 9da81cd1..10acd986 100644 --- a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/messages/RdfStoreMessage.java +++ b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/eventbus/messages/RdfStoreMessage.java @@ -12,6 +12,12 @@ record UpdateEntity(String requestUri, String entityRepresentation) implements R record DeleteEntity(String requestUri) implements RdfStoreMessage {} + record CreateBody( + String workspaceName, + String agentName, + String bodyRepresentation + ) implements RdfStoreMessage {} + record CreateArtifact( String requestUri, String artifactName, diff --git a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/HttpInterfaceConfig.java b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/HttpInterfaceConfig.java index 0964421a..38b81f3d 100644 --- a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/HttpInterfaceConfig.java +++ b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/HttpInterfaceConfig.java @@ -22,4 +22,6 @@ public interface HttpInterfaceConfig { String getAgentBodiesUri(String workspaceName); String getAgentBodyUri(String workspaceName, String agentName); + + String getAgentUri(String agentName); } diff --git a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/impl/HttpInterfaceConfigImpl.java b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/impl/HttpInterfaceConfigImpl.java index 764b4fc5..c0d8f1fa 100644 --- a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/impl/HttpInterfaceConfigImpl.java +++ b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/impl/HttpInterfaceConfigImpl.java @@ -76,4 +76,9 @@ public String getAgentBodiesUri(final String workspaceName) { public String getAgentBodyUri(final String workspaceName, final String agentName) { return this.getAgentBodiesUri(workspaceName) + "/" + agentName; } + + @Override + public String getAgentUri(final String agentName) { + return this.baseUri + "/agents/" + agentName; + } } diff --git a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/impl/RepresentationFactoryImpl.java b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/impl/RepresentationFactoryImpl.java index a81523f4..04b30da4 100644 --- a/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/impl/RepresentationFactoryImpl.java +++ b/yggdrasil-utils/src/main/java/org/hyperagents/yggdrasil/utils/impl/RepresentationFactoryImpl.java @@ -152,6 +152,7 @@ public String createBodyRepresentation( .Builder(agentName) .addSecurityScheme(securityScheme) .addSemanticType("https://purl.org/hmas/core/Artifact") + .addSemanticType("https://example.org/Body") .addThingURI(this.httpConfig.getAgentBodyUri(workspaceName, agentName)) .addGraph(metadata); return serializeThingDescription(td);