Compare commits
25 Commits
Author | SHA1 | Date |
---|---|---|
luca.frosini | 18b77a179a | |
luca.frosini | 85935c71d7 | |
luca.frosini | ae136f9567 | |
luca.frosini | e1b1f23e81 | |
luca.frosini | 8d5c498d07 | |
luca.frosini | f6772e4647 | |
luca.frosini | 54d387e035 | |
luca.frosini | 25fffa886d | |
luca.frosini | 19f1614a15 | |
luca.frosini | f35c1564b8 | |
luca.frosini | 745da916b1 | |
Luca Frosini | 35d8d854e2 | |
Luca Frosini | 5aad485d06 | |
Luca Frosini | e7e251dac8 | |
Luca Frosini | f6f2402b07 | |
Luca Frosini | 6e33d8a936 | |
Luca Frosini | 693c9f54b2 | |
Luca Frosini | 6d08991dc3 | |
Luca Frosini | 3596058992 | |
Luca Frosini | a26b4fcba5 | |
Luca Frosini | 71a9acae34 | |
Luca Frosini | a60aa315fe | |
Luca Frosini | 74a5ce896c | |
Luca Frosini | e1284006e7 | |
Luca Frosini | 9c23f9f42a |
|
@ -1,3 +1,4 @@
|
||||||
target
|
target
|
||||||
.classpath
|
.classpath
|
||||||
.project
|
.project
|
||||||
|
/.DS_Store
|
||||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -2,6 +2,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||||
|
|
||||||
# Changelog for Resource Registry API
|
# Changelog for Resource Registry API
|
||||||
|
|
||||||
|
# [v5.1.0-SNAPSHOT]
|
||||||
|
|
||||||
|
- Added Contexts as Teee in ContextCache [#24555]
|
||||||
|
- Added query parameters to paginate result of queries [#24648]
|
||||||
|
|
||||||
|
|
||||||
|
## [v5.0.0]
|
||||||
|
|
||||||
|
- Migrate code to reorganized E/R format [#24992]
|
||||||
|
- Added query parameters to request Metadata [#25040]
|
||||||
|
|
||||||
|
|
||||||
## [v4.3.0]
|
## [v4.3.0]
|
||||||
|
|
||||||
- Enhanced gcube-bom version
|
- Enhanced gcube-bom version
|
||||||
|
@ -22,6 +34,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||||
- Added ContextCache utilities
|
- Added ContextCache utilities
|
||||||
- Added support to provide APIs to get the list of instance contexts [#20012] [#20013]
|
- Added support to provide APIs to get the list of instance contexts [#20012] [#20013]
|
||||||
|
|
||||||
|
|
||||||
## [v4.0.0] [r4.26.0] - 2020-11-11
|
## [v4.0.0] [r4.26.0] - 2020-11-11
|
||||||
|
|
||||||
- Switched JSON management to gcube-jackson [#19116]
|
- Switched JSON management to gcube-jackson [#19116]
|
||||||
|
|
6
pom.xml
6
pom.xml
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<groupId>org.gcube.information-system</groupId>
|
<groupId>org.gcube.information-system</groupId>
|
||||||
<artifactId>resource-registry-api</artifactId>
|
<artifactId>resource-registry-api</artifactId>
|
||||||
<version>4.3.0</version>
|
<version>5.1.0-SNAPSHOT</version>
|
||||||
<name>Resource Registry API</name>
|
<name>Resource Registry API</name>
|
||||||
<description>Resource Registry API is a library containing classes shared across resource-registry-* components</description>
|
<description>Resource Registry API is a library containing classes shared across resource-registry-* components</description>
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.gcube.distribution</groupId>
|
<groupId>org.gcube.distribution</groupId>
|
||||||
<artifactId>gcube-bom</artifactId>
|
<artifactId>gcube-bom</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.4.0</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.gcube.common</groupId>
|
<groupId>org.gcube.common</groupId>
|
||||||
<artifactId>authorization-utils</artifactId>
|
<artifactId>authorization-utils</artifactId>
|
||||||
<version>[2.1.0, 3.0.0-SNAPSHOT)</version>
|
<version>[2.2.0, 3.0.0-SNAPSHOT)</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
|
@ -2,9 +2,12 @@ package org.gcube.informationsystem.resourceregistry.api.contexts;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.HashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
import java.util.TreeSet;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -13,6 +16,7 @@ import org.gcube.informationsystem.contexts.impl.relations.IsParentOfImpl;
|
||||||
import org.gcube.informationsystem.contexts.reference.entities.Context;
|
import org.gcube.informationsystem.contexts.reference.entities.Context;
|
||||||
import org.gcube.informationsystem.contexts.reference.relations.IsParentOf;
|
import org.gcube.informationsystem.contexts.reference.relations.IsParentOf;
|
||||||
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
|
||||||
|
import org.gcube.informationsystem.tree.Tree;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -45,25 +49,9 @@ public class ContextCache {
|
||||||
return singleton;
|
return singleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanCache() {
|
|
||||||
cleanCache(Calendar.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cleanCache(Calendar now) {
|
|
||||||
this.contexts = null;
|
|
||||||
this.uuidToContext = new HashMap<>();
|
|
||||||
this.uuidToContextFullName = new HashMap<>();
|
|
||||||
this.contextFullNameToUUID = new HashMap<>();
|
|
||||||
this.creationTime = Calendar.getInstance();
|
|
||||||
this.creationTime.setTimeInMillis(now.getTimeInMillis());
|
|
||||||
this.expiringTime = Calendar.getInstance();
|
|
||||||
this.expiringTime.setTimeInMillis(now.getTimeInMillis());
|
|
||||||
this.expiringTime.add(Calendar.MILLISECOND, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ContextCacheRenewal contextCacheRenewal;
|
protected ContextCacheRenewal contextCacheRenewal;
|
||||||
|
|
||||||
// in millisec used for logging purposes only
|
// in millisec used only for logging and debugging
|
||||||
protected Calendar creationTime;
|
protected Calendar creationTime;
|
||||||
// in millisec
|
// in millisec
|
||||||
protected Calendar expiringTime;
|
protected Calendar expiringTime;
|
||||||
|
@ -72,25 +60,53 @@ public class ContextCache {
|
||||||
protected Map<UUID, Context> uuidToContext;
|
protected Map<UUID, Context> uuidToContext;
|
||||||
protected Map<UUID, String> uuidToContextFullName;
|
protected Map<UUID, String> uuidToContextFullName;
|
||||||
protected Map<String, UUID> contextFullNameToUUID;
|
protected Map<String, UUID> contextFullNameToUUID;
|
||||||
|
protected Tree<Context> contextsTree;
|
||||||
|
|
||||||
public ContextCache() {
|
public ContextCache() {
|
||||||
Calendar now = Calendar.getInstance();
|
Calendar now = Calendar.getInstance();
|
||||||
cleanCache(now);
|
cleanCache(now);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void cleanCache() {
|
||||||
|
cleanCache(Calendar.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void cleanCache(Calendar calendar) {
|
||||||
|
this.contexts = null;
|
||||||
|
this.uuidToContext = new LinkedHashMap<>();
|
||||||
|
this.uuidToContextFullName = new LinkedHashMap<>();
|
||||||
|
this.contextFullNameToUUID = new TreeMap<>();
|
||||||
|
this.contextsTree = new Tree<Context>(new ContextInformation());
|
||||||
|
this.contextsTree.setAllowMultipleInheritance(false);
|
||||||
|
this.creationTime = Calendar.getInstance();
|
||||||
|
this.creationTime.setTimeInMillis(calendar.getTimeInMillis());
|
||||||
|
this.expiringTime = Calendar.getInstance();
|
||||||
|
this.expiringTime.setTimeInMillis(calendar.getTimeInMillis());
|
||||||
|
this.expiringTime.add(Calendar.MILLISECOND, -1);
|
||||||
|
this.expiringTime.add(Calendar.MILLISECOND, expiringTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void renew() throws ResourceRegistryException {
|
||||||
|
cleanCache();
|
||||||
|
refreshContextsIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContextCacheRenewal getContextCacheRenewal() {
|
||||||
|
return contextCacheRenewal;
|
||||||
|
}
|
||||||
|
|
||||||
public void setContextCacheRenewal(ContextCacheRenewal contextCacheRenewal) {
|
public void setContextCacheRenewal(ContextCacheRenewal contextCacheRenewal) {
|
||||||
if(this.contextCacheRenewal==null) {
|
if(this.contextCacheRenewal==null) {
|
||||||
this.contextCacheRenewal = contextCacheRenewal;
|
this.contextCacheRenewal = contextCacheRenewal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refreshContextsIfNeeded() throws ResourceRegistryException {
|
public synchronized void refreshContextsIfNeeded() throws ResourceRegistryException {
|
||||||
Calendar now = Calendar.getInstance();
|
Calendar now = Calendar.getInstance();
|
||||||
if((now.after(expiringTime) || (contexts==null)) && contextCacheRenewal!=null) {
|
if((now.after(expiringTime) || (contexts==null)) && contextCacheRenewal!=null) {
|
||||||
try {
|
try {
|
||||||
List<Context> contexts = contextCacheRenewal.renew();
|
List<Context> contexts = contextCacheRenewal.renew();
|
||||||
cleanCache(now);
|
setContexts(now, contexts);
|
||||||
setContexts(contexts);
|
|
||||||
} catch (ResourceRegistryException e) {
|
} catch (ResourceRegistryException e) {
|
||||||
logger.error("Unable to refresh Cache", e);
|
logger.error("Unable to refresh Cache", e);
|
||||||
if(contexts==null) {
|
if(contexts==null) {
|
||||||
|
@ -106,48 +122,61 @@ public class ContextCache {
|
||||||
return contexts;
|
return contexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setContexts(List<Context> contexts) {
|
public void setContexts(List<Context> contexts) {
|
||||||
|
Calendar now = Calendar.getInstance();
|
||||||
|
setContexts(now, contexts);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setContexts(Calendar calendar, List<Context> contexts) {
|
||||||
|
cleanCache(calendar);
|
||||||
this.contexts = new ArrayList<>();
|
this.contexts = new ArrayList<>();
|
||||||
|
|
||||||
for(Context c : contexts) {
|
for(Context c : contexts) {
|
||||||
UUID uuid = c.getHeader().getUUID();
|
UUID uuid = c.getID();
|
||||||
Context context = new ContextImpl(c.getName());
|
Context context = new ContextImpl(c.getName());
|
||||||
context.setHeader(c.getHeader());
|
context.setMetadata(c.getMetadata());
|
||||||
|
context.setID(uuid);
|
||||||
this.contexts.add(context);
|
this.contexts.add(context);
|
||||||
this.uuidToContext.put(uuid, context);
|
this.uuidToContext.put(uuid, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(Context c : contexts) {
|
for(Context c : contexts) {
|
||||||
UUID uuid = c.getHeader().getUUID();
|
UUID uuid = c.getID();
|
||||||
Context context = this.uuidToContext.get(uuid);
|
Context context = this.uuidToContext.get(uuid);
|
||||||
if(c.getParent()!=null) {
|
if(c.getParent()!=null) {
|
||||||
IsParentOf ipo = c.getParent();
|
IsParentOf ipo = c.getParent();
|
||||||
UUID parentUUID = ipo.getSource().getHeader().getUUID();
|
UUID parentUUID = ipo.getSource().getID();
|
||||||
Context parent = this.uuidToContext.get(parentUUID);
|
Context parent = this.uuidToContext.get(parentUUID);
|
||||||
IsParentOf isParentOf = new IsParentOfImpl(parent, context);
|
IsParentOf isParentOf = new IsParentOfImpl(parent, context);
|
||||||
isParentOf.setHeader(ipo.getHeader());
|
isParentOf.setID(parentUUID);
|
||||||
|
isParentOf.setMetadata(ipo.getMetadata());
|
||||||
parent.addChild(isParentOf);
|
parent.addChild(isParentOf);
|
||||||
context.setParent(isParentOf);
|
context.setParent(isParentOf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for(Context context : contexts) {
|
for(Context context : contexts) {
|
||||||
UUID uuid = context.getHeader().getUUID();
|
UUID uuid = context.getID();
|
||||||
String fullName = getContextFullName(context);
|
String fullName = getContextFullName(context);
|
||||||
this.uuidToContextFullName.put(uuid, fullName);
|
this.uuidToContextFullName.put(uuid, fullName);
|
||||||
this.contextFullNameToUUID.put(fullName, uuid);
|
this.contextFullNameToUUID.put(fullName, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SortedSet<String> contextFullNames = new TreeSet<String>(contextFullNameToUUID.keySet());
|
||||||
|
for(String contextFullName : contextFullNames) {
|
||||||
|
UUID uuid = contextFullNameToUUID.get(contextFullName);
|
||||||
|
Context context = uuidToContext.get(uuid);
|
||||||
|
contextsTree.addNode(context);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getContextFullName(Context context) {
|
protected String getContextFullName(Context context) {
|
||||||
StringBuilder stringBuilder = new StringBuilder();
|
StringBuilder stringBuilder = new StringBuilder();
|
||||||
IsParentOf ipo = context.getParent();
|
IsParentOf ipo = context.getParent();
|
||||||
if(ipo!=null) {
|
if(ipo!=null) {
|
||||||
Context c = ipo.getSource();
|
Context c = ipo.getSource();
|
||||||
c = uuidToContext.get(c.getHeader().getUUID());
|
c = uuidToContext.get(c.getID());
|
||||||
String parentFullName = getContextFullName(c);
|
String parentFullName = getContextFullName(c);
|
||||||
stringBuilder.append(parentFullName);
|
stringBuilder.append(parentFullName);
|
||||||
}
|
}
|
||||||
|
@ -179,12 +208,12 @@ public class ContextCache {
|
||||||
|
|
||||||
public synchronized Context getContextByUUID(String uuid) throws ResourceRegistryException {
|
public synchronized Context getContextByUUID(String uuid) throws ResourceRegistryException {
|
||||||
refreshContextsIfNeeded();
|
refreshContextsIfNeeded();
|
||||||
return uuidToContext.get(UUID.fromString(uuid));
|
return getContextByUUID(UUID.fromString(uuid));
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Context getContextByFullName(String contextFullName) throws ResourceRegistryException {
|
public synchronized Context getContextByFullName(String contextFullName) throws ResourceRegistryException {
|
||||||
UUID uuid = getUUIDByFullName(contextFullName);
|
UUID uuid = getUUIDByFullName(contextFullName);
|
||||||
return uuidToContext.get(uuid);
|
return getContextByUUID(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -192,7 +221,7 @@ public class ContextCache {
|
||||||
*/
|
*/
|
||||||
public synchronized Map<UUID, String> getUUIDToContextFullNameAssociation() throws ResourceRegistryException {
|
public synchronized Map<UUID, String> getUUIDToContextFullNameAssociation() throws ResourceRegistryException {
|
||||||
refreshContextsIfNeeded();
|
refreshContextsIfNeeded();
|
||||||
return new HashMap<>(uuidToContextFullName);
|
return new LinkedHashMap<>(uuidToContextFullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -200,7 +229,11 @@ public class ContextCache {
|
||||||
*/
|
*/
|
||||||
public synchronized Map<String, UUID> getContextFullNameToUUIDAssociation() throws ResourceRegistryException {
|
public synchronized Map<String, UUID> getContextFullNameToUUIDAssociation() throws ResourceRegistryException {
|
||||||
refreshContextsIfNeeded();
|
refreshContextsIfNeeded();
|
||||||
return new HashMap<>(contextFullNameToUUID);
|
return new TreeMap<>(contextFullNameToUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tree<Context> getContextsTree() {
|
||||||
|
return contextsTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package org.gcube.informationsystem.resourceregistry.api.contexts;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.gcube.informationsystem.contexts.reference.entities.Context;
|
||||||
|
import org.gcube.informationsystem.contexts.reference.relations.IsParentOf;
|
||||||
|
import org.gcube.informationsystem.tree.NodeInformation;
|
||||||
|
|
||||||
|
public class ContextInformation implements NodeInformation<Context> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIdentifier(Context context) {
|
||||||
|
return context.getID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getParentIdentifiers(Context root, Context context) {
|
||||||
|
Set<String> set = new LinkedHashSet<>();
|
||||||
|
if(root !=null && context.getID().compareTo(root.getID())==0) {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
IsParentOf parent = context.getParent();
|
||||||
|
if(parent!=null) {
|
||||||
|
set.add(parent.getSource().getID().toString());
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ public class ContextUtility {
|
||||||
|
|
||||||
private static Logger logger = LoggerFactory.getLogger(ContextUtility.class);
|
private static Logger logger = LoggerFactory.getLogger(ContextUtility.class);
|
||||||
|
|
||||||
// public static Set<UUID> getContextUUIDSe(String jsonArray) throws IOException {
|
// public static Set<UUID> getContextUUIDSet(String jsonArray) throws IOException {
|
||||||
// ObjectMapper mapper = ElementMapper.getObjectMapper();
|
// ObjectMapper mapper = ElementMapper.getObjectMapper();
|
||||||
// JavaType type = mapper.getTypeFactory().constructCollectionType(HashSet.class, UUID.class);
|
// JavaType type = mapper.getTypeFactory().constructCollectionType(HashSet.class, UUID.class);
|
||||||
// return mapper.readValue(jsonArray, type);
|
// return mapper.readValue(jsonArray, type);
|
||||||
|
@ -33,7 +33,7 @@ public class ContextUtility {
|
||||||
// Set<UUID> uuids = getContextUUIDSet(jsonArray);
|
// Set<UUID> uuids = getContextUUIDSet(jsonArray);
|
||||||
// return getContextFullNameSet(uuids);
|
// return getContextFullNameSet(uuids);
|
||||||
// }
|
// }
|
||||||
//
|
|
||||||
public static Map<UUID, String> getContextMap(String objectnode) throws JsonParseException, JsonMappingException, IOException{
|
public static Map<UUID, String> getContextMap(String objectnode) throws JsonParseException, JsonMappingException, IOException{
|
||||||
ObjectMapper mapper = ElementMapper.getObjectMapper();
|
ObjectMapper mapper = ElementMapper.getObjectMapper();
|
||||||
JavaType type = mapper.getTypeFactory().constructMapType(HashMap.class, UUID.class, String.class);
|
JavaType type = mapper.getTypeFactory().constructMapType(HashMap.class, UUID.class, String.class);
|
||||||
|
@ -49,13 +49,12 @@ public class ContextUtility {
|
||||||
return uuids;
|
return uuids;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<String> getContextFullNameSet(Collection<UUID> uuids) throws Exception {
|
public static Set<String> getContextFullNameSet(ContextCache contextCache, Collection<UUID> uuids) throws Exception {
|
||||||
ContextCache contextCache = ContextCache.getInstance();
|
|
||||||
Set<String> contextFullNames = new HashSet<>();
|
Set<String> contextFullNames = new HashSet<>();
|
||||||
try {
|
try {
|
||||||
Map<UUID, String> uuidToContextFullNameAssociation = contextCache.getUUIDToContextFullNameAssociation();
|
Map<UUID, String> uuidToContextFullNameAssociation = contextCache.getUUIDToContextFullNameAssociation();
|
||||||
if(!uuidToContextFullNameAssociation.keySet().containsAll(uuids)) {
|
if(!uuidToContextFullNameAssociation.keySet().containsAll(uuids)) {
|
||||||
logger.debug("{} does not contain all the contexts identified by the following UUID list. Trying to invalidate the cache", ContextCache.class.getSimpleName(), uuids);
|
logger.debug("{} does not contain all the contexts identified by the following UUID list {}. Trying to invalidate the cache", ContextCache.class.getSimpleName(), uuids);
|
||||||
contextCache.cleanCache();
|
contextCache.cleanCache();
|
||||||
uuidToContextFullNameAssociation = contextCache.getUUIDToContextFullNameAssociation();
|
uuidToContextFullNameAssociation = contextCache.getUUIDToContextFullNameAssociation();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package org.gcube.informationsystem.resourceregistry.api.request;
|
||||||
|
|
||||||
|
import org.gcube.informationsystem.base.reference.IdentifiableElement;
|
||||||
|
import org.gcube.informationsystem.model.reference.properties.Metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
|
*/
|
||||||
|
public class BaseRequestInfo implements RequestInfo {
|
||||||
|
|
||||||
|
public static final Integer DEFAULT_OFFSET = 0;
|
||||||
|
public static final Integer DEFAULT_LIMIT = 10;
|
||||||
|
|
||||||
|
public static final Integer UNBOUNDED_LIMIT = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The offset parameter indicates the starting position of the result.
|
||||||
|
*/
|
||||||
|
protected Integer offset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To get unlimited results the limit query parameters must be set to -1.
|
||||||
|
* If the results are too much the operation could have a timeout.
|
||||||
|
*/
|
||||||
|
protected Integer limit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track if the request requested to include {@link Metadata}
|
||||||
|
*/
|
||||||
|
protected boolean includeMeta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track if the request requested to include {@link Metadata} in all
|
||||||
|
* {@link IdentifiableElement} or just in the root instance
|
||||||
|
*/
|
||||||
|
protected boolean allMeta;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track if hierarchicalMode has been requested
|
||||||
|
*/
|
||||||
|
protected boolean hierarchicalMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Track if the request requested to include contexts
|
||||||
|
*/
|
||||||
|
protected boolean includeContexts;
|
||||||
|
|
||||||
|
public BaseRequestInfo() {
|
||||||
|
this(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseRequestInfo(Integer offset, Integer limit) {
|
||||||
|
this.offset = offset;
|
||||||
|
this.limit = limit;
|
||||||
|
this.includeMeta = false;
|
||||||
|
this.allMeta = false;
|
||||||
|
this.hierarchicalMode = false;
|
||||||
|
this.includeContexts = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getLimit() {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLimit(Integer limit) {
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getOffset() {
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOffset(Integer offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean includeMeta() {
|
||||||
|
return includeMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIncludeMeta(boolean includeMeta) {
|
||||||
|
this.includeMeta = includeMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allMeta() {
|
||||||
|
return allMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAllMeta(boolean allMeta) {
|
||||||
|
this.allMeta = allMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHierarchicalMode() {
|
||||||
|
return hierarchicalMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHierarchicalMode(boolean hierarchicalMode) {
|
||||||
|
this.hierarchicalMode = hierarchicalMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean includeContexts() {
|
||||||
|
return includeContexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIncludeContexts(boolean includeContexts) {
|
||||||
|
this.includeContexts = includeContexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.gcube.informationsystem.resourceregistry.api.request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
|
*/
|
||||||
|
public interface RequestInfo {
|
||||||
|
|
||||||
|
public Integer getLimit();
|
||||||
|
|
||||||
|
public void setLimit(Integer limit);
|
||||||
|
|
||||||
|
public Integer getOffset();
|
||||||
|
|
||||||
|
public void setOffset(Integer offset);
|
||||||
|
|
||||||
|
public boolean includeMeta();
|
||||||
|
|
||||||
|
public void setIncludeMeta(boolean includeMeta);
|
||||||
|
|
||||||
|
public boolean allMeta();
|
||||||
|
|
||||||
|
public void setAllMeta(boolean allMeta);
|
||||||
|
|
||||||
|
public boolean isHierarchicalMode();
|
||||||
|
|
||||||
|
public void setHierarchicalMode(boolean hierarchicalMode);
|
||||||
|
|
||||||
|
public boolean includeContexts();
|
||||||
|
|
||||||
|
public void setIncludeContexts(boolean includeContexts);
|
||||||
|
|
||||||
|
}
|
|
@ -18,7 +18,13 @@ public class AccessPath {
|
||||||
public static final String RAW_QUERY_PARAMETER_DEFAULT_VALUE = "false";
|
public static final String RAW_QUERY_PARAMETER_DEFAULT_VALUE = "false";
|
||||||
|
|
||||||
public static final String HIERARCHICAL_MODE_QUERY_PARAMETER = InstancePath.HIERARCHICAL_MODE_QUERY_PARAMETER;
|
public static final String HIERARCHICAL_MODE_QUERY_PARAMETER = InstancePath.HIERARCHICAL_MODE_QUERY_PARAMETER;
|
||||||
public static final String INCLUDE_CONTEXTS_IN_HEADER_QUERY_PARAMETER = InstancePath.INCLUDE_CONTEXTS_IN_HEADER_QUERY_PARAMETER;
|
public static final String INCLUDE_CONTEXTS_QUERY_PARAMETER = InstancePath.INCLUDE_CONTEXTS_QUERY_PARAMETER;
|
||||||
|
public static final String INCLUDE_META_QUERY_PARAMETER = InstancePath.INCLUDE_META_QUERY_PARAMETER;
|
||||||
|
public static final String INCLUDE_META_IN_ALL_INSTANCES_QUERY_PARAMETER = InstancePath.INCLUDE_META_IN_ALL_INSTANCES_QUERY_PARAMETER;
|
||||||
|
|
||||||
|
public static final String OFFSET_QUERY_PARAMETER = InstancePath.OFFSET_QUERY_PARAMETER;
|
||||||
|
public static final String LIMIT_QUERY_PARAMETER = InstancePath.LIMIT_QUERY_PARAMETER;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used only in getRelated() function
|
* Used only in getRelated() function
|
||||||
|
|
|
@ -9,4 +9,8 @@ public class ContextPath {
|
||||||
|
|
||||||
public static final String CURRENT_CONTEXT_PATH_PART = "CURRENT_CONTEXT";
|
public static final String CURRENT_CONTEXT_PATH_PART = "CURRENT_CONTEXT";
|
||||||
|
|
||||||
|
public static final String INCLUDE_META_QUERY_PARAMETER = InstancePath.INCLUDE_META_QUERY_PARAMETER;
|
||||||
|
public static final String OFFSET_QUERY_PARAMETER = InstancePath.OFFSET_QUERY_PARAMETER;
|
||||||
|
public static final String LIMIT_QUERY_PARAMETER = InstancePath.LIMIT_QUERY_PARAMETER;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package org.gcube.informationsystem.resourceregistry.api.rest;
|
package org.gcube.informationsystem.resourceregistry.api.rest;
|
||||||
|
|
||||||
|
import org.gcube.informationsystem.base.reference.IdentifiableElement;
|
||||||
|
import org.gcube.informationsystem.model.reference.properties.Metadata;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
*/
|
*/
|
||||||
|
@ -7,10 +10,26 @@ public class InstancePath {
|
||||||
|
|
||||||
public static final String INSTANCES_PATH_PART = "instances";
|
public static final String INSTANCES_PATH_PART = "instances";
|
||||||
|
|
||||||
|
public static final String OFFSET_QUERY_PARAMETER = "offset";
|
||||||
|
public static final String LIMIT_QUERY_PARAMETER = "limit";
|
||||||
|
|
||||||
public static final String POLYMORPHIC_QUERY_PARAMETER = "polymorphic";
|
public static final String POLYMORPHIC_QUERY_PARAMETER = "polymorphic";
|
||||||
|
|
||||||
public static final String HIERARCHICAL_MODE_QUERY_PARAMETER = "hierarchical";
|
public static final String HIERARCHICAL_MODE_QUERY_PARAMETER = "hierarchical";
|
||||||
|
|
||||||
public static final String INCLUDE_CONTEXTS_IN_HEADER_QUERY_PARAMETER = "includeContextsInHeader";
|
public static final String INCLUDE_CONTEXTS_QUERY_PARAMETER = "includeContexts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to include {@link Metadata} in {@link IdentifiableElement} root instance
|
||||||
|
*/
|
||||||
|
public static final String INCLUDE_META_QUERY_PARAMETER = "includeMeta";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request to include {@link Metadata} in all {@link IdentifiableElement}
|
||||||
|
* instance.
|
||||||
|
* It must be used in conjunction with {@link #INCLUDE_META_QUERY_PARAMETER}
|
||||||
|
* If {@link #INCLUDE_META_QUERY_PARAMETER} is false it has no meaning
|
||||||
|
*/
|
||||||
|
public static final String INCLUDE_META_IN_ALL_INSTANCES_QUERY_PARAMETER = "allMeta";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ public class QueryTemplatePath {
|
||||||
|
|
||||||
public static final String QUERY_TEMPLATES_PATH_PART = "query-templates";
|
public static final String QUERY_TEMPLATES_PATH_PART = "query-templates";
|
||||||
|
|
||||||
|
public static final String INCLUDE_META_QUERY_PARAMETER = InstancePath.INCLUDE_META_QUERY_PARAMETER;
|
||||||
|
|
||||||
|
public static final String INCLUDE_META_IN_ALL_INSTANCES_QUERY_PARAMETER = InstancePath.INCLUDE_META_IN_ALL_INSTANCES_QUERY_PARAMETER;
|
||||||
|
|
||||||
}
|
}
|
|
@ -24,4 +24,8 @@ public class SharingPath {
|
||||||
*/
|
*/
|
||||||
public static final String FORCE_ADD_TO_CONTEXT_QUERY_PARAMETER = "forceAddToContext";
|
public static final String FORCE_ADD_TO_CONTEXT_QUERY_PARAMETER = "forceAddToContext";
|
||||||
|
|
||||||
|
public static final String INCLUDE_CONTEXTS_QUERY_PARAMETER = InstancePath.INCLUDE_CONTEXTS_QUERY_PARAMETER;
|
||||||
|
public static final String INCLUDE_META_QUERY_PARAMETER = InstancePath.INCLUDE_META_QUERY_PARAMETER;
|
||||||
|
public static final String INCLUDE_META_IN_ALL_INSTANCES_QUERY_PARAMETER = InstancePath.INCLUDE_META_IN_ALL_INSTANCES_QUERY_PARAMETER;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ public class TypePath {
|
||||||
|
|
||||||
public static final String TYPES_PATH_PART = "types";
|
public static final String TYPES_PATH_PART = "types";
|
||||||
|
|
||||||
public static final String POLYMORPHIC_QUERY_PARAMETER = "polymorphic";
|
public static final String POLYMORPHIC_QUERY_PARAMETER = InstancePath.POLYMORPHIC_QUERY_PARAMETER;
|
||||||
|
|
||||||
|
public static final String INCLUDE_META_QUERY_PARAMETER = InstancePath.INCLUDE_META_QUERY_PARAMETER;
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
package org.gcube.informationsystem.resourceregistry.api.utils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.gcube.com.fasterxml.jackson.annotation.JsonTypeName;
|
|
||||||
import org.gcube.com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import org.gcube.com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import org.gcube.informationsystem.base.reference.Element;
|
|
||||||
import org.gcube.informationsystem.base.reference.IdentifiableElement;
|
|
||||||
import org.gcube.informationsystem.serialization.ElementMapper;
|
|
||||||
import org.gcube.informationsystem.types.TypeMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Luca Frosini (ISTI - CNR)
|
|
||||||
*/
|
|
||||||
public abstract class Utility {
|
|
||||||
|
|
||||||
public static String getClassFromJsonNode(JsonNode jsonNode){
|
|
||||||
return jsonNode.get(Element.CLASS_PROPERTY).asText();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getClassFromJsonString(String json) throws JsonProcessingException, IOException{
|
|
||||||
JsonNode jsonNode = ElementMapper.getObjectMapper().readTree(json);
|
|
||||||
return getClassFromJsonNode(jsonNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getUUIDStringFromJsonNode(JsonNode jsonNode){
|
|
||||||
return jsonNode.get(IdentifiableElement.HEADER_PROPERTY).get(Element.CLASS_PROPERTY).asText();
|
|
||||||
}
|
|
||||||
public static UUID getUUIDFromJsonNode(JsonNode jsonNode){
|
|
||||||
String uuidString = getUUIDStringFromJsonNode(jsonNode);
|
|
||||||
return UUID.fromString(uuidString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getUUIDStringFromJsonString(String json) throws JsonProcessingException, IOException{
|
|
||||||
JsonNode jsonNode = ElementMapper.getObjectMapper().readTree(json);
|
|
||||||
return getUUIDStringFromJsonNode(jsonNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UUID getUUIDFromJsonString(String json) throws JsonProcessingException, IOException{
|
|
||||||
JsonNode jsonNode = ElementMapper.getObjectMapper().readTree(json);
|
|
||||||
return getUUIDFromJsonNode(jsonNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO move somewhere else, probably in Element
|
|
||||||
|
|
||||||
public static String getTypeName(Element element){
|
|
||||||
return getTypeName(element.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <E extends Element> String getTypeName(Class<E> clz){
|
|
||||||
if(!clz.isInterface()){
|
|
||||||
return clz.getAnnotation(JsonTypeName.class).value();
|
|
||||||
}else {
|
|
||||||
return TypeMapper.getType(clz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@ import org.gcube.common.authorization.utils.secret.SecretUtility;
|
||||||
import org.gcube.common.keycloak.KeycloakClientFactory;
|
import org.gcube.common.keycloak.KeycloakClientFactory;
|
||||||
import org.gcube.common.keycloak.model.TokenResponse;
|
import org.gcube.common.keycloak.model.TokenResponse;
|
||||||
import org.gcube.common.scope.api.ScopeProvider;
|
import org.gcube.common.scope.api.ScopeProvider;
|
||||||
import org.gcube.informationsystem.model.reference.properties.Header;
|
import org.gcube.informationsystem.model.reference.properties.Metadata;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -112,7 +112,7 @@ public class ContextTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getUser() {
|
public static String getUser() {
|
||||||
String user = Header.UNKNOWN_USER;
|
String user = Metadata.UNKNOWN_USER;
|
||||||
try {
|
try {
|
||||||
user = SecretManagerProvider.instance.get().getUser().getUsername();
|
user = SecretManagerProvider.instance.get().getUser().getUsername();
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.gcube.informationsystem.resourceregistry.api.contexts;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.gcube.informationsystem.contexts.reference.entities.Context;
|
||||||
|
import org.gcube.informationsystem.resourceregistry.api.exceptions.ResourceRegistryException;
|
||||||
|
import org.gcube.informationsystem.serialization.ElementMapper;
|
||||||
|
import org.gcube.informationsystem.tree.Node;
|
||||||
|
import org.gcube.informationsystem.tree.NodeElaborator;
|
||||||
|
import org.gcube.informationsystem.tree.Tree;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Luca Frosini (ISTI - CNR)
|
||||||
|
*/
|
||||||
|
public class ContextCacheTest{
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ContextCacheTest.class);
|
||||||
|
|
||||||
|
protected File getTypesDirectory() throws Exception {
|
||||||
|
URL logbackFileURL = ContextCacheTest.class.getClassLoader().getResource("logback-test.xml");
|
||||||
|
File logbackFile = new File(logbackFileURL.toURI());
|
||||||
|
File resourcesDirectory = logbackFile.getParentFile();
|
||||||
|
return new File(resourcesDirectory, "contexts");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String readFile(File file) throws IOException {
|
||||||
|
byte[] encoded = Files.readAllBytes(file.toPath());
|
||||||
|
return new String(encoded, Charset.defaultCharset());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Context> getContexts() throws Exception {
|
||||||
|
File typesDirectory = getTypesDirectory();
|
||||||
|
File file = new File(typesDirectory, "contexts.json");
|
||||||
|
String json = readFile(file);
|
||||||
|
List<Context> contexts = ElementMapper.unmarshalList(Context.class, json);
|
||||||
|
return contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test() throws Exception {
|
||||||
|
List<Context> contexts = getContexts();
|
||||||
|
ContextCache contextCache = new ContextCache();
|
||||||
|
contextCache.setContexts(contexts);
|
||||||
|
contextCache.setContextCacheRenewal(new ContextCacheRenewal() {
|
||||||
|
@Override
|
||||||
|
public List<Context> renew() throws ResourceRegistryException {
|
||||||
|
try {
|
||||||
|
return getContexts();
|
||||||
|
}catch (Exception e) {
|
||||||
|
throw new ResourceRegistryException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Tree<Context> contextTree = contextCache.getContextsTree();
|
||||||
|
contextTree.elaborate(new NodeElaborator<Context>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void elaborate(Node<Context> node, int level) throws Exception {
|
||||||
|
StringBuffer stringBuffer = new StringBuffer();
|
||||||
|
for (int i = 0; i < level; ++i) {
|
||||||
|
stringBuffer.append(Node.INDENTATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
Context context = node.getNodeElement();
|
||||||
|
UUID uuid = context.getID();
|
||||||
|
String fullName = contextCache.getContextFullNameByUUID(uuid);
|
||||||
|
logger.info("{}- {} (ID:{})", stringBuffer.toString(), fullName, uuid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1 @@
|
||||||
|
[{"type":"Context","id":"9a7752f6-ca01-4a89-a9dd-f36aeb2cbf50","name":"gcube","parent":null,"children":[{"type":"IsParentOf","id":"b4bdbd3e-87c4-43d1-b642-532f6a30ad44","target":{"type":"Context","id":"4e6adfe6-ab93-47c0-a532-339bb92ad07a","name":"devNext"}},{"type":"IsParentOf","id":"542179cb-36fc-4d4e-9771-b55d3b2bd301","target":{"type":"Context","id":"2a9f4a4b-5f3e-4eee-9630-e2f7b2e58c34","name":"devsec"}}]},{"type":"Context","id":"4e6adfe6-ab93-47c0-a532-339bb92ad07a","name":"devNext","parent":{"type":"IsParentOf","id":"b4bdbd3e-87c4-43d1-b642-532f6a30ad44","source":{"type":"Context","id":"9a7752f6-ca01-4a89-a9dd-f36aeb2cbf50","name":"gcube"}},"children":[{"type":"IsParentOf","id":"01cc9e06-475b-43a4-a4cd-7f5070f0dc65","target":{"type":"Context","id":"c7f3af7e-7e8c-406e-8d5a-cd11c82b5fa3","name":"NextNext"}}]},{"type":"Context","id":"2a9f4a4b-5f3e-4eee-9630-e2f7b2e58c34","name":"devsec","parent":{"type":"IsParentOf","id":"542179cb-36fc-4d4e-9771-b55d3b2bd301","source":{"type":"Context","id":"9a7752f6-ca01-4a89-a9dd-f36aeb2cbf50","name":"gcube"}},"children":[{"type":"IsParentOf","id":"e28b8e16-84d5-4cd9-a5a1-8a55fa371d0c","target":{"type":"Context","id":"a3e40b10-01d0-11ed-8d67-f3498769ebff","name":"CCP"}},{"type":"IsParentOf","id":"e46696d5-3290-4d7a-b22a-71940bda7ec0","target":{"type":"Context","id":"8efe07f5-de24-49f9-a2fb-fbfdcfda8c91","name":"devVRE"}}]},{"type":"Context","id":"c7f3af7e-7e8c-406e-8d5a-cd11c82b5fa3","name":"NextNext","parent":{"type":"IsParentOf","id":"01cc9e06-475b-43a4-a4cd-7f5070f0dc65","source":{"type":"Context","id":"4e6adfe6-ab93-47c0-a532-339bb92ad07a","name":"devNext"}},"children":[]},{"type":"Context","id":"a3e40b10-01d0-11ed-8d67-f3498769ebff","name":"CCP","parent":{"type":"IsParentOf","id":"e28b8e16-84d5-4cd9-a5a1-8a55fa371d0c","source":{"type":"Context","id":"2a9f4a4b-5f3e-4eee-9630-e2f7b2e58c34","name":"devsec"}},"children":[]},{"type":"Context","id":"8efe07f5-de24-49f9-a2fb-fbfdcfda8c91","name":"devVRE","parent":{"type":"IsParentOf","id":"e46696d5-3290-4d7a-b22a-71940bda7ec0","source":{"type":"Context","id":"2a9f4a4b-5f3e-4eee-9630-e2f7b2e58c34","name":"devsec"}},"children":[]}]
|
Loading…
Reference in New Issue