From d2b797c19a0ab877f50785c372bec0ab3f0cbd6e Mon Sep 17 00:00:00 2001 From: "luca.frosini" Date: Fri, 17 Nov 2023 17:21:45 +0100 Subject: [PATCH] Implementing the new match query --- .../queries/json/JsonQuery.java | 7 + .../queries/json/base/JsonQueryERElement.java | 205 +++++++++++++++++- .../json/base/entities/JsonQueryFacet.java | 9 +- .../json/base/entities/JsonQueryResource.java | 88 +++++++- .../base/relations/JsonQueryConsistsOf.java | 9 +- .../base/relations/JsonQueryIsRelatedTo.java | 19 +- .../queries/JsonQueryTest.java | 30 +++ .../EService-query-output.json | 10 + .../projection-queries/EService-query.json | 49 +++++ .../projection-queries/EService.match.oquery | 16 ++ .../EService.traversal.oquery | 37 ++++ .../HostingNode-query-output.json | 18 ++ .../projection-queries/HostingNode-query.json | 66 ++++++ 13 files changed, 551 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/projection-queries/EService-query-output.json create mode 100644 src/test/resources/projection-queries/EService-query.json create mode 100644 src/test/resources/projection-queries/EService.match.oquery create mode 100644 src/test/resources/projection-queries/EService.traversal.oquery create mode 100644 src/test/resources/projection-queries/HostingNode-query-output.json create mode 100644 src/test/resources/projection-queries/HostingNode-query.json diff --git a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/JsonQuery.java b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/JsonQuery.java index 5aa9768..3f7bee7 100644 --- a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/JsonQuery.java +++ b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/JsonQuery.java @@ -103,6 +103,13 @@ public class JsonQuery { return entryPoint.createQuery(new StringBuffer()); } + + public StringBuffer createMatchQuery() throws SchemaException, InvalidQueryException, ResourceRegistryException { + entryPoint = getJsonQueryERElement(jsonQuery); + entryPoint.setEntryPoint(true); + return entryPoint.createMatchQuery(new StringBuffer()); + } + public String query() throws InvalidQueryException, ResourceRegistryException { ODatabaseDocument current = ContextUtility.getCurrentODatabaseDocumentFromThreadLocal(); oDatabaseDocument = null; diff --git a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/JsonQueryERElement.java b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/JsonQueryERElement.java index 90ad721..0840d09 100644 --- a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/JsonQueryERElement.java +++ b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/JsonQueryERElement.java @@ -1,7 +1,9 @@ package org.gcube.informationsystem.resourceregistry.queries.json.base; +import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.List; import java.util.Set; import org.gcube.com.fasterxml.jackson.databind.JsonNode; @@ -44,6 +46,51 @@ public abstract class JsonQueryERElement { protected Direction direction; protected boolean entryPoint; + /* Start of variables used to create in MATCH queries */ + /** + * Instruct the JSON query analyzer if it is a projection + */ + protected boolean projection; + /** + * The caller of this instance analyzer. + * It is null if this instance is an entry point. + * (e.g. a JsonQueryResource instance for a JsonQueryConsistsOf instance) + * + */ + protected JsonQueryERElement caller; + /** + * The chain of callers of this instance analyzer. + * breadcrumb.get(breadcrumb.size-1) == caller + * breadcrumb is empty if this instance is an entry point + */ + protected List breadcrumb; + /** + * The breadcrumb.size() provide the level of nesting. + * The position which element number at the same level. + * E.g. the index of a ConsistsOf when the caller is a Resource. + * + * It is used in conjunction with breadcrumb.size() to attach a number to the alias. + * This allows to generate a predictive not clashing alias instead of using random string/number. + * This is useful for testing purposes. + */ + protected int position; + + /** + * Contains the alias if needed by the class + */ + protected String alias; + + /** + * This field is used by entry point only. + * Any class at any level can retrieve the entry point using + * breadcrumb.get(0); + */ + protected List fieldsToEmit; + /* Start of variables used to create in MATCH queries */ + + + + /** * it indicates the number of properties in this.jsonNode * This number is manipulated while analyzing the jsonNode @@ -62,6 +109,12 @@ public abstract class JsonQueryERElement { this.entryPoint = false; this.traverseBack = true; + this.projection = false; + this.caller = null; + this.breadcrumb = new ArrayList<>(); + this.position = 0; + + this.fieldNamesToRemove = new HashSet<>(); this.fieldNamesToRemove.add(Element.TYPE_PROPERTY); this.fieldNamesToRemove.add(ModelElement.SUPERTYPES_PROPERTY); @@ -91,6 +144,76 @@ public abstract class JsonQueryERElement { this.traverseBack = !entryPoint; } + public boolean isProjection() { + return projection; + } + + public void setProjection(boolean projection) { + this.projection = projection; + if(projection && !entryPoint) { + breadcrumb.get(0).setEntryPoint(projection); + } + } + + public JsonQueryERElement getCaller() { + return caller; + } + + public void setCaller(JsonQueryERElement caller) { + this.caller = caller; + } + + public List getBreadcrumb() { + return breadcrumb; + } + + public void setBreadcrumb(List breadcrumb) { + this.breadcrumb = breadcrumb; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + public String getAlias() { + return alias; + } + + public String getAlias(boolean generateifNull) { + if(alias==null && generateifNull) { + alias = generateAlias().toString(); + } + return alias; + } + + protected StringBuffer generateAlias() { + StringBuffer sb = new StringBuffer(); + sb.append(type.toLowerCase()); + + // The entry point will have 00 as suffix + sb.append(breadcrumb.size()); + sb.append(position); + return sb; + } + + /** + * Add a field to emit only if this instance is the entry point + * @param fieldToEmit + */ + protected void addFieldToEmit(String fieldToEmit) { + if(entryPoint) { + fieldsToEmit.add(fieldToEmit); + logger.trace("The field to emit ({}) has been added to entry point, i.e. {} with alias {}", fieldToEmit, this.type, this.alias); + }else { + logger.trace("The field to emit ({}) will be added to entry point", fieldToEmit); + breadcrumb.get(0).addFieldToEmit(fieldToEmit); + } + } + public boolean isTraverseBack() { return traverseBack; } @@ -103,9 +226,7 @@ public abstract class JsonQueryERElement { return createTraversalQuery(stringBuffer); } - protected abstract StringBuffer createTraversalQuery(StringBuffer stringBuffer) throws SchemaNotFoundException, InvalidQueryException, SchemaException, ResourceRegistryException; - - protected abstract StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaNotFoundException, InvalidQueryException, SchemaException, ResourceRegistryException; + public abstract StringBuffer createTraversalQuery(StringBuffer stringBuffer) throws SchemaNotFoundException, InvalidQueryException, SchemaException, ResourceRegistryException; protected StringBuffer addConstraints(JsonNode jsonNode, LogicalOperator queryLogicalOperator, String fieldNamePrefix) throws InvalidQueryException { StringBuffer stringBuffer = new StringBuffer(); @@ -240,4 +361,82 @@ public abstract class JsonQueryERElement { } return stringBuffer; } + + protected List getChildrenBreadcrumb() { + List childrenBreadcrumb = new ArrayList<>(this.breadcrumb); + childrenBreadcrumb.add(this); + return childrenBreadcrumb; + } + + protected StringBuffer wrapMatchQuery(StringBuffer buffer) throws InvalidQueryException { + if(entryPoint) { + alias = getAlias(true); + + StringBuffer entryBuffer = new StringBuffer(); + entryBuffer.append("MATCH\n"); + entryBuffer.append("\t{class: "); // The { has to be closed + entryBuffer.append(type); + entryBuffer.append(", as: "); + entryBuffer.append(alias); + entryBuffer.append(", where: "); + entryBuffer.append("(($currentMatch['@class'] INSTANCEOF '"); // The first and the second ( has to be closed + entryBuffer.append(type); + entryBuffer.append("')"); // close the second ( + + if(size>1) { + entryBuffer.append(" AND ("); + entryBuffer.append(addConstraints(jsonNode, null, null)); + entryBuffer.append(")"); + } + entryBuffer.append(")"); // close the first ( + + entryBuffer.append("}"); + entryBuffer.append(buffer); + + entryBuffer.append("\nRETURN\n"); + + if(!projection) { + entryBuffer.append("\tDISTINCT("); + entryBuffer.append(alias); + entryBuffer.append(") as ret"); + + StringBuffer wrap = new StringBuffer(); + wrap.append("SELECT EXPAND(ret) FROM (\n"); + wrap.append(entryBuffer); + wrap.append("\n)"); + + entryBuffer = wrap; + }else { + int size = fieldsToEmit.size(); + for(int i=0; i childrenBreadcrumb) throws SchemaException, ResourceRegistryException; + + public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException { + List childrenBreadcrumb = getChildrenBreadcrumb(); + + if(entryPoint) { + alias = getAlias(); + } + + StringBuffer buffer = getSpecificMatchQuery(childrenBreadcrumb); + buffer = wrapMatchQuery(buffer); + + stringBuffer.append(buffer); + + return stringBuffer; + } } diff --git a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/entities/JsonQueryFacet.java b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/entities/JsonQueryFacet.java index 81ca0a9..7899b2b 100644 --- a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/entities/JsonQueryFacet.java +++ b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/entities/JsonQueryFacet.java @@ -1,11 +1,14 @@ package org.gcube.informationsystem.resourceregistry.queries.json.base.entities; +import java.util.List; + import org.gcube.com.fasterxml.jackson.databind.JsonNode; import org.gcube.informationsystem.base.reference.AccessType; import org.gcube.informationsystem.base.reference.Direction; import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException; import org.gcube.informationsystem.resourceregistry.api.exceptions.queries.InvalidQueryException; import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException; +import org.gcube.informationsystem.resourceregistry.queries.json.base.JsonQueryERElement; import org.gcube.informationsystem.resourceregistry.queries.json.base.relations.JsonQueryConsistsOf; import org.gcube.informationsystem.resourceregistry.utils.OrientDBUtility; @@ -87,9 +90,11 @@ public class JsonQueryFacet extends JsonQueryEntity { return newBuffer; } - + @Override - public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException { + protected StringBuffer getSpecificMatchQuery(List childrenBreadcrumb) + throws SchemaException, ResourceRegistryException { + // TODO Auto-generated method stub return null; } diff --git a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/entities/JsonQueryResource.java b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/entities/JsonQueryResource.java index 1b6467a..eb34316 100644 --- a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/entities/JsonQueryResource.java +++ b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/entities/JsonQueryResource.java @@ -1,5 +1,7 @@ package org.gcube.informationsystem.resourceregistry.queries.json.base.entities; +import java.util.List; + import org.gcube.com.fasterxml.jackson.databind.JsonNode; import org.gcube.com.fasterxml.jackson.databind.node.ArrayNode; import org.gcube.informationsystem.base.reference.AccessType; @@ -7,6 +9,7 @@ import org.gcube.informationsystem.base.reference.Direction; import org.gcube.informationsystem.model.reference.entities.Resource; import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException; import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException; +import org.gcube.informationsystem.resourceregistry.queries.json.base.JsonQueryERElement; import org.gcube.informationsystem.resourceregistry.queries.json.base.relations.JsonQueryConsistsOf; import org.gcube.informationsystem.resourceregistry.queries.json.base.relations.JsonQueryIsRelatedTo; import org.gcube.informationsystem.resourceregistry.utils.OrientDBUtility; @@ -115,8 +118,89 @@ public class JsonQueryResource extends JsonQueryEntity { } @Override - public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException { - return null; + protected StringBuffer getSpecificMatchQuery(List childrenBreadcrumb) throws SchemaException, ResourceRegistryException { + + StringBuffer buffer = new StringBuffer(); + + int isRelatedToSize = 0; + + ArrayNode consistsOfArray = (ArrayNode) jsonNode.get(Resource.CONSISTS_OF_PROPERTY); + int consistsOfSize = 0; + if(consistsOfArray!=null) { + consistsOfSize = consistsOfArray.size(); + } + + ArrayNode isRelatedToArray = (ArrayNode) jsonNode.get(Resource.IS_RELATED_TO_PROPERTY); + if(isRelatedToArray!=null && isRelatedToArray.size()>0) { + --size; + isRelatedToSize = isRelatedToArray.size(); + for(int i=0; i0) { + traverseBack = true; + } + jsonQueryIsRelatedTo.setTraverseBack(traverseBack); + + buffer = jsonQueryIsRelatedTo.createMatchQuery(buffer); + + if(traverseBack) { + buffer.append(jsonQueryIsRelatedTo.getDirection().opposite().name().toLowerCase()); + buffer.append("V('"); + buffer.append(type); + buffer.append("') "); + if(alias!=null || entryPoint) { + buffer.append("{ where: ($matched."); + buffer.append(alias); + buffer.append(" == $currentMatch)}\n"); + } + } + } + } + + + if(consistsOfSize>0) { + --size; + for(int i=0; i childrenBreadcrumb) + throws SchemaException, ResourceRegistryException { + // TODO Auto-generated method stub return null; } diff --git a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/relations/JsonQueryIsRelatedTo.java b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/relations/JsonQueryIsRelatedTo.java index a1f2e9c..5d39a30 100644 --- a/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/relations/JsonQueryIsRelatedTo.java +++ b/src/main/java/org/gcube/informationsystem/resourceregistry/queries/json/base/relations/JsonQueryIsRelatedTo.java @@ -1,5 +1,7 @@ package org.gcube.informationsystem.resourceregistry.queries.json.base.relations; +import java.util.List; + import javax.ws.rs.InternalServerErrorException; import org.gcube.com.fasterxml.jackson.databind.JsonNode; @@ -9,6 +11,7 @@ import org.gcube.informationsystem.model.reference.relations.IsRelatedTo; import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException; import org.gcube.informationsystem.resourceregistry.api.exceptions.queries.InvalidQueryException; import org.gcube.informationsystem.resourceregistry.api.exceptions.types.SchemaException; +import org.gcube.informationsystem.resourceregistry.queries.json.base.JsonQueryERElement; import org.gcube.informationsystem.resourceregistry.queries.json.base.entities.JsonQueryResource; import org.gcube.informationsystem.resourceregistry.utils.OrientDBUtility; @@ -192,10 +195,20 @@ public class JsonQueryIsRelatedTo extends JsonQueryRelation { return stringBuffer; } - + @Override - public StringBuffer createMatchQuery(StringBuffer stringBuffer) throws SchemaException, ResourceRegistryException { - return null; + protected StringBuffer getSpecificMatchQuery(List childrenBreadcrumb) { + + if(!entryPoint && direction==null) { + throw new InternalServerErrorException("Caller Resource must invoke setDirectionByJson() first. This is a server bug. Please contact the administator. "); + } + + StringBuffer buffer = new StringBuffer(); + + + + return buffer; } + } diff --git a/src/test/java/org/gcube/informationsystem/resourceregistry/queries/JsonQueryTest.java b/src/test/java/org/gcube/informationsystem/resourceregistry/queries/JsonQueryTest.java index 268eb7e..1c14b33 100644 --- a/src/test/java/org/gcube/informationsystem/resourceregistry/queries/JsonQueryTest.java +++ b/src/test/java/org/gcube/informationsystem/resourceregistry/queries/JsonQueryTest.java @@ -188,4 +188,34 @@ public class JsonQueryTest extends ContextTest { logger.info("No limit listing: Got {} with UUID {}", Entity.NAME, uuid); } } + + @Test + public void testCreateMatchQuery() throws Exception { + File queriesDirectory = getQueriesDirectory(); + File jsonQueryFile = new File(queriesDirectory, "query6.json"); + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(jsonQueryFile); + logger.info("Going to test the following JSON query {}", jsonNode.toString()); + + JsonQuery jsonQuery = new JsonQuery(); + jsonQuery.setJsonQuery(jsonNode); + StringBuffer createdStringBuffer = jsonQuery.createMatchQuery(); + + logger.info("Created Query from JSON:\n{}", createdStringBuffer.toString()); + +// StringBuffer expectedStringBuffer = new StringBuffer(); +// File expectedQueryFile = new File(queriesDirectory, jsonQueryFile.getName().replace("json", "match.oquery")); +// try(BufferedReader br = new BufferedReader(new FileReader(expectedQueryFile))) { +// for(String line; (line = br.readLine()) != null; ) { +// expectedStringBuffer.append(line); +// } +// } +// +// logger.info("Expected Query from JSON: {}", expectedStringBuffer.toString()); +// +// Assert.assertTrue(createdStringBuffer.toString().compareTo(expectedStringBuffer.toString())==0); +// +// String result = jsonQuery.query(); +// logger.info("Result : {}", result); + } } diff --git a/src/test/resources/projection-queries/EService-query-output.json b/src/test/resources/projection-queries/EService-query-output.json new file mode 100644 index 0000000..ab17922 --- /dev/null +++ b/src/test/resources/projection-queries/EService-query-output.json @@ -0,0 +1,10 @@ +[ + { + "Id" : "", + "Group": "", + "Name" : "", + "Version" : "", + "Status" : "", + "Host" "" + } +] \ No newline at end of file diff --git a/src/test/resources/projection-queries/EService-query.json b/src/test/resources/projection-queries/EService-query.json new file mode 100644 index 0000000..21970e2 --- /dev/null +++ b/src/test/resources/projection-queries/EService-query.json @@ -0,0 +1,49 @@ +{ + "_projection" : { + "type": "EService", + "_emit" : { + "id" : "id" + }, + "consistsOf": [ + { + "type": "IsIdentifiedBy", + "target": { + "type": "SoftwareFacet", + "_emit" : { + "group" : "Group", + "name": "Name", + "version" : "Version" + } + } + }, + { + "type": "ConsistsOf", + "target": { + "type": "StateFacet", + "_emit" : { + "value" : "Status" + } + } + } + ], + "isRelatedTo" : [ + { + "type": "Activates", + "source": { + "type": "HostingNode", + "consistsOf": [ + { + "type": "IsIdentifiedBy", + "target": { + "type": "NetworkingFacet", + "_emit" : { + "hostName" : "Host" + } + } + } + ] + } + } + ] + } +} \ No newline at end of file diff --git a/src/test/resources/projection-queries/EService.match.oquery b/src/test/resources/projection-queries/EService.match.oquery new file mode 100644 index 0000000..e9143cf --- /dev/null +++ b/src/test/resources/projection-queries/EService.match.oquery @@ -0,0 +1,16 @@ +MATCH + {class: EService, as: eservice, where: ($currentMatch['@class'] INSTANCEOF 'EService')} + .outE('IsIdentifiedBy').inV('SoftwareFacet') {as: softwarefacet, where: ($currentMatch['@class'] INSTANCEOF 'SoftwareFacet')} + .inE('IsIdentifiedBy') + .outV('Eservice') {where: ($matched.eservice == $currentMatch)} + .outE('ConsistsOf').inV("StateFacet") {as: statefacet, where: (($currentMatch['@class'] INSTANCEOF 'StateFacet'))} + .inE('ConsistsOf') + .outV('Eservice') {where: ($matched.eservice == $currentMatch)} + .inE('Activates').outV('HostingNode').outE('IsIdentifiedBy').inV('NetworkingFacet') {as: networkingfacet, where: ($currentMatch['@class'] INSTANCEOF 'NetworkingFacet')} +RETURN + eservice.id AS ID, + softwarefacet.group AS Group, + softwarefacet.name AS Name, + softwarefacet.version AS Version, + statefacet.value AS Status, + networkingfacet.hostName AS Host \ No newline at end of file diff --git a/src/test/resources/projection-queries/EService.traversal.oquery b/src/test/resources/projection-queries/EService.traversal.oquery new file mode 100644 index 0000000..7f2cb1c --- /dev/null +++ b/src/test/resources/projection-queries/EService.traversal.oquery @@ -0,0 +1,37 @@ +SELECT id, + outE("IsIdentifiedBy").inV("SoftwareFacet").name[0] AS `Name`, + outE("IsIdentifiedBy").inV("SoftwareFacet").group[0] AS `Group`, + outE("IsIdentifiedBy").inV("SoftwareFacet").version[0] AS `Version`, + outE("ConsistsOf").inV("StateFacet").value AS `Status`, + inE("Activates").outV("HostingNode").outE("IsIdentifiedBy").inV("NetworkingFacet").hostName[0] AS `Host` + FROM ( + TRAVERSE outV("EService") FROM ( + TRAVERSE inE("ConsistsOf") FROM ( + SELECT FROM ( + TRAVERSE inV("StateFacet") FROM ( + TRAVERSE outE("ConsistsOf") FROM ( + TRAVERSE outV("EService") FROM ( + TRAVERSE inE("IsIdentifiedBy") FROM ( + SELECT FROM ( + TRAVERSE inV("SoftwareFacet") FROM ( + TRAVERSE outE("IsIdentifiedBy") FROM ( + TRAVERSE inV("EService") FROM ( + TRAVERSE outE("Activates") FROM ( + TRAVERSE outV("HostingNode") FROM ( + TRAVERSE inE("IsIdentifiedBy") FROM ( + SELECT FROM NetworkingFacet + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) + ) +) WHERE @class INSTANCEOF "EService" \ No newline at end of file diff --git a/src/test/resources/projection-queries/HostingNode-query-output.json b/src/test/resources/projection-queries/HostingNode-query-output.json new file mode 100644 index 0000000..2cb9a42 --- /dev/null +++ b/src/test/resources/projection-queries/HostingNode-query-output.json @@ -0,0 +1,18 @@ +[ + { + "id" : "", + "Last Update Time": "", + "HostName" : "", + "Status" : "", + "HD Space Left" : "34 Gb", + "Mem. Available." "1,23 Gb" + }, + { + "id" : "", + "Last Update Time": "", + "HostName" : "", + "Status" : "", + "HD Space Left" : "42 Gb", + "Mem. Available." "2,54 Gb" + } +] \ No newline at end of file diff --git a/src/test/resources/projection-queries/HostingNode-query.json b/src/test/resources/projection-queries/HostingNode-query.json new file mode 100644 index 0000000..6c85a7e --- /dev/null +++ b/src/test/resources/projection-queries/HostingNode-query.json @@ -0,0 +1,66 @@ +{ + "_projection" : { + "type": "HostingNode", + "_emit" : { + "id" : "Id", + }, + "metadata" : { + "_emit" : { + "lastUpdateTime" : "Last Update Time" + } + }, + "consistsOf": [ + { + "type": "IsIdentifiedBy", + "target": { + "type": "NetworkingFacet", + "_emit" : { + "hostName" : "HostName" + } + } + }, + { + "type": "ConsistsOf", + "target": { + "type": "StateFacet", + "_emit" : { + "value" : "Status" + } + } + }, + { + "type": "HasPersistentMemory", + "target": { + "type": "MemoryFacet", + "_concat" : { + "values" : [ + { + "_sub" : [ + "size", + "used" + ] + }, + "unit" + ], + "separator" : " ", + "as" : "HD Space Left" + } + } + }, + { + "type": "HasVolatileMemory", + "target": { + "type": "MemoryFacet", + "_concat" : { + "values" : [ + "size", + "unit" + ], + "separator" : " ", + "as" : "Mem. Available." + } + } + } + ] + } +} \ No newline at end of file