diff --git a/api/pom.xml b/api/pom.xml index 3011f5222a..3583772cd7 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -85,6 +85,7 @@ fluent-hc 4.5.6 + org.vivoweb vitro-dependencies @@ -111,9 +112,15 @@ test - org.easymock - easymock - 3.5 + org.mockito + mockito-core + 4.3.1 + test + + + org.mockito + mockito-inline + 4.3.1 test diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/AbstractPool.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/AbstractPool.java new file mode 100644 index 0000000000..8a23a4e0b5 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/AbstractPool.java @@ -0,0 +1,203 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.RDF_TYPE; +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.FULL_UNION; +import static edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader.toJavaUri; +import static java.lang.String.format; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentNavigableMap; +import java.util.concurrent.ConcurrentSkipListMap; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.jena.ontology.OntModel; +import org.apache.jena.rdf.model.NodeIterator; +import org.apache.jena.rdf.model.Property; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Poolable; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess; +import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoaderException; + +public abstract class AbstractPool, P extends Pool> implements Pool { + + private final Log log = LogFactory.getLog(this.getClass()); + + private static final Object mutex = new Object(); + + private ConcurrentNavigableMap components; + private ServletContext ctx; + private ConfigurationBeanLoader loader; + private ContextModelAccess modelAccess; + private OntModel dynamicAPIModel; + private ConcurrentLinkedQueue obsoleteComponents; + + protected AbstractPool() { + components = new ConcurrentSkipListMap<>(); + obsoleteComponents = new ConcurrentLinkedQueue<>(); + } + + protected ConcurrentNavigableMap getComponents() { + return components; + } + + public abstract P getPool(); + + public abstract C getDefault(); + + public abstract Class getType(); + + public C get(K key) { + C component = components.get(key); + + if (component == null) { + return getDefault(); + } + + component.addClient(); + return component; + } + + public void printKeys() { + for (Map.Entry entry : components.entrySet()) { + log.debug(format("%s in pool: '%s'", getType().getName(), entry.getKey())); + } + } + + public void add(String uri, C component) { + K key = component.getKey(); + log.info(format("Adding component %s with URI %s", key, uri)); + if (isInModel(uri)) { + synchronized (mutex) { + C oldComponent = components.put(key, component); + if (oldComponent != null) { + obsoleteComponents.add(oldComponent); + unloadObsoleteComponents(); + } + } + } else { + throw new RuntimeException(format("%s %s with URI %s not found in model. Not adding to pool.", + getType().getName(), key, uri)); + } + } + + public void remove(String uri, K key) { + log.info(format("Removing component %s with URI %s", key, uri)); + if (!isInModel(uri)) { + synchronized (mutex) { + C oldComponent = components.remove(key); + if (oldComponent != null) { + obsoleteComponents.add(oldComponent); + unloadObsoleteComponents(); + } + } + } else { + throw new RuntimeException(format("%s %s with URI %s still exists in model. Not removing from pool.", + getType().getName(), key, uri)); + } + } + + private boolean isInModel(String uri) { + Resource s = dynamicAPIModel.getResource(uri); + Property p = dynamicAPIModel.getProperty(RDF_TYPE); + + String javaUri = toJavaUri(getType()); + + NodeIterator nit = dynamicAPIModel.listObjectsOfProperty(s, p); + while (nit.hasNext()) { + RDFNode node = nit.next(); + if (node.isResource() && node.toString().replace("#", ".").equals(javaUri)) { + return true; + } + } + + return false; + } + + public void reload(String uri) { + try { + add(uri, loader.loadInstance(uri, getType())); + } catch (ConfigurationBeanLoaderException e) { + throw new RuntimeException(format("Failed to reload %s with URI %s.", getType().getName(), uri)); + } + } + + public synchronized void reload() { + if (ctx == null) { + log.error(format("Context is null. Can't reload %s.", this.getClass().getName())); + return; + } + if (loader == null) { + log.error(format("Loader is null. Can't reload %s.", this.getClass().getName())); + return; + } + ConcurrentNavigableMap newActions = new ConcurrentSkipListMap<>(); + loadComponents(newActions); + ConcurrentNavigableMap oldActions = this.components; + components = newActions; + for (Map.Entry component : oldActions.entrySet()) { + obsoleteComponents.add(component.getValue()); + oldActions.remove(component.getKey()); + } + unloadObsoleteComponents(); + } + + public void init(ServletContext ctx) { + this.ctx = ctx; + modelAccess = ModelAccess.on(ctx); + dynamicAPIModel = modelAccess.getOntModel(FULL_UNION); + loader = new ConfigurationBeanLoader(dynamicAPIModel, ctx); + log.debug("Context Initialization ..."); + loadComponents(components); + } + + private void loadComponents(ConcurrentNavigableMap components) { + Set newActions = loader.loadEach(getType()); + log.debug(format("Context Initialization. %s %s(s) currently loaded.", components.size(), getType().getName())); + for (C component : newActions) { + if (component.isValid()) { + components.put(component.getKey(), component); + } else { + log.error(format("%s with rpcName %s is invalid.", getType().getName(), component.getKey())); + } + } + log.debug(format("Context Initialization finished. %s %s(s) loaded.", components.size(), getType().getName())); + } + + private void unloadObsoleteComponents() { + for (C component : obsoleteComponents) { + if (!isComponentInUse(component)) { + component.dereference(); + obsoleteComponents.remove(component); + } + } + } + + private boolean isComponentInUse(C component) { + if (!component.hasClients()) { + return false; + } + component.removeDeadClients(); + if (!component.hasClients()) { + return false; + } + return true; + } + + public long obsoleteCount() { + return obsoleteComponents.size(); + } + + public long count() { + return components.size(); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/ActionPool.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/ActionPool.java index 66472ce959..633fcff567 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/ActionPool.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/ActionPool.java @@ -1,99 +1,29 @@ package edu.cornell.mannlib.vitro.webapp.dynapi; -import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.FULL_UNION; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import javax.servlet.ServletContext; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.jena.ontology.OntModel; - import edu.cornell.mannlib.vitro.webapp.dynapi.components.Action; import edu.cornell.mannlib.vitro.webapp.dynapi.components.DefaultAction; -import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess; -import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; -import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader; -public class ActionPool { +public class ActionPool extends AbstractPool { - private static ActionPool INSTANCE = null; - private static final Log log = LogFactory.getLog(ActionPool.class); - - private Map actions; - private ServletContext ctx; - private ConfigurationBeanLoader loader; - private ContextModelAccess modelAccess; - private OntModel dynamicAPIModel; + private static ActionPool INSTANCE = new ActionPool(); - private ActionPool(){ - this.actions = new HashMap(); - INSTANCE = this; - } - public static ActionPool getInstance() { - if (INSTANCE == null) { - INSTANCE = new ActionPool(); - } return INSTANCE; } - - public Action getByName(String name) { - Action action = actions.get(name); - if (action == null) { - action = new DefaultAction(); - } - return action; - } - - public void printActionNames() { - for (Map.Entry entry : actions.entrySet()) { - log.debug("Action in pool: '" + entry.getKey() + "'"); - } - } - - public void reload() { - if (ctx == null ) { - log.error("Context is null. Can't reload action pool."); - return; - } - if (loader == null ) { - log.error("Loader is null. Can't reload action pool."); - return; - } - for (Map.Entry entry : actions.entrySet()) { - entry.getValue().dereference(); - } - actions.clear(); - loadActions(); - } - public void init(ServletContext ctx) { - this.ctx = ctx; - modelAccess = ModelAccess.on(ctx); - dynamicAPIModel = modelAccess.getOntModel(FULL_UNION); - loader = new ConfigurationBeanLoader( dynamicAPIModel, ctx); - log.debug("Context Initialization ..."); - loadActions(); + @Override + public ActionPool getPool() { + return getInstance(); } - private void loadActions() { - Set actions = loader.loadEach(Action.class); - log.debug("Context Initialization. actions loaded: " + actions.size()); - for (Action action : actions) { - if (action.isValid()) { - add(action); - } else { - log.error("Action with rpcName " + action.getName() + " is invalid."); - } - } - log.debug("Context Initialization finished. " + actions.size() + " actions loaded."); + @Override + public Action getDefault() { + return new DefaultAction(); } - private void add(Action action) { - actions.put(action.getName(), action); + @Override + public Class getType() { + return Action.class; } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/OperationData.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/OperationData.java index 911ab55a11..5313ad1c3d 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/OperationData.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/OperationData.java @@ -7,8 +7,8 @@ public class OperationData { - private Map params; - private ServletContext context; + private final Map params; + private final ServletContext context; public OperationData(HttpServletRequest request) { params = request.getParameterMap(); @@ -20,10 +20,7 @@ public ServletContext getContext() { } public boolean has(String paramName) { - if (params.containsKey(paramName)) { - return true; - } - return false; + return params.containsKey(paramName); } public String[] get(String paramName) { diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/Pool.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/Pool.java new file mode 100644 index 0000000000..e07d27c031 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/Pool.java @@ -0,0 +1,27 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import javax.servlet.ServletContext; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Poolable; + +public interface Pool> { + + public C get(K key); + + public void printKeys(); + + public void add(String uri, C component); + + public void remove(String uri, K key); + + public void reload(); + + public void reload(String uri); + + public void init(ServletContext ctx); + + public long obsoleteCount(); + + public long count(); + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/RESTEndpoint.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/RESTEndpoint.java index ebd1b4ab4b..53ceddfb44 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/RESTEndpoint.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/RESTEndpoint.java @@ -1,5 +1,8 @@ package edu.cornell.mannlib.vitro.webapp.dynapi; +import static edu.cornell.mannlib.vitro.webapp.dynapi.request.RequestPath.REST_BASE_PATH; +import static java.lang.String.format; + import java.io.IOException; import javax.servlet.ServletException; @@ -11,53 +14,112 @@ import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Action; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.OperationResult; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Resource; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.ResourceKey; +import edu.cornell.mannlib.vitro.webapp.dynapi.request.RequestPath; + +@WebServlet(name = "RESTEndpoint", urlPatterns = { REST_BASE_PATH + "/*" }) +public class RESTEndpoint extends VitroHttpServlet { -@WebServlet(name = "RESTEndpoint", urlPatterns = { "/api/rest/*" }) -public class RESTEndpoint extends VitroHttpServlet { + private static final Log log = LogFactory.getLog(RESTEndpoint.class); - private static final long serialVersionUID = 1L; - private static final Log log = LogFactory.getLog(RESTEndpoint.class); private ResourcePool resourcePool = ResourcePool.getInstance(); - - + private ActionPool actionPool = ActionPool.getInstance(); + @Override - public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - if (request.getMethod().equalsIgnoreCase("PATCH")){ - doPatch(request, response); - } else { - super.service(request, response); - } + public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if (request.getMethod().equalsIgnoreCase("PATCH")) { + doPatch(request, response); + } else { + super.service(request, response); + } } - + @Override - public void doPost( HttpServletRequest request, HttpServletResponse response ) { + public void doPost(HttpServletRequest request, HttpServletResponse response) { process(request, response); } - + @Override - public void doGet( HttpServletRequest request, HttpServletResponse response ) { + public void doGet(HttpServletRequest request, HttpServletResponse response) { process(request, response); } - + @Override - public void doDelete( HttpServletRequest request, HttpServletResponse response ) { + public void doDelete(HttpServletRequest request, HttpServletResponse response) { process(request, response); } - + @Override - public void doPut( HttpServletRequest request, HttpServletResponse response ) { + public void doPut(HttpServletRequest request, HttpServletResponse response) { process(request, response); } - - public void doPatch( HttpServletRequest request, HttpServletResponse response ) { + + public void doPatch(HttpServletRequest request, HttpServletResponse response) { process(request, response); } - + private void process(HttpServletRequest request, HttpServletResponse response) { - String requestURL = request.getRequestURI(); - //String resourceName = requestURL.substring(requestURL.lastIndexOf("/") + 1 ); - //Resource resource = resourcePool.getByName(resourceName); - log.debug(request.getMethod()); + RequestPath requestPath = RequestPath.from(request); + + if (requestPath.isValid()) { + ResourceKey resourceKey = ResourceKey.of(requestPath.getResourceName(), requestPath.getResourceVersion()); + + if (log.isDebugEnabled()) { + resourcePool.printKeys(); + } + Resource resource = resourcePool.get(resourceKey); + String method = request.getMethod(); + + String actionName = null; + + if (requestPath.isCustomRestAction()) { + if (method.toUpperCase().equals("POST")) { + String customRestActionName = requestPath.getActionName(); + + try { + actionName = resource.getCustomRestActionByName(customRestActionName); + } catch (UnsupportedOperationException e) { + log.error(format("Custom REST action %s not implemented for resource %s", customRestActionName, + resource.getKey()), e); + OperationResult.notImplemented().prepareResponse(response); + return; + } finally { + resource.removeClient(); + } + } else { + OperationResult.notImplemented().prepareResponse(response); + resource.removeClient(); + return; + } + } else { + try { + actionName = resource.getActionNameByMethod(method); + } catch (UnsupportedOperationException e) { + log.error(format("Method %s not implemented for resource %s", method, resource.getKey()), e); + OperationResult.notImplemented().prepareResponse(response); + return; + } finally { + resource.removeClient(); + } + } + + if (log.isDebugEnabled()) { + actionPool.printKeys(); + } + Action action = actionPool.get(actionName); + OperationData input = new OperationData(request); + try { + OperationResult result = action.run(input); + result.prepareResponse(response); + } finally { + action.removeClient(); + } + } else { + OperationResult.notFound().prepareResponse(response); + } } - + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpoint.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpoint.java index ee31f35be3..f3ca8b7f31 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpoint.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpoint.java @@ -1,5 +1,7 @@ package edu.cornell.mannlib.vitro.webapp.dynapi; +import static edu.cornell.mannlib.vitro.webapp.dynapi.request.RequestPath.RPC_BASE_PATH; + import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -10,42 +12,48 @@ import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; import edu.cornell.mannlib.vitro.webapp.dynapi.components.Action; import edu.cornell.mannlib.vitro.webapp.dynapi.components.OperationResult; +import edu.cornell.mannlib.vitro.webapp.dynapi.request.RequestPath; + +@WebServlet(name = "RPCEndpoint", urlPatterns = { RPC_BASE_PATH + "/*" }) +public class RPCEndpoint extends VitroHttpServlet { -@WebServlet(name = "RPCEndpoint", urlPatterns = { "/api/rpc/*" }) -public class RPCEndpoint extends VitroHttpServlet { + private static final Log log = LogFactory.getLog(RESTEndpoint.class); - private static final long serialVersionUID = 1L; - private static final Log log = LogFactory.getLog(RPCEndpoint.class); private ActionPool actionPool = ActionPool.getInstance(); - + @Override - public void doGet( HttpServletRequest request, HttpServletResponse response ) { - process(request, response); + public void doGet(HttpServletRequest request, HttpServletResponse response) { + OperationResult.notImplemented().prepareResponse(response); } - + @Override - public void doPost( HttpServletRequest request, HttpServletResponse response ) { - process(request, response); + public void doPost(HttpServletRequest request, HttpServletResponse response) { + RequestPath requestPath = RequestPath.from(request); + if (requestPath.isValid()) { + if (log.isDebugEnabled()) { + actionPool.printKeys(); + } + Action action = actionPool.get(requestPath.getActionName()); + OperationData input = new OperationData(request); + try { + OperationResult result = action.run(input); + result.prepareResponse(response); + } finally { + action.removeClient(); + } + } else { + OperationResult.notFound().prepareResponse(response); + } } - + @Override - public void doDelete( HttpServletRequest request, HttpServletResponse response ) { - process(request, response); + public void doDelete(HttpServletRequest request, HttpServletResponse response) { + OperationResult.notImplemented().prepareResponse(response); } - + @Override - public void doPut( HttpServletRequest request, HttpServletResponse response ) { - process(request, response); - } - - private void process(HttpServletRequest request, HttpServletResponse response) { - String requestURL = request.getRequestURI(); - String actionName = requestURL.substring(requestURL.lastIndexOf("/") + 1 ); - log.debug(actionName); - actionPool.printActionNames(); - Action action = actionPool.getByName(actionName); - OperationData input = new OperationData(request); - OperationResult result = action.run(input); - result.prepareResponse(response); + public void doPut(HttpServletRequest request, HttpServletResponse response) { + OperationResult.notImplemented().prepareResponse(response); } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/ResourcePool.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/ResourcePool.java index 2b7863ae14..ea118b6f78 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/ResourcePool.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/ResourcePool.java @@ -1,60 +1,30 @@ package edu.cornell.mannlib.vitro.webapp.dynapi; -import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.FULL_UNION; - -import java.util.HashMap; -import java.util.Set; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Resource; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.ResourceKey; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.DefaultResource; -import javax.servlet.ServletContext; +public class ResourcePool extends VersionableAbstractPool { -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.jena.ontology.OntModel; + private static ResourcePool INSTANCE = new ResourcePool(); -import edu.cornell.mannlib.vitro.webapp.dynapi.components.Resource; -import edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccess; -import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; -import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader; - -public class ResourcePool { - - private static final Log log = LogFactory.getLog(ResourcePool.class); - private static ResourcePool INSTANCE; - private ContextModelAccess modelAccess; - private OntModel dynamicAPIModel; - private ConfigurationBeanLoader loader; - private HashMap resources; - private ServletContext ctx; - - private ResourcePool() { - this.resources = new HashMap(); - INSTANCE = this; - } public static ResourcePool getInstance() { - if (INSTANCE == null) { - INSTANCE = new ResourcePool(); - } return INSTANCE; } - public void init(ServletContext ctx) { - this.ctx = ctx; - modelAccess = ModelAccess.on(ctx); - dynamicAPIModel = modelAccess.getOntModel(FULL_UNION); - loader = new ConfigurationBeanLoader( dynamicAPIModel, ctx); - loadResources(); + @Override + public ResourcePool getPool() { + return getInstance(); } - - private void loadResources() { - Set resources = loader.loadEach(Resource.class); - log.debug("Context Initialization. resources created: " + resources.size()); - for (Resource resource : resources) { - add(resource); - } - log.debug("Context Initialization finished. " + resources.size() + " resources loaded."); + + @Override + public Resource getDefault() { + return new DefaultResource(); } - private void add(Resource resource) { - resources.put(resource.getName(), resource); + @Override + public Class getType() { + return Resource.class; } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/VersionableAbstractPool.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/VersionableAbstractPool.java new file mode 100644 index 0000000000..f3afd49bdc --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/VersionableAbstractPool.java @@ -0,0 +1,80 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import java.util.Map.Entry; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Version; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Versionable; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Versioned; + +public abstract class VersionableAbstractPool, P extends Pool> + extends AbstractPool { + + public C get(K key) { + C component = null; + Entry entry = getComponents().floorEntry(key); + if (entry != null) { + component = entry.getValue(); + String keyName = key.getName(); + Version keyVersion = key.getVersion(); + // ensure key name matches component key name + if (keyName.equals(component.getKey().getName())) { + boolean hasVersionMax = component.getVersionMax() != null; + boolean hasVersionMin = component.getVersionMin() != null; + if (hasVersionMax) { + // ensure key version is not greater than component version max + if (greaterThanVersionMax(component, keyVersion)) { + component = null; + } + } else if (hasVersionMin) { + // ensure key version specific values are respected + if (mismatchSpecificVersion(component, keyVersion)) { + component = null; + } + } + } else { + component = null; + } + } + + if (component == null) { + return getDefault(); + } + + component.addClient(); + return component; + } + + private boolean greaterThanVersionMax(C component, Version keyVersion) { + Version componentVersionMax = Version.of(component.getVersionMax()); + boolean majorVersionGreater = keyVersion.getMajor().compareTo(componentVersionMax.getMajor()) > 0; + + boolean minorVersionGreater = minorVersionSpecific(keyVersion) + && keyVersion.getMinor().compareTo(componentVersionMax.getMinor()) > 0; + + boolean patchVersionGreater = patchVersionSpecific(keyVersion) + && keyVersion.getPatch().compareTo(componentVersionMax.getPatch()) > 0; + + return majorVersionGreater || minorVersionGreater || patchVersionGreater; + } + + private boolean mismatchSpecificVersion(C component, Version keyVersion) { + Version componentVersionMin = Version.of(component.getVersionMin()); + + boolean mismatchMinorVersion = minorVersionSpecific(keyVersion) + && !keyVersion.getMinor().equals(componentVersionMin.getMinor()); + + boolean mismatchPatchVersion = patchVersionSpecific(keyVersion) + && !keyVersion.getPatch().equals(componentVersionMin.getPatch()); + + return mismatchMinorVersion || mismatchPatchVersion; + } + + private boolean minorVersionSpecific(Version keyVersion) { + return !keyVersion.getMinor().equals(Integer.MAX_VALUE); + } + + private boolean patchVersionSpecific(Version keyVersion) { + return !keyVersion.getPatch().equals(Integer.MAX_VALUE); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Action.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Action.java index 28a408e6ad..90673de29b 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Action.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Action.java @@ -1,21 +1,26 @@ package edu.cornell.mannlib.vitro.webapp.dynapi.components; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import edu.cornell.mannlib.vitro.webapp.dynapi.OperationData; import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; -public class Action implements RunnableComponent{ - - private static final Log log = LogFactory.getLog(Action.class); +public class Action implements Poolable, Operation, Link { private Step firstStep = null; private RPC rpc; + private Set clients = ConcurrentHashMap.newKeySet(); + + private Parameters providedParams = new Parameters(); + private Parameters requiredParams; + @Override public void dereference() { if (firstStep != null) { @@ -25,29 +30,90 @@ public void dereference() { rpc.dereference(); rpc = null; } - + public OperationResult run(OperationData input) { if (firstStep == null) { return new OperationResult(HttpServletResponse.SC_NOT_IMPLEMENTED); } return firstStep.run(input); } - + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#firstStep", minOccurs = 1, maxOccurs = 1) public void setStep(OperationalStep step) { this.firstStep = step; - } - + } + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#assignedRPC", minOccurs = 1, maxOccurs = 1) public void setRPC(RPC rpc) { this.rpc = rpc; } - - public String getName() { + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#providesParameter") + public void addProvidedParameter(Parameter param) { + providedParams.add(param); + } + + @Override + public String getKey() { return rpc.getName(); } + @Override public boolean isValid() { return true; } + + @Override + public void addClient() { + clients.add(Thread.currentThread().getId()); + } + + @Override + public void removeClient() { + clients.remove(Thread.currentThread().getId()); + } + + @Override + public void removeDeadClients() { + Map currentThreadIds = Thread + .getAllStackTraces() + .keySet() + .stream() + .collect(Collectors.toMap(Thread::getId, Thread::isAlive)); + for (Long client : clients) { + if (!currentThreadIds.containsKey(client) || currentThreadIds.get(client) == false) { + clients.remove(client); + } + } + } + + @Override + public boolean hasClients() { + return !clients.isEmpty(); + } + + @Override + public Set getNextLinks() { + return Collections.singleton(firstStep); + } + + @Override + public Parameters getRequiredParams() { + return requiredParams; + } + + public void computeScopes() { + requiredParams = Scopes.computeInitialRequirements(this); + } + + @Override + public Parameters getProvidedParams() { + return providedParams; + } + + @Override + public boolean isRoot() { + return true; + } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Condition.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Condition.java new file mode 100644 index 0000000000..395fb013d2 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Condition.java @@ -0,0 +1,5 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +public interface Condition extends ParameterInfo{ + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ConditionalStep.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ConditionalStep.java index f9d918a8ad..69d76ea91e 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ConditionalStep.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ConditionalStep.java @@ -1,10 +1,14 @@ package edu.cornell.mannlib.vitro.webapp.dynapi.components; +import java.util.Set; + import edu.cornell.mannlib.vitro.webapp.dynapi.OperationData; public class ConditionalStep implements Step { - @Override + private Condition condition; + + @Override public void dereference() { // TODO Auto-generated method stub @@ -15,4 +19,29 @@ public OperationResult run(OperationData input) { return null; } + @Override + public Set getNextLinks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Parameters getRequiredParams() { + return condition.getRequiredParams(); + } + + @Override + public Parameters getProvidedParams() { + return new Parameters(); + } + + @Override + public boolean isRoot() { + return false; + } + + public void setCondition(Condition condition) { + this.condition = condition; + } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/CustomAction.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/CustomRESTAction.java similarity index 86% rename from api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/CustomAction.java rename to api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/CustomRESTAction.java index 11d0d2a21c..6a62b14789 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/CustomAction.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/CustomRESTAction.java @@ -2,7 +2,7 @@ import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; -public class CustomAction implements Removable { +public class CustomRESTAction implements Removable { private String name; private RPC targetRPC; @@ -17,7 +17,7 @@ public String getName() { return name; } - @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#customActionName", minOccurs = 1, maxOccurs = 1) + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#customRESTActionName", minOccurs = 1, maxOccurs = 1) public void setName(String name) { this.name = name; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/DefaultResource.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/DefaultResource.java new file mode 100644 index 0000000000..0601d19cf5 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/DefaultResource.java @@ -0,0 +1,10 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class DefaultResource extends Resource { + + private static final Log log = LogFactory.getLog(DefaultResource.class); + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Link.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Link.java new file mode 100644 index 0000000000..2622c30a35 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Link.java @@ -0,0 +1,11 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +import java.util.Set; + +public interface Link extends ParameterInfo { + + public Set getNextLinks(); + + public boolean isRoot(); + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/N3Template.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/N3Template.java index f6a209c93c..062407eaad 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/N3Template.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/N3Template.java @@ -6,10 +6,13 @@ public class N3Template implements Template{ private String n3Text; + private Parameters requiredParams = new Parameters(); - @Override - public void dereference() { - + //region @Property Setters + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#requiresParameter") + public void addRequiredParameter(Parameter param) { + requiredParams.add(param); } @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#hasN3Text", minOccurs = 1, maxOccurs = 1) @@ -17,34 +20,29 @@ public void setN3Text(String n3Text) { this.n3Text = n3Text; } - @Override - public OperationResult run(OperationData input) { - - return null; - } + //endregion + + //region Getters @Override - public void addRequiredParameter(Parameter param) { - // TODO Auto-generated method stub - + public Parameters getRequiredParams() { + return requiredParams; } @Override - public void addProvidedParameter(Parameter param) { - // TODO Auto-generated method stub - + public Parameters getProvidedParams() { + return new Parameters(); } + //endregion + @Override - public Parameters getRequiredParams() { - // TODO Auto-generated method stub + public OperationResult run(OperationData input) { return null; } @Override - public Parameters getProvidedParams() { - // TODO Auto-generated method stub - return null; - } + public void dereference() { + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Operation.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Operation.java index 50153b094a..6682cf62b4 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Operation.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Operation.java @@ -1,12 +1,6 @@ package edu.cornell.mannlib.vitro.webapp.dynapi.components; -public interface Operation extends RunnableComponent { - - public void addRequiredParameter(Parameter param); - - public void addProvidedParameter(Parameter param); +public interface Operation extends RunnableComponent, ParameterInfo { - public Parameters getRequiredParams(); - public Parameters getProvidedParams(); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/OperationResult.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/OperationResult.java index b635386de5..f48f88f8d7 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/OperationResult.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/OperationResult.java @@ -27,5 +27,9 @@ public void prepareResponse(HttpServletResponse response) { public static OperationResult notImplemented() { return new OperationResult(HttpServletResponse.SC_NOT_IMPLEMENTED); } + + public static OperationResult notFound() { + return new OperationResult(HttpServletResponse.SC_NOT_FOUND); + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/OperationalStep.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/OperationalStep.java index bcdc808cd1..5cb437cc1d 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/OperationalStep.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/OperationalStep.java @@ -1,5 +1,8 @@ package edu.cornell.mannlib.vitro.webapp.dynapi.components; +import java.util.Collections; +import java.util.Set; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -59,4 +62,24 @@ public OperationResult run(OperationData data) { } return result; } + + @Override + public Set getNextLinks() { + return Collections.singleton(nextStep); + } + + @Override + public Parameters getRequiredParams() { + return operation.getRequiredParams(); + } + + @Override + public Parameters getProvidedParams() { + return operation.getProvidedParams(); + } + + @Override + public boolean isRoot() { + return false; + } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ParameterInfo.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ParameterInfo.java new file mode 100644 index 0000000000..343d8456a6 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ParameterInfo.java @@ -0,0 +1,9 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +public interface ParameterInfo { + + public Parameters getRequiredParams(); + + public Parameters getProvidedParams(); + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Poolable.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Poolable.java new file mode 100644 index 0000000000..0ab579dca5 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Poolable.java @@ -0,0 +1,17 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +public interface Poolable extends Removable { + + public K getKey(); + + public boolean isValid(); + + public void addClient(); + + public void removeClient(); + + public void removeDeadClients(); + + public boolean hasClients(); + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Resource.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Resource.java index 076abdc22d..f2829ac399 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Resource.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Resource.java @@ -2,15 +2,15 @@ import java.util.LinkedList; import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; -public class Resource implements Removable { +public class Resource implements Versionable { - private static final Log log = LogFactory.getLog(Resource.class); private String name; private String versionMin; private String versionMax; @@ -19,17 +19,21 @@ public class Resource implements Removable { private RPC rpcOnDelete; private RPC rpcOnPut; private RPC rpcOnPatch; - private List customActions = new LinkedList(); + private List customRESTActions = new LinkedList(); + + private Set clients = ConcurrentHashMap.newKeySet(); + @Override public String getVersionMin() { return versionMin; } - + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#restAPIVersionMin", minOccurs = 0, maxOccurs = 1) public void setVersionMin(String versionMin) { this.versionMin = versionMin; } + @Override public String getVersionMax() { return versionMax; } @@ -39,13 +43,23 @@ public void setVersionMax(String versionMax) { this.versionMax = versionMax; } + public String getName() { + return name; + } + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#resourceName", minOccurs = 1, maxOccurs = 1) public void setName(String name) { this.name = name; } - - public String getName() { - return name; + + @Override + public ResourceKey getKey() { + return ResourceKey.of(name, versionMin); + } + + @Override + public boolean isValid() { + return true; } public RPC getRpcOnGet() { @@ -92,16 +106,80 @@ public RPC getRpcOnPatch() { public void setRpcOnPatch(RPC rpcOnPatch) { this.rpcOnPatch = rpcOnPatch; } - - @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#hasCustomAction") - public void addCustomAction(CustomAction customAction) { - customActions.add(customAction); + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#hasCustomRESTAction") + public void addCustomRESTAction(CustomRESTAction customRESTAction) { + customRESTActions.add(customRESTAction); } @Override public void dereference() { // TODO Auto-generated method stub - + + } + + @Override + public void addClient() { + clients.add(Thread.currentThread().getId()); + } + + @Override + public void removeClient() { + clients.remove(Thread.currentThread().getId()); } - + + @Override + public void removeDeadClients() { + Map currentThreadIds = Thread + .getAllStackTraces() + .keySet() + .stream() + .collect(Collectors.toMap(Thread::getId, Thread::isAlive)); + for (Long client : clients) { + if (!currentThreadIds.containsKey(client) || currentThreadIds.get(client) == false) { + clients.remove(client); + } + } + } + + @Override + public boolean hasClients() { + return !clients.isEmpty(); + } + + public String getCustomRestActionByName(String name) { + for (CustomRESTAction customRestAction : customRESTActions) { + if (customRestAction.getName().equals(name)) { + return customRestAction.getTargetRPC().getName(); + } + } + throw new UnsupportedOperationException("Unsupported custom action"); + } + + public String getActionNameByMethod(String method) { + System.out.println("\nget action name: " + method + "\n"); + switch (method.toUpperCase()) { + case "POST": + return getNameOfRpc(rpcOnPost); + case "GET": + return getNameOfRpc(rpcOnGet); + case "DELETE": + return getNameOfRpc(rpcOnDelete); + case "PUT": + return getNameOfRpc(rpcOnPut); + case "PATCH": + return getNameOfRpc(rpcOnPatch); + default: + throw new UnsupportedOperationException("Unsupported method"); + } + } + + private String getNameOfRpc(RPC rpc) { + System.out.println("\nget name of rpc: " + rpc + "\n"); + if (rpc != null) { + return rpc.getName(); + } + throw new UnsupportedOperationException("Unable to determine action"); + } + } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ResourceKey.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ResourceKey.java new file mode 100644 index 0000000000..b86d940e33 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/ResourceKey.java @@ -0,0 +1,82 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +import static java.lang.String.format; + +public class ResourceKey implements Comparable, Versioned { + + private final String name; + + private final Version version; + + private ResourceKey(String name, String version) { + this.name = name; + this.version = Version.of(version); + } + + @Override + public String getName() { + return name; + } + + @Override + public Version getVersion() { + return version; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ResourceKey other = (ResourceKey) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (version == null) { + if (other.version != null) { + return false; + } + } else if (!version.equals(other.version)) { + return false; + } + return true; + } + + @Override + public int compareTo(ResourceKey o) { + int nameCompare = this.name.compareTo(o.name); + if (nameCompare == 0) { + return this.version.compareTo(o.version); + } + return nameCompare; + } + + @Override + public String toString() { + return format("%s (%s)", name, version.toString()); + } + + public static ResourceKey of(String name, String version) { + return new ResourceKey(name, version); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/SPARQLQuery.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/SPARQLQuery.java index f5411c3b17..2d311d4ec0 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/SPARQLQuery.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/SPARQLQuery.java @@ -17,7 +17,7 @@ import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess; import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; -public class SPARQLQuery implements Operation{ +public class SPARQLQuery implements Operation { private static final Log log = LogFactory.getLog(SPARQLQuery.class); @@ -30,12 +30,12 @@ public class SPARQLQuery implements Operation{ public void dereference() { //TODO } - + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#requiresParameter") public void addRequiredParameter(Parameter param) { - requiredParams.add(param); + requiredParams.add(param); } - + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#providesParameter") public void addProvidedParameter(Parameter param) { providedParams.add(param); @@ -50,11 +50,13 @@ public void setQueryText(String queryText) { public void setQueryModel(ModelComponent model) { this.modelComponent = model; } - + + @Override public Parameters getRequiredParams() { return requiredParams; } + @Override public Parameters getProvidedParams() { return providedParams; } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Scopes.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Scopes.java new file mode 100644 index 0000000000..8e30a707a5 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Scopes.java @@ -0,0 +1,25 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +import java.util.HashSet; +import java.util.Set; + +public class Scopes { + + public static Parameters computeInitialRequirements(Action action) { + Parameters actionResult = action.getProvidedParams(); + Set links = action.getNextLinks(); + Set requirements = new HashSet(); + + for (Link link : links) { + computeRequirements(link, actionResult); + } + + return new Parameters(); + } + + private static void computeRequirements(Link link, Parameters actionResult) { + // TODO Auto-generated method stub + + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/SolrQuery.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/SolrQuery.java new file mode 100644 index 0000000000..1f6dd4f12c --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/SolrQuery.java @@ -0,0 +1,217 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; +import edu.cornell.mannlib.vitro.webapp.dynapi.OperationData; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngineException; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.*; + +public class SolrQuery implements Operation{ + + private static final Log log = LogFactory.getLog(SolrQuery.class); + + private Parameters requiredParams = new Parameters(); + private Parameters providedParams = new Parameters(); + + private String queryText; + private String offset; + private String limit; + private ArrayList fields = new ArrayList<>(); + private ArrayList filters = new ArrayList<>(); + private ArrayList facets = new ArrayList<>(); + private ArrayList sorts = new ArrayList<>(); + + //region @Property Setters + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#requiresParameter") + public void addRequiredParameter(Parameter param) { + requiredParams.add(param); + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#providesParameter") + public void addProvidedParameter(Parameter param) { + providedParams.add(param); + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#solrQueryText", maxOccurs = 1) + public void setQueryText(String queryText){ + this.queryText=queryText; + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#solrFilter") + public void addFilter(String filter){ + this.filters.add(filter); + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#solrField") + public void addField(String field){ + this.fields.add(field); + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#solrLimit", maxOccurs = 1) + public void setLimit(String limit){ + this.limit=limit; + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#solrOffset", maxOccurs = 1) + public void setOffset(String offset){ + this.offset=offset; + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#solrSort") + public void addSort(String sort){ + this.sorts.add(sort); + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#solrFacet") + public void addFacet(String facet){ + this.facets.add(facet); + } + + //endregion + + //region Getters + + @Override + public Parameters getRequiredParams() { + return requiredParams; + } + + @Override + public Parameters getProvidedParams() { + return providedParams; + } + + public String getQueryText() { + return queryText; + } + + public ArrayList getFields() { + return fields; + } + + public ArrayList getFilters() { + return filters; + } + + public String getOffset() { + return offset; + } + + public String getLimit() { + return limit; + } + + public ArrayList getFacets() { + return facets; + } + + public ArrayList getSorts() { + return sorts; + } + + //endregion + + @Override + public OperationResult run(OperationData input) { + if (!isInputValid(input)) { + return new OperationResult(400); + } + + SearchQuery searchQuery; + try { + searchQuery = createSearchQuery(input); + } catch (Exception e) { + log.error("Error while parsing input data for query"); + log.error(e); + return new OperationResult(400); + } + + SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine(); + SearchResponse response; + try { + response = searchEngine.query(searchQuery); + } catch (SearchEngineException e) { + log.error("Error while executing Solr Query:"); + log.error(e.getMessage()); + return new OperationResult(500); + } + + return new OperationResult(200); + } + + private SearchQuery createSearchQuery(OperationData input) + throws InputMismatchException, IllegalArgumentException{ + SearchQuery searchQuery = ApplicationUtils.instance().getSearchEngine().createQuery(); + + if(queryText!=null){ + searchQuery = searchQuery.setQuery(replaceVariablesWithInput(queryText, input)); + } + if(offset!=null){ + searchQuery = searchQuery.setStart(Integer.parseInt(replaceVariablesWithInput(offset, input))); + } + if(limit!=null){ + searchQuery = searchQuery.setRows(Integer.parseInt(replaceVariablesWithInput(limit,input))); + } + for(String field:fields){ + searchQuery = searchQuery.addFields(replaceVariablesWithInput(field, input)); + } + for(String filter:filters){ + searchQuery = searchQuery.addFilterQuery(replaceVariablesWithInput(filter, input)); + } + for(String sort: sorts){ + sort = replaceVariablesWithInput(sort, input); + String[] sortTokens = sort.trim().split(" "); + searchQuery = searchQuery.addSortField( + sortTokens[0], + SearchQuery.Order.valueOf(sortTokens[sortTokens.length-1].toUpperCase()) + ); + } + return searchQuery; + } + + private String replaceVariablesWithInput(String property, OperationData input) + throws InputMismatchException{ + + String[] propertyVariables = Arrays.stream(property.split(":| |,")) + .filter(propertySegment -> propertySegment.startsWith("?")) + .map(propertyVariable -> propertyVariable.substring(1)) + .toArray(String[]::new); + + for(String propertyVar:propertyVariables){ + if(!input.has(propertyVar) || input.get(propertyVar).length>1){ + throw new InputMismatchException(); + } + property=property.replace("?"+propertyVar, input.get(propertyVar)[0]); + } + + return property; + } + + private boolean isInputValid(OperationData input) { + for (String name : requiredParams.getNames()) { + if (!input.has(name)) { + log.error("Parameter " + name + " not found"); + return false; + } + Parameter param = requiredParams.get(name); + String[] inputValues = input.get(name); + if (!param.isValid(name, inputValues)){ + return false; + } + } + return true; + } + + @Override + public void dereference() { + + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Step.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Step.java index c43a30b502..9498ea3118 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Step.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Step.java @@ -1,5 +1,5 @@ package edu.cornell.mannlib.vitro.webapp.dynapi.components; -public interface Step extends RunnableComponent{ +public interface Step extends RunnableComponent, Link{ } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Version.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Version.java new file mode 100644 index 0000000000..a13921c841 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Version.java @@ -0,0 +1,103 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +import static java.lang.String.format; +import static java.util.regex.Pattern.quote; + +public class Version implements Comparable { + + private final static String PERIOD_PATTERN = quote("."); + + private final Integer major; + + private final Integer minor; + + private final Integer patch; + + private Version(String version) { + String[] parts = version.split(PERIOD_PATTERN); + major = parts.length > 0 ? Integer.parseInt(parts[0]) : 0; + minor = parts.length > 1 ? Integer.parseInt(parts[1]) : Integer.MAX_VALUE; + patch = parts.length > 2 ? Integer.parseInt(parts[2]) : Integer.MAX_VALUE; + } + + public Integer getMajor() { + return major; + } + + public Integer getMinor() { + return minor; + } + + public Integer getPatch() { + return patch; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((major == null) ? 0 : major.hashCode()); + result = prime * result + ((minor == null) ? 0 : minor.hashCode()); + result = prime * result + ((patch == null) ? 0 : patch.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Version other = (Version) obj; + if (major == null) { + if (other.major != null) { + return false; + } + } else if (!major.equals(other.major)) { + return false; + } + if (minor == null) { + if (other.minor != null) { + return false; + } + } else if (!minor.equals(other.minor)) { + return false; + } + if (patch == null) { + if (other.patch != null) { + return false; + } + } else if (!patch.equals(other.patch)) { + return false; + } + return true; + } + + @Override + public int compareTo(Version o) { + int majorCompare = this.major.compareTo(o.major); + if (majorCompare == 0) { + int minorCompare = this.minor.compareTo(o.minor); + if (minorCompare == 0) { + return this.patch.compareTo(o.patch); + } + return minorCompare; + } + return majorCompare; + } + + @Override + public String toString() { + return format("%s.%s.%s", major, minor, patch); + } + + public static Version of(String version) { + return new Version(version); + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Versionable.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Versionable.java new file mode 100644 index 0000000000..14212bded9 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Versionable.java @@ -0,0 +1,9 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +public interface Versionable extends Poolable { + + public String getVersionMin(); + + public String getVersionMax(); + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Versioned.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Versioned.java new file mode 100644 index 0000000000..3824185f40 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/Versioned.java @@ -0,0 +1,9 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components; + +public interface Versioned { + + public String getName(); + + public Version getVersion(); + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/AbstractValidator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/AbstractValidator.java index 6cbbf7cb15..5c11c67c7a 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/AbstractValidator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/AbstractValidator.java @@ -1,22 +1,11 @@ package edu.cornell.mannlib.vitro.webapp.dynapi.components.validators; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; + public abstract class AbstractValidator implements Validator { - - private String name; - @Override public void dereference() { } - @Override - public String getName() { - return name; - } - - @Override - public void setName(String name) { - this.name = name; - } - } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/IsInteger.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/IsInteger.java index c3f2bce74a..c39b92efaf 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/IsInteger.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/IsInteger.java @@ -13,18 +13,20 @@ public boolean isValid(String name, String[] values) { if (!super.isValid(name, values)) { return false; } - - if (!isInteger(values[0])) { - return false; + + for (String value : values) { + if (!isInteger(values[0])) { + return false; + } } return true; } private boolean isInteger(String string) { - //TODO: Do the real check - if (NumberUtils.isParsable(string)) { + if (NumberUtils.isDigits(string)) { return true; } + log.debug("Value is not a number. Validation failed."); return false; } } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/NumericRangeValidator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/NumericRangeValidator.java new file mode 100644 index 0000000000..9b0f10d1bb --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/NumericRangeValidator.java @@ -0,0 +1,62 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components.validators; + +import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class NumericRangeValidator extends IsNotBlank { + + private static final Log log = LogFactory.getLog(NumericRangeValidator.class); + + private Float minValue; + + private Float maxValue; + + public Float getMinValue() { + return minValue; + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#validatorMinNumericValue", maxOccurs = 1) + public void setMinValue(float minValue) { + this.minValue = minValue; + } + + public Float getMaxValue() { + return maxValue; + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#validatorMaxNumericValue", maxOccurs = 1) + public void setMaxValue(float maxValue) { + this.maxValue = maxValue; + } + + @Override + public boolean isValid(String name, String[] values) { + if (!super.isValid(name, values)) { + return false; + } + for (String value : values) { + if (!isInRange(value)) { + log.debug("Value of " + name + " is not in range [" + ((minValue != null)?minValue:" ") + "-" + ((maxValue != null)?maxValue:" ")+"]."); + return false; + } + } + return true; + } + + private boolean isInRange(String string) { + if (NumberUtils.isParsable(string)) { + double value = NumberUtils.toDouble(string); + + if ((minValue != null) && (value < minValue)) + return false; + + if ((maxValue != null) && (value > maxValue)) + return false; + + return true; + } + return false; + } +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/RegularExpressionValidator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/RegularExpressionValidator.java new file mode 100644 index 0000000000..5401c8eae9 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/RegularExpressionValidator.java @@ -0,0 +1,44 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components.validators; + +import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.util.regex.Pattern; + +public class RegularExpressionValidator extends AbstractValidator { + + private static final Log log = LogFactory.getLog(RegularExpressionValidator.class); + + private String regularExpression; + + public String getRegularExpression() { + return regularExpression; + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#validatorRegularExpressionValue", minOccurs = 1, maxOccurs = 1) + public void setRegularExpression (String regularExpression) { + this.regularExpression = regularExpression; + } + + @Override + public boolean isValid(String name, String[] values) { + if (values.length == 0) { + log.debug("No values of " + name + " found. Validation failed."); + return false; + } + + for (String value : values) { + if (!isLengthInRange(value)) { + log.debug("Value of " + name + " is not in accordance with the pattern \"" + regularExpression + "\""); + return false; + } + } + + return true; + } + + private boolean isLengthInRange(String string) { + return Pattern.matches(regularExpression, string); + } +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/StringLengthRangeValidator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/StringLengthRangeValidator.java new file mode 100644 index 0000000000..4b671d30c3 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/StringLengthRangeValidator.java @@ -0,0 +1,59 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.components.validators; + +import edu.cornell.mannlib.vitro.webapp.utils.configuration.Property; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class StringLengthRangeValidator extends IsNotBlank { + + private static final Log log = LogFactory.getLog(StringLengthRangeValidator.class); + + private Integer minLength; + + private Integer maxLength; + + public Integer getMinLength() { + return minLength; + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#validatorMinLengthValue", maxOccurs = 1) + public void setMinLength(int minLength) { + this.minLength = minLength; + } + + public Integer getMaxLength() { + return maxLength; + } + + @Property(uri = "https://vivoweb.org/ontology/vitro-dynamic-api#validatorMaxLengthValue", maxOccurs = 1) + public void setMaxLength(int maxValue) { + this.maxLength = maxValue; + } + + @Override + public boolean isValid(String name, String[] values) { + if (!super.isValid(name, values)) { + return false; + } + + for (String value : values) { + if (!isLengthInRange(value)) { + log.debug("Length of " + name + " is not in range [" + ((minLength != null)?minLength:" ") + "-" + ((maxLength != null)?maxLength:" ")+"]."); + return false; + } + } + return true; + } + + private boolean isLengthInRange(String string) { + int length = string.length(); + + if ((minLength != null) && (length < minLength)) + return false; + + if ((maxLength != null) && (length > maxLength)) + return false; + + return true; + } +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/Validator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/Validator.java index 039f23aa79..261a7242d2 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/Validator.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/components/validators/Validator.java @@ -4,10 +4,6 @@ public interface Validator extends Removable { - public boolean isValid(String name, String[] values); - - public String getName(); - - public void setName(String getName); + boolean isValid(String name, String[] values); } diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/request/RequestPath.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/request/RequestPath.java new file mode 100644 index 0000000000..a8b4ef7af8 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dynapi/request/RequestPath.java @@ -0,0 +1,139 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.request; + +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +import java.util.Base64; + +import javax.servlet.http.HttpServletRequest; + +public class RequestPath { + + public static final String RESOURCE_ID_PARAM = "RESOURCE_ID"; + + public static final String API_BASE_PATH = "/api"; + public static final String RPC_BASE_PATH = API_BASE_PATH + "/rpc"; + public static final String REST_BASE_PATH = API_BASE_PATH + "/rest"; + + private final RequestType type; + + private final String[] pathParts; + + private final String resourceVersion; + + private final String resourceName; + + private final String resourceId; + + private final String actionName; + + private RequestPath(HttpServletRequest request) { + String contextPath = request != null && request.getContextPath() != null + ? request.getContextPath() + : EMPTY; + String pathInfo = request != null && request.getPathInfo() != null + ? request.getPathInfo() + : EMPTY; + + pathParts = pathInfo.split("/"); + + if (contextPath.toLowerCase().contains(RPC_BASE_PATH)) { + type = RequestType.RPC; + actionName = pathParts.length > 1 ? pathParts[1] : null; + resourceVersion = null; + resourceName = null; + resourceId = null; + } else if (contextPath.toLowerCase().contains(REST_BASE_PATH)) { + type = RequestType.REST; + resourceVersion = pathParts.length > 1 ? pathParts[1] : null; + resourceName = pathParts.length > 2 ? pathParts[2] : null; + + if (pathParts.length > 3) { + if (pathParts[3].toLowerCase().startsWith("resource:")) { + resourceId = decode(pathParts[3]); + actionName = pathParts.length > 4 ? pathParts[4] : null; + request.getParameterMap().put(RESOURCE_ID_PARAM, new String[] { resourceId }); + } else { + resourceId = null; + actionName = pathParts[3]; + } + } else { + resourceId = null; + actionName = null; + } + } else { + type = RequestType.UNKNOWN; + resourceVersion = null; + resourceName = null; + resourceId = null; + actionName = null; + } + } + + private String decode(String pathParameter) { + return new String(Base64.getDecoder().decode(pathParameter.split(":")[1])); + } + + public RequestType getType() { + return type; + } + + public String getResourceVersion() { + return resourceVersion; + } + + public String getResourceName() { + return resourceName; + } + + public String getResourceId() { + return resourceId; + } + + public String getActionName() { + return actionName; + } + + public boolean isValid() { + boolean isValid = false; + switch (type) { + case REST: + boolean hasVersionAndName = isNotEmpty(resourceVersion) && isNotEmpty(resourceName); + if (pathParts.length == 3) { + isValid = hasVersionAndName; + } else if (pathParts.length > 3) { + boolean hasActionName = isNotEmpty(actionName); + boolean hasResourceId = isNotEmpty(resourceId); + + if (pathParts.length == 4) { + isValid = hasVersionAndName && (hasActionName || hasResourceId); + } else if (pathParts.length == 5) { + isValid = hasVersionAndName && hasResourceId && hasActionName; + } + } + break; + case RPC: + boolean hasActionName = isNotEmpty(actionName); + + isValid = hasActionName; + break; + case UNKNOWN: + default: + } + + return isValid; + } + + public boolean isCustomRestAction() { + return isNotEmpty(resourceVersion) && isNotEmpty(resourceName) && isNotEmpty(actionName); + } + + public static RequestPath from(HttpServletRequest request) { + return new RequestPath(request); + } + + public enum RequestType { + RPC, REST, UNKNOWN + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilderTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilderTest.java index e7872e6d14..4c81db0572 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilderTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/UrlBuilderTest.java @@ -3,21 +3,17 @@ package edu.cornell.mannlib.vitro.webapp.controller.freemarker; -import static org.easymock.EasyMock.*; - -import javax.servlet.http.HttpServletRequest; - import org.junit.Assert; - import org.junit.Test; -import stubs.edu.cornell.mannlib.vitro.webapp.dao.ApplicationDaoStub; -import stubs.edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactoryStub; import edu.cornell.mannlib.vitro.testing.AbstractTestClass; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap; import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; import edu.cornell.mannlib.vitro.webapp.web.URLEncoder; +import stubs.edu.cornell.mannlib.vitro.webapp.dao.ApplicationDaoStub; +import stubs.edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactoryStub; +import stubs.javax.servlet.http.HttpServletRequestStub; public class UrlBuilderTest extends AbstractTestClass { @@ -114,7 +110,7 @@ public void testGetIndividualProfileURI(){ } protected VitroRequest makeMockVitroRequest( final String defaultNS){ - HttpServletRequest req = createMock( HttpServletRequest.class ); + HttpServletRequestStub req = new HttpServletRequestStub(); return new VitroRequest(req){ @Override diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ActionPoolTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ActionPoolTest.java new file mode 100644 index 0000000000..5bc491e5dc --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ActionPoolTest.java @@ -0,0 +1,315 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import org.junit.After; +import org.junit.Test; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Action; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.DefaultAction; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoaderException; + +public class ActionPoolTest extends ServletContextTest { + + protected final static String TEST_PERSON_ACTION_URI = "https://vivoweb.org/ontology/vitro-dynamic-api/action/testPersonAction1"; + + @After + public void reset() { + setup(); + + ActionPool actionPool = ActionPool.getInstance(); + actionPool.init(servletContext); + actionPool.reload(); + + assertEquals(0, actionPool.count()); + assertEquals(0, actionPool.obsoleteCount()); + } + + @Test + public void testGetInstance() { + ActionPool actionPool = ActionPool.getInstance(); + assertNotNull(actionPool); + assertEquals(actionPool, ActionPool.getInstance()); + } + + @Test + public void testGetBeforeInit() { + ActionPool actionPool = ActionPool.getInstance(); + Action action = actionPool.get(TEST_ACTION_NAME); + assertNotNull(action); + assertTrue(action instanceof DefaultAction); + } + + @Test + public void testPrintKeysBeforeInit() { + ActionPool actionPool = ActionPool.getInstance(); + actionPool.printKeys(); + // nothing to assert + } + + @Test + public void testReloadBeforeInit() throws IOException { + ActionPool actionPool = ActionPool.getInstance(); + actionPool.reload(); + // not sure what to assert + } + + @Test + public void testInit() throws IOException { + ActionPool actionPool = initWithDefaultModel(); + assertEquals(1, actionPool.count()); + assertEquals(0, actionPool.obsoleteCount()); + + assertAction(TEST_ACTION_NAME, actionPool.get(TEST_ACTION_NAME)); + } + + @Test + public void testPrintKeys() throws IOException { + ActionPool actionPool = initWithDefaultModel(); + + actionPool.printKeys(); + // nothing to assert + } + + @Test + public void testAdd() throws IOException, ConfigurationBeanLoaderException { + ActionPool actionPool = initWithDefaultModel(); + + loadTestModel(); + + Action action = loader.loadInstance(TEST_PERSON_ACTION_URI, Action.class); + + actionPool.add(TEST_PERSON_ACTION_URI, action); + + assertEquals(0, actionPool.obsoleteCount()); + + assertAction(TEST_PERSON_ACTION_NAME, actionPool.get(TEST_PERSON_ACTION_NAME)); + } + + @Test + public void testAddHasClient() throws IOException, ConfigurationBeanLoaderException { + ActionPool actionPool = initWithDefaultModel(); + + loadTestModel(); + + actionPool.reload(); + + Action action = loader.loadInstance(TEST_PERSON_ACTION_URI, Action.class); + + assertEquals(0, actionPool.obsoleteCount()); + + Action actionHasClient = actionPool.get(TEST_PERSON_ACTION_NAME); + + actionPool.add(TEST_PERSON_ACTION_URI, action); + + assertEquals(1, actionPool.obsoleteCount()); + + actionHasClient.removeClient(); + } + + @Test(expected = RuntimeException.class) + public void testAddWithoutModelLoaded() throws IOException, ConfigurationBeanLoaderException { + ActionPool actionPool = initWithDefaultModel(); + + loadTestModel(); + + Action action = loader.loadInstance(TEST_PERSON_ACTION_URI, Action.class); + + reset(); + + assertTrue(actionPool.get(TEST_PERSON_ACTION_NAME) instanceof DefaultAction); + + actionPool.add(TEST_PERSON_ACTION_URI, action); + } + + @Test + public void testRemove() throws IOException, ConfigurationBeanLoaderException { + ActionPool actionPool = initWithDefaultModel(); + + loadTestModel(); + + actionPool.reload(); + + Action action = actionPool.get(TEST_PERSON_ACTION_NAME); + + assertFalse(action instanceof DefaultAction); + + action.removeClient(); + + reset(); + + actionPool.remove(TEST_PERSON_ACTION_URI, TEST_PERSON_ACTION_NAME); + + assertEquals(0, actionPool.obsoleteCount()); + + assertTrue(actionPool.get(TEST_PERSON_ACTION_NAME) instanceof DefaultAction); + } + + @Test + public void testRemoveHasClient() throws IOException, ConfigurationBeanLoaderException { + ActionPool actionPool = initWithDefaultModel(); + + loadTestModel(); + + actionPool.reload(); + + Action actionHasClient = actionPool.get(TEST_PERSON_ACTION_NAME); + + assertFalse(actionHasClient instanceof DefaultAction); + + setup(); + + actionPool.init(servletContext); + + actionPool.remove(TEST_PERSON_ACTION_URI, TEST_PERSON_ACTION_NAME); + + assertEquals(1, actionPool.obsoleteCount()); + + assertTrue(actionPool.get(TEST_PERSON_ACTION_NAME) instanceof DefaultAction); + + actionHasClient.removeClient(); + } + + @Test(expected = RuntimeException.class) + public void testRemoveWithModelLoaded() throws IOException, ConfigurationBeanLoaderException { + ActionPool actionPool = initWithDefaultModel(); + + loadTestModel(); + + actionPool.reload(); + + actionPool.remove(TEST_PERSON_ACTION_URI, TEST_PERSON_ACTION_NAME); + } + + @Test + public void testReloadSingle() throws IOException { + ActionPool actionPool = initWithDefaultModel(); + + loadTestModel(); + + Action action = actionPool.get(TEST_PERSON_ACTION_NAME); + + assertTrue(action instanceof DefaultAction); + + actionPool.reload(TEST_PERSON_ACTION_URI); + + assertAction(TEST_PERSON_ACTION_NAME, actionPool.get(TEST_PERSON_ACTION_NAME)); + } + + @Test + public void testReload() throws IOException { + ActionPool actionPool = initWithDefaultModel(); + + assertAction(TEST_ACTION_NAME, actionPool.get(TEST_ACTION_NAME)); + + loadTestModel(); + + actionPool.reload(); + + assertEquals(8, actionPool.count()); + assertEquals(0, actionPool.obsoleteCount()); + + assertAction(TEST_PERSON_ACTION_NAME, actionPool.get(TEST_PERSON_ACTION_NAME)); + } + + @Test + public void testReloadThreadSafety() throws IOException { + ActionPool actionPool = initWithDefaultModel(); + + assertAction(TEST_ACTION_NAME, actionPool.get(TEST_ACTION_NAME)); + + loadTestModel(); + + CompletableFuture reloadFuture = CompletableFuture.runAsync(() -> actionPool.reload()); + + while (!reloadFuture.isDone()) { + assertAction(TEST_ACTION_NAME, actionPool.get(TEST_ACTION_NAME)); + } + + assertAction(TEST_ACTION_NAME, actionPool.get(TEST_ACTION_NAME)); + + assertAction(TEST_PERSON_ACTION_NAME, actionPool.get(TEST_PERSON_ACTION_NAME)); + } + + @Test + public void testRealodOfActionHasClient() throws IOException { + ActionPool actionPool = initWithDefaultModel(); + + loadTestModel(); + + Action action = actionPool.get(TEST_ACTION_NAME); + + CompletableFuture reloadFuture = CompletableFuture.runAsync(() -> actionPool.reload()); + + while (!reloadFuture.isDone()) { + assertEquals(TEST_ACTION_NAME, action.getKey()); + } + + action.removeClient(); + } + + @Test + public void testClientsManagement() throws IOException, InterruptedException { + ActionPool actionPool = initWithDefaultModel(); + + actionPool.reload(); + + long initalCount = actionPool.obsoleteCount(); + Action action = actionPool.get(TEST_ACTION_NAME); + + action.removeClient(); + + assertFalse(action.hasClients()); + + Thread t1 = getActionInThread(actionPool, TEST_ACTION_NAME); + + t1.join(); + + assertTrue(action.hasClients()); + + actionPool.reload(); + + assertEquals(initalCount, actionPool.obsoleteCount()); + } + + private Thread getActionInThread(ActionPool actionPool, String name) { + Runnable client = new Runnable() { + @Override + public void run() { + Action action = actionPool.get(name); + assertEquals(name, action.getKey()); + assertTrue(action.hasClients()); + } + }; + Thread thread = new Thread(client); + thread.start(); + return thread; + } + + private ActionPool initWithDefaultModel() throws IOException { + loadDefaultModel(); + + ActionPool actionPool = ActionPool.getInstance(); + + actionPool.init(servletContext); + + return actionPool; + } + + private void assertAction(String expectedName, Action actualAction) { + assertNotNull(actualAction); + assertFalse(format("%s not loaded!", expectedName), actualAction instanceof DefaultAction); + assertTrue(actualAction.isValid()); + assertEquals(expectedName, actualAction.getKey()); + assertTrue(actualAction.hasClients()); + actualAction.removeClient(); + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RESTEndpointTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RESTEndpointTest.java new file mode 100644 index 0000000000..d332ab8c6f --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RESTEndpointTest.java @@ -0,0 +1,204 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static edu.cornell.mannlib.vitro.webapp.dynapi.request.RequestPath.REST_BASE_PATH; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Action; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.OperationResult; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Resource; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.ResourceKey; + +@RunWith(MockitoJUnitRunner.class) +public class RESTEndpointTest { + + private final static String PATH_INFO = "/1/test"; + private final static String CONTEXT_PATH = REST_BASE_PATH + PATH_INFO; + + private Map params; + + private ServletContext context; + + private MockedStatic resourcePoolStatic; + + private MockedStatic actionPoolStatic; + + private RESTEndpoint restEndpoint; + + @Mock + private ResourcePool resourcePool; + + @Mock + private ActionPool actionPool; + + @Mock + private Resource resource; + + @Spy + private Action action; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Before + public void beforeEach() { + resourcePoolStatic = mockStatic(ResourcePool.class); + actionPoolStatic = mockStatic(ActionPool.class); + + when(ResourcePool.getInstance()).thenReturn(resourcePool); + when(resourcePool.get(any(ResourceKey.class))).thenReturn(resource); + + when(ActionPool.getInstance()).thenReturn(actionPool); + when(actionPool.get(any(String.class))).thenReturn(action); + + when(request.getParameterMap()).thenReturn(params); + when(request.getServletContext()).thenReturn(context); + + restEndpoint = new RESTEndpoint(); + } + + @After + public void afterEach() { + resourcePoolStatic.close(); + actionPoolStatic.close(); + } + + @Test + public void doPostTest() { + prepareMocks("POST", "test"); + restEndpoint.doPost(request, response); + verifyMocks(1, HttpServletResponse.SC_OK); + } + + @Test + public void doPostCustomRestActionTest() { + OperationResult result = new OperationResult(HttpServletResponse.SC_OK); + + when(request.getMethod()).thenReturn("POST"); + when(request.getContextPath()).thenReturn(CONTEXT_PATH + "/dedupe"); + when(request.getPathInfo()).thenReturn(PATH_INFO + "/dedupe"); + + when(action.run(any(OperationData.class))).thenReturn(result); + + when(resource.getCustomRestActionByName("dedupe")).thenReturn("dedupe"); + doNothing().when(resource).removeClient(); + + restEndpoint.doPost(request, response); + + verify(resource, times(0)).getActionNameByMethod(any()); + verify(resource, times(1)).getCustomRestActionByName(any()); + verify(resource, times(1)).removeClient(); + verify(action, times(1)).run(any()); + verify(action, times(1)).removeClient(); + verify(response, times(1)).setStatus(HttpServletResponse.SC_OK); + } + + @Test + public void doPostTestNotFound() { + when(request.getPathInfo()).thenReturn(EMPTY); + restEndpoint.doPost(request, response); + verifyMocks(0, HttpServletResponse.SC_NOT_FOUND); + } + + @Test + public void doGetTest() { + prepareMocks("GET", "test"); + restEndpoint.doGet(request, response); + verifyMocks(1, HttpServletResponse.SC_OK); + } + + @Test + public void doGetTestNotFound() { + when(request.getPathInfo()).thenReturn(EMPTY); + restEndpoint.doGet(request, response); + verifyMocks(0, HttpServletResponse.SC_NOT_FOUND); + } + + @Test + public void doPutTest() { + prepareMocks("PUT", "test"); + restEndpoint.doPut(request, response); + verifyMocks(1, HttpServletResponse.SC_OK); + } + + @Test + public void doPutTestNotFound() { + when(request.getPathInfo()).thenReturn(EMPTY); + restEndpoint.doPut(request, response); + verifyMocks(0, HttpServletResponse.SC_NOT_FOUND); + } + + @Test + public void doPatchTest() { + prepareMocks("PATCH", "test"); + restEndpoint.doPatch(request, response); + verifyMocks(1, HttpServletResponse.SC_OK); + } + + @Test + public void doPatchTestNotFound() { + when(request.getPathInfo()).thenReturn(EMPTY); + restEndpoint.doPatch(request, response); + verifyMocks(0, HttpServletResponse.SC_NOT_FOUND); + } + + @Test + public void doDeleteTest() { + prepareMocks("DELETE", "test"); + restEndpoint.doDelete(request, response); + verifyMocks(1, HttpServletResponse.SC_OK); + } + + @Test + public void doDeleteTestNotFound() { + when(request.getPathInfo()).thenReturn(EMPTY); + restEndpoint.doDelete(request, response); + verifyMocks(0, HttpServletResponse.SC_NOT_FOUND); + } + + private void prepareMocks(String method, String actionName) { + OperationResult result = new OperationResult(HttpServletResponse.SC_OK); + + when(request.getMethod()).thenReturn(method); + when(request.getContextPath()).thenReturn(CONTEXT_PATH); + when(request.getPathInfo()).thenReturn(PATH_INFO); + + when(action.run(any(OperationData.class))).thenReturn(result); + + when(resource.getActionNameByMethod(method)).thenReturn(actionName); + doNothing().when(resource).removeClient(); + } + + private void verifyMocks(int count, int status) { + verify(resource, times(count)).getActionNameByMethod(any()); + verify(resource, times(0)).getCustomRestActionByName(any()); + verify(resource, times(count)).removeClient(); + verify(action, times(count)).run(any()); + verify(action, times(count)).removeClient(); + verify(response, times(1)).setStatus(status); + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpointIT.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpointIT.java new file mode 100644 index 0000000000..904af9a227 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpointIT.java @@ -0,0 +1,201 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import static javax.servlet.http.HttpServletResponse.SC_NOT_IMPLEMENTED; +import static javax.servlet.http.HttpServletResponse.SC_OK; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.util.Arrays; +import java.util.Collection; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.jena.query.QueryExecution; +import org.apache.jena.query.QueryExecutionFactory; +import org.apache.jena.query.ResultSetFactory; +import org.apache.jena.rdf.model.Model; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.mockito.MockedStatic; + +@RunWith(Parameterized.class) +public class RPCEndpointIT extends ServletContextIT { + + private final static String URI_CONTEXT = "/api/rpc/"; + private final static String URI_BASE = "http://localhost" + URI_CONTEXT; + + private RPCEndpoint rpcEndpoint; + + private HttpServletRequest request; + + private HttpServletResponse response; + + private MockedStatic actionPoolStatic; + + private MockedStatic queryExecutionFactoryStatic; + + private ActionPool actionPool; + + private QueryExecution queryExecution; + + @Parameter(0) + public String testAction; + + @Parameter(1) + public String testLimit; + + @Parameter(2) + public String testEmail; + + @Parameter(3) + public Integer testStatus; + + @Parameter(4) + public Method testBefore; + + @Parameter(5) + public Method testAfter; + + @Parameter(6) + public String testMessage; + + @Before + public void beforeEach() throws Exception { + queryExecution = mock(QueryExecution.class); + actionPool = new ActionPool(); + + actionPoolStatic = mockStatic(ActionPool.class); + when(ActionPool.getInstance()).thenReturn(actionPool); + + queryExecutionFactoryStatic = mockStatic(QueryExecutionFactory.class); + when(QueryExecutionFactory.create(any(String.class), any(Model.class))).thenReturn(queryExecution); + + request = mock(HttpServletRequest.class); + response = mock(HttpServletResponse.class); + rpcEndpoint = new RPCEndpoint(); + + loadDefaultModel(); + + when(request.getServletContext()).thenReturn(servletContext); + when(request.getContextPath()).thenReturn(URI_CONTEXT); + + actionPool.init(request.getServletContext()); + actionPool.reload(); + + if (testAction != null) { + StringBuffer buffer = new StringBuffer(URI_BASE + testAction); + when(request.getRequestURL()).thenReturn(buffer); + when(request.getPathInfo()).thenReturn("/" + testAction); + } + + when(request.getParameterMap()).thenReturn(parameterMap); + + mockParameterIntoMap("limit", testLimit); + mockParameterIntoMap("email", testEmail); + mockStatus(response); + runCallback(testBefore); + } + + @After + public void afterEach() throws Exception { + runCallback(testAfter); + + actionPoolStatic.close(); + queryExecutionFactoryStatic.close(); + } + + @Test + public void doGetTest() { + rpcEndpoint.doGet(request, response); + + // For all permutations, this should return HTTP 501. + assertResponseStatus(SC_NOT_IMPLEMENTED); + } + + @Test + public void doPostTest() { + rpcEndpoint.doPost(request, response); + assertResponseStatus(testStatus); + } + + @Test + public void doDeleteTest() { + rpcEndpoint.doDelete(request, response); + + // For all permutations, this should return HTTP 501. + assertResponseStatus(SC_NOT_IMPLEMENTED); + } + + @Test + public void doPutTest() { + rpcEndpoint.doPut(request, response); + + // For all permutations, this should return HTTP 501. + assertResponseStatus(SC_NOT_IMPLEMENTED); + } + + private void assertResponseStatus(int status) { + assertEquals("Invalid Status for test: " + testMessage, status, response.getStatus()); + } + + /** + * Prevent SPARQL from actually running by returning a mocked response. + * @throws IOException + */ + protected void mockSparqlResponseEmptySuccess() throws IOException { + String json = readMockFile("sparql/response/json/sparql-empty-success.json"); + InputStream stream = new ByteArrayInputStream(json.getBytes()); + when(queryExecution.execSelect()).thenReturn(ResultSetFactory.fromJSON(stream)); + } + + @Parameterized.Parameters + public static Collection requests() throws MalformedURLException, NoSuchMethodException, SecurityException { + int nf = SC_NOT_FOUND; + int se = SC_INTERNAL_SERVER_ERROR; + int ni = SC_NOT_IMPLEMENTED; + int ok = SC_OK; + + String actionIsEmpty = ""; + String actionIsUnknown = "unknown"; + String actionIsGood = TEST_ACTION_NAME; + String limitIsEmpty = ""; + String limitIsBad = "-1"; + String limitIsGood = "10"; + String emailIsEmpty = ""; + String emailIsBad = "a"; + String emailIsGood = "example@localhost"; + + Method[] before = new Method[] { + RPCEndpointIT.class.getDeclaredMethod("mockSparqlResponseEmptySuccess") + }; + + return Arrays.asList(new Object[][] { + // action limit email status before after testMessage + { null, null, null, nf, null, null, "NULL Request" }, + { actionIsEmpty, null, null, nf, null, null, "Empty Action" }, + { actionIsUnknown, null, null, ni, null, null, "Unknown Action" }, + { actionIsGood, null, null, se, null, null, "NULL Limit" }, + { actionIsGood, limitIsEmpty, null, se, null, null, "Empty Limit" }, + { actionIsGood, limitIsBad, null, se, null, null, "Bad Limit" }, + { actionIsGood, limitIsGood, null, se, null, null, "NULL E-mail" }, + { actionIsGood, limitIsGood, emailIsEmpty, se, null, null, "Empty E-mail" }, + { actionIsGood, limitIsGood, emailIsBad, se, null, null, "Bad E-mail" }, + { actionIsGood, limitIsGood, emailIsGood, ok, before[0], null, "Valid Request" }, + }); + } +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpointTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpointTest.java new file mode 100644 index 0000000000..90346485fd --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/RPCEndpointTest.java @@ -0,0 +1,114 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static edu.cornell.mannlib.vitro.webapp.dynapi.request.RequestPath.RPC_BASE_PATH; +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Action; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.OperationResult; + +@RunWith(MockitoJUnitRunner.class) +public class RPCEndpointTest { + + private final static String PATH_INFO = "/test"; + private final static String CONTEXT_PATH = RPC_BASE_PATH + PATH_INFO; + + private Map params; + + private ServletContext context; + + private MockedStatic actionPoolStatic; + + private RPCEndpoint rpcEndpoint; + + @Mock + private ActionPool actionPool; + + @Spy + private Action action; + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + @Before + public void beforeEach() { + actionPoolStatic = mockStatic(ActionPool.class); + when(ActionPool.getInstance()).thenReturn(actionPool); + when(actionPool.get(any(String.class))).thenReturn(action); + + when(request.getParameterMap()).thenReturn(params); + when(request.getServletContext()).thenReturn(context); + + rpcEndpoint = new RPCEndpoint(); + } + + @After + public void afterEach() { + actionPoolStatic.close(); + } + + @Test + public void doGetTest() { + rpcEndpoint.doGet(request, response); + verify(action, times(0)).run(any()); + verify(response, times(1)).setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); + } + + @Test + public void doPostTest() { + OperationResult result = new OperationResult(HttpServletResponse.SC_OK); + + when(request.getContextPath()).thenReturn(CONTEXT_PATH); + when(request.getPathInfo()).thenReturn(PATH_INFO); + when(action.run(any(OperationData.class))).thenReturn(result); + + rpcEndpoint.doPost(request, response); + verify(action, times(1)).run(any()); + verify(response, times(1)).setStatus(HttpServletResponse.SC_OK); + } + + @Test + public void doPostTestOnMissing() { + when(request.getPathInfo()).thenReturn(EMPTY); + + rpcEndpoint.doPost(request, response); + verify(action, times(0)).run(any()); + verify(response, times(1)).setStatus(HttpServletResponse.SC_NOT_FOUND); + } + + @Test + public void doDeleteTest() { + rpcEndpoint.doDelete(request, response); + verify(action, times(0)).run(any()); + verify(response, times(1)).setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); + } + + @Test + public void doPutTest() { + rpcEndpoint.doPut(request, response); + verify(action, times(0)).run(any()); + verify(response, times(1)).setStatus(HttpServletResponse.SC_NOT_IMPLEMENTED); + } +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ResourcePoolTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ResourcePoolTest.java new file mode 100644 index 0000000000..315d53a1a5 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ResourcePoolTest.java @@ -0,0 +1,552 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static java.lang.String.format; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import org.junit.After; +import org.junit.Test; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.DefaultResource; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Resource; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.ResourceKey; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoaderException; + +public class ResourcePoolTest extends ServletContextTest { + + protected final static String TEST_PERSON_RESOURCE_URI = "https://vivoweb.org/ontology/vitro-dynamic-api/resource/testPersonResource1"; + + @After + public void reset() { + setup(); + + ResourcePool resourcePool = ResourcePool.getInstance(); + resourcePool.init(servletContext); + resourcePool.reload(); + + ActionPool actionPool = ActionPool.getInstance(); + actionPool.init(servletContext); + actionPool.reload(); + + assertEquals(0, resourcePool.count()); + assertEquals(0, resourcePool.obsoleteCount()); + + assertEquals(0, actionPool.count()); + assertEquals(0, actionPool.obsoleteCount()); + } + + @Test + public void testGetInstance() { + ResourcePool resourcePool = ResourcePool.getInstance(); + assertNotNull(resourcePool); + assertEquals(resourcePool, ResourcePool.getInstance()); + } + + @Test + public void testGetBeforeInit() { + ResourcePool resourcePool = ResourcePool.getInstance(); + assertTrue(resourcePool.get(TEST_RESOURCE_KEY) instanceof DefaultResource); + } + + @Test + public void testPrintKeysBeforeInit() { + ResourcePool resourcePool = ResourcePool.getInstance(); + resourcePool.printKeys(); + // nothing to assert + } + + @Test + public void testReloadBeforeInit() throws IOException { + ResourcePool resourcePool = ResourcePool.getInstance(); + resourcePool.reload(); + // not sure what to assert + } + + @Test + public void testInit() throws IOException { + ResourcePool resourcePool = initWithDefaultModel(); + assertEquals(1, resourcePool.count()); + assertEquals(0, resourcePool.obsoleteCount()); + + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(TEST_RESOURCE_KEY)); + } + + @Test + public void testVersioning() throws IOException { + ResourcePool resourcePool = initWithDefaultModel(); + + ResourceKey resouce_v0 = ResourceKey.of("test_resource", "0"); + ResourceKey resource_v1 = ResourceKey.of("test_resource", "1"); + + ResourceKey document_v0 = ResourceKey.of("test_document_resource", "0"); + ResourceKey document_v1 = ResourceKey.of("test_document_resource", "1"); + + String testDocumentActionName = "test_document"; + + ResourceKey person_v0 = ResourceKey.of("test_person_resource", "0"); + ResourceKey person_v1 = ResourceKey.of("test_person_resource", "1"); + ResourceKey person_v1_0 = ResourceKey.of("test_person_resource", "1.0"); + ResourceKey person_v1_0_0 = ResourceKey.of("test_person_resource", "1.0.0"); + ResourceKey person_v1_1 = ResourceKey.of("test_person_resource", "1.1"); + ResourceKey person_v1_1_0 = ResourceKey.of("test_person_resource", "1.1.0"); + ResourceKey person_v1_2 = ResourceKey.of("test_person_resource", "1.2"); + ResourceKey person_v2 = ResourceKey.of("test_person_resource", "2"); + ResourceKey person_v3 = ResourceKey.of("test_person_resource", "3"); + ResourceKey person_v4 = ResourceKey.of("test_person_resource", "4"); + ResourceKey person_v4_2 = ResourceKey.of("test_person_resource", "4.2"); + ResourceKey person_v4_3 = ResourceKey.of("test_person_resource", "4.3"); + ResourceKey person_v4_3_6 = ResourceKey.of("test_person_resource", "4.3.6"); + ResourceKey person_v4_3_7 = ResourceKey.of("test_person_resource", "4.3.7"); + ResourceKey person_v4_3_8 = ResourceKey.of("test_person_resource", "4.3.8"); + ResourceKey person_v4_4 = ResourceKey.of("test_person_resource", "4.4"); + ResourceKey person_v5 = ResourceKey.of("test_person_resource", "5"); + + ResourceKey expectedDocument_v1_0_0 = ResourceKey.of("test_document_resource", "1.0.0"); + + ResourceKey expectedPerson_v1_0_0 = TEST_PERSON_RESOURCE_KEY; // "test_person_resource", "1.0.0" + ResourceKey expectedPerson_v1_1_0 = ResourceKey.of("test_person_resource", "1.1.0"); + ResourceKey expectedPerson_v2_0_0 = ResourceKey.of("test_person_resource", "2.0.0"); + ResourceKey expectedPerson_v4_3_7 = ResourceKey.of("test_person_resource", "4.3.7"); + + + // base test for test_resource + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resouce_v0)); + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resource_v1)); + // base test for test_document + assertTrue(resourcePool.get(document_v0) instanceof DefaultResource); + assertTrue(resourcePool.get(document_v1) instanceof DefaultResource); + // demonstrate no person resources in resource pool + assertTrue(resourcePool.get(person_v0) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v1) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v1_0) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v1_1) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v1_1_0) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v1_2) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v2) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v3) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v4) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v4_2) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v4_3) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v4_3_6) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v4_3_7) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v4_3_8) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v4_4) instanceof DefaultResource); + assertTrue(resourcePool.get(person_v5) instanceof DefaultResource); + + loadTestModel(); + resourcePool.reload(); + // base test for test_resource + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resouce_v0)); + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resource_v1)); + // base test for test_document + assertTrue(resourcePool.get(document_v0) instanceof DefaultResource); + assertResource(expectedDocument_v1_0_0, testDocumentActionName, resourcePool.get(document_v1)); + + // no person version 0 in pool + assertTrue(resourcePool.get(person_v0) instanceof DefaultResource); + // person resource version 1.0.0 has no max version, any major version request greater than 1 should return version 1.0.0 + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1)); + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v2)); + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v3)); + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v4)); + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v5)); + + + loadPersonVersion1_1Model(); + resourcePool.reload(); + // base test for test_resource + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resouce_v0)); + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resource_v1)); + // base test for test_document + assertTrue(resourcePool.get(document_v0) instanceof DefaultResource); + assertResource(expectedDocument_v1_0_0, testDocumentActionName, resourcePool.get(document_v1)); + + // no person version 0 in pool + assertTrue(resourcePool.get(person_v0) instanceof DefaultResource); + // still able to get person version 1.0.0 + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_0)); + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_0_0)); + + // able to get person version 1.1.0 from varying levels of specificity + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_1)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_1_0)); + + // person version 1 does not have specific minor version 2 + assertTrue(resourcePool.get(person_v1_2) instanceof DefaultResource); + + // person resource version 1.1.0 has no max version, any major version request greater than 1 should return version 1.1.0 + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v2)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v3)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v4)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v5)); + + + loadPersonVersion2Model(); + resourcePool.reload(); + // base test for test_resource + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resouce_v0)); + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resource_v1)); + // base test for test_document + assertTrue(resourcePool.get(document_v0) instanceof DefaultResource); + assertResource(expectedDocument_v1_0_0, testDocumentActionName, resourcePool.get(document_v1)); + + // no person version 0 in pool + assertTrue(resourcePool.get(person_v0) instanceof DefaultResource); + // still able to get person version 1.0.0 + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_0)); + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_0_0)); + + // able to get person version 1.1.0 from varying levels of specificity + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_1)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_1_0)); + + // person version 1 does not have specific minor version 2 + assertTrue(resourcePool.get(person_v1_2) instanceof DefaultResource); + + // able to get person version 2.0.0 + assertResource(expectedPerson_v2_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v2)); + + // person resource version 2.0.0 has no max version, any major version request greater than 2 should return version 2.0.0 + assertResource(expectedPerson_v2_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v3)); + assertResource(expectedPerson_v2_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v4)); + assertResource(expectedPerson_v2_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v5)); + + + loadPersonVersion4_3_7Model(); + resourcePool.reload(); + // base test for test_resource + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resouce_v0)); + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(resource_v1)); + // base test for test_document + assertTrue(resourcePool.get(document_v0) instanceof DefaultResource); + assertResource(expectedDocument_v1_0_0, testDocumentActionName, resourcePool.get(document_v1)); + + + // no person version 0 in pool + assertTrue(resourcePool.get(person_v0) instanceof DefaultResource); + // still able to get person version 1.0.0 + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_0)); + assertResource(expectedPerson_v1_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_0_0)); + + // able to get person version 1.1.0 from varying levels of specificity + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_1)); + assertResource(expectedPerson_v1_1_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v1_1_0)); + + // person version 1 does not have specific minor version 2 + assertTrue(resourcePool.get(person_v1_2) instanceof DefaultResource); + + // still able to get person version 2.0.0 + assertResource(expectedPerson_v2_0_0, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v2)); + + // skipped a version from 2.0.0 to 4.3.7 and 2.0.0 has max of 2.0.0 + assertTrue(resourcePool.get(person_v3) instanceof DefaultResource); + + // no version 4.2 exists + assertTrue(resourcePool.get(person_v4_2) instanceof DefaultResource); + // version 4.3.6 does not exist + assertTrue(resourcePool.get(person_v4_3_6) instanceof DefaultResource); + // version 4.3.8 does not exist + assertTrue(resourcePool.get(person_v4_3_8) instanceof DefaultResource); + + // no version 4.4 exists + assertTrue(resourcePool.get(person_v4_4) instanceof DefaultResource); + + // able to get person version 4.3.7 at varying levels of specificity + assertResource(expectedPerson_v4_3_7, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v4)); + assertResource(expectedPerson_v4_3_7, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v4_3)); + assertResource(expectedPerson_v4_3_7, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v4_3_7)); + + // person resource version 4.3.7 has no max version, any major version request greater than 4 should return version 2.0.0 + assertResource(expectedPerson_v4_3_7, TEST_PERSON_ACTION_NAME, resourcePool.get(person_v5)); + } + + @Test + public void testPrintKeys() throws IOException { + ResourcePool resourcePool = initWithDefaultModel(); + + resourcePool.printKeys(); + // nothing to assert + } + + @Test + public void testAdd() throws IOException, ConfigurationBeanLoaderException { + ResourcePool resourcePool = initWithDefaultModel(); + + loadTestModel(); + + Resource resource = loader.loadInstance(TEST_PERSON_RESOURCE_URI, Resource.class); + + resourcePool.add(TEST_PERSON_RESOURCE_URI, resource); + + assertEquals(0, resourcePool.obsoleteCount()); + + assertResource(TEST_PERSON_RESOURCE_KEY, TEST_PERSON_ACTION_NAME, resourcePool.get(TEST_PERSON_RESOURCE_KEY)); + } + + @Test + public void testAddHasClient() throws IOException, ConfigurationBeanLoaderException { + ResourcePool resourcePool = initWithDefaultModel(); + + loadTestModel(); + + resourcePool.reload(); + + Resource resource = loader.loadInstance(TEST_PERSON_RESOURCE_URI, Resource.class); + + assertEquals(0, resourcePool.obsoleteCount()); + + Resource resourceHasClient = resourcePool.get(TEST_PERSON_RESOURCE_KEY); + + resourcePool.add(TEST_PERSON_RESOURCE_URI, resource); + + assertEquals(1, resourcePool.obsoleteCount()); + + resourceHasClient.removeClient(); + } + + @Test(expected = RuntimeException.class) + public void testAddWithoutModelLoaded() throws IOException, ConfigurationBeanLoaderException { + ResourcePool resourcePool = initWithDefaultModel(); + + loadTestModel(); + + Resource resource = loader.loadInstance(TEST_PERSON_RESOURCE_URI, Resource.class); + + reset(); + + assertTrue(resourcePool.get(TEST_PERSON_RESOURCE_KEY) instanceof DefaultResource); + + resourcePool.add(TEST_PERSON_RESOURCE_URI, resource); + } + + @Test + public void testRemove() throws IOException, ConfigurationBeanLoaderException { + ResourcePool resourcePool = initWithDefaultModel(); + + loadTestModel(); + + resourcePool.reload(); + + Resource resource = resourcePool.get(TEST_PERSON_RESOURCE_KEY); + + assertFalse(resource instanceof DefaultResource); + + resource.removeClient(); + + reset(); + + resourcePool.remove(TEST_PERSON_RESOURCE_URI, TEST_PERSON_RESOURCE_KEY); + + assertEquals(0, resourcePool.obsoleteCount()); + + assertTrue(resourcePool.get(TEST_PERSON_RESOURCE_KEY) instanceof DefaultResource); + } + + @Test + public void testRemoveHasClient() throws IOException, ConfigurationBeanLoaderException { + ResourcePool resourcePool = initWithDefaultModel(); + + loadTestModel(); + + resourcePool.reload(); + + Resource resourceHasClient = resourcePool.get(TEST_PERSON_RESOURCE_KEY); + + assertFalse(resourceHasClient instanceof DefaultResource); + + setup(); + + resourcePool.init(servletContext); + + resourcePool.remove(TEST_PERSON_RESOURCE_URI, TEST_PERSON_RESOURCE_KEY); + + assertEquals(1, resourcePool.obsoleteCount()); + + assertTrue(resourcePool.get(TEST_PERSON_RESOURCE_KEY) instanceof DefaultResource); + + resourceHasClient.removeClient(); + } + + @Test(expected = RuntimeException.class) + public void testRemoveWithModelLoaded() throws IOException, ConfigurationBeanLoaderException { + ResourcePool resourcePool = initWithDefaultModel(); + + loadTestModel(); + + resourcePool.reload(); + + resourcePool.remove(TEST_PERSON_RESOURCE_URI, TEST_PERSON_RESOURCE_KEY); + } + + @Test + public void testReloadSingle() throws IOException { + ResourcePool resourcePool = initWithDefaultModel(); + + loadTestModel(); + + Resource resource = resourcePool.get(TEST_PERSON_RESOURCE_KEY); + + assertTrue(resource instanceof DefaultResource); + + resourcePool.reload(TEST_PERSON_RESOURCE_URI); + + assertResource(TEST_PERSON_RESOURCE_KEY, TEST_PERSON_ACTION_NAME, resourcePool.get(TEST_PERSON_RESOURCE_KEY)); + } + + @Test + public void testReload() throws IOException { + ResourcePool resourcePool = initWithDefaultModel(); + + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(TEST_RESOURCE_KEY)); + + loadTestModel(); + + resourcePool.reload(); + + assertEquals(8, resourcePool.count()); + assertEquals(0, resourcePool.obsoleteCount()); + + assertResource(TEST_PERSON_RESOURCE_KEY, TEST_PERSON_ACTION_NAME, resourcePool.get(TEST_PERSON_RESOURCE_KEY)); + } + + @Test + public void testReloadThreadSafety() throws IOException { + ResourcePool resourcePool = initWithDefaultModel(); + + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(TEST_RESOURCE_KEY)); + + loadTestModel(); + + CompletableFuture reloadFuture = CompletableFuture.runAsync(() -> resourcePool.reload()); + + while (!reloadFuture.isDone()) { + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(TEST_RESOURCE_KEY)); + } + + assertResource(TEST_RESOURCE_KEY, TEST_ACTION_NAME, resourcePool.get(TEST_RESOURCE_KEY)); + + assertResource(TEST_PERSON_RESOURCE_KEY, TEST_PERSON_ACTION_NAME, resourcePool.get(TEST_PERSON_RESOURCE_KEY)); + } + + @Test + public void testRealodOfResourceHasClient() throws IOException { + ResourcePool resourcePool = initWithDefaultModel(); + + loadTestModel(); + + Resource resource = resourcePool.get(TEST_RESOURCE_KEY); + + CompletableFuture reloadFuture = CompletableFuture.runAsync(() -> resourcePool.reload()); + + while (!reloadFuture.isDone()) { + assertEquals(TEST_RESOURCE_KEY, resource.getKey()); + } + + resource.removeClient(); + } + + @Test + public void testClientsManagement() throws IOException, InterruptedException { + ResourcePool resourcePool = initWithDefaultModel(); + + resourcePool.reload(); + + long initalCount = resourcePool.obsoleteCount(); + Resource resource = resourcePool.get(TEST_RESOURCE_KEY); + + resource.removeClient(); + + assertFalse(resource.hasClients()); + + Thread t1 = getResourceInThread(resourcePool, TEST_RESOURCE_KEY); + + t1.join(); + + assertTrue(resource.hasClients()); + + resourcePool.reload(); + + assertEquals(initalCount, resourcePool.obsoleteCount()); + } + + private Thread getResourceInThread(ResourcePool resourcePool, ResourceKey resourceKey) { + Runnable client = new Runnable() { + @Override + public void run() { + Resource resource = resourcePool.get(resourceKey); + assertEquals(resourceKey, resource.getKey()); + assertTrue(resource.hasClients()); + } + }; + Thread thread = new Thread(client); + thread.start(); + return thread; + } + + private ResourcePool initWithDefaultModel() throws IOException { + loadDefaultModel(); + + ResourcePool resourcePool = ResourcePool.getInstance(); + resourcePool.init(servletContext); + + ActionPool actionPool = ActionPool.getInstance(); + actionPool.init(servletContext); + + return resourcePool; + } + + private void loadPersonVersion1_1Model() throws IOException { + // versioning action reuses testSparqlQuery1 from testing action + loadModel( + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person1_1.n3") + ); + } + + private void loadPersonVersion2Model() throws IOException { + // versioning action reuses testSparqlQuery1 from testing action + loadModel( + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person2.n3") + ); + } + + private void loadPersonVersion4_3_7Model() throws IOException { + // versioning action reuses testSparqlQuery1 from testing action + loadModel( + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person4_3_7.n3") + ); + } + + private void assertResource(ResourceKey expctedResourceKey, String expectedActionName, Resource actualResource) { + assertNotNull(actualResource); + assertFalse(format("%s not loaded!", expctedResourceKey), actualResource instanceof DefaultResource); + assertEquals(expctedResourceKey, actualResource.getKey()); + assertTrue(actualResource.hasClients()); + + assertEquals(expectedActionName, actualResource.getRpcOnGet().getName()); + assertEquals("GET", actualResource.getRpcOnGet().getHttpMethod().getName()); + assertEquals(expctedResourceKey.getVersion().toString(), actualResource.getRpcOnGet().getMinVersion()); + assertEquals(expectedActionName, actualResource.getRpcOnPost().getName()); + assertEquals("POST", actualResource.getRpcOnPost().getHttpMethod().getName()); + assertEquals(expctedResourceKey.getVersion().toString(), actualResource.getRpcOnPost().getMinVersion()); + assertEquals(expectedActionName, actualResource.getRpcOnDelete().getName()); + assertEquals("DELETE", actualResource.getRpcOnDelete().getHttpMethod().getName()); + assertEquals(expctedResourceKey.getVersion().toString(), actualResource.getRpcOnDelete().getMinVersion()); + assertEquals(expectedActionName, actualResource.getRpcOnPut().getName()); + assertEquals("PUT", actualResource.getRpcOnPut().getHttpMethod().getName()); + assertEquals(expctedResourceKey.getVersion().toString(), actualResource.getRpcOnPut().getMinVersion()); + assertEquals(expectedActionName, actualResource.getRpcOnPatch().getName()); + assertEquals("PATCH", actualResource.getRpcOnPatch().getHttpMethod().getName()); + assertEquals(expctedResourceKey.getVersion().toString(), actualResource.getRpcOnPatch().getMinVersion()); + + actualResource.removeClient(); + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ServletContextIT.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ServletContextIT.java new file mode 100644 index 0000000000..2f021153b9 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ServletContextIT.java @@ -0,0 +1,61 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +public abstract class ServletContextIT extends ServletContextTest { + + protected Map parameterMap; + + protected int status; + + public void setup() { + super.setup(); + + parameterMap = new HashMap<>(); + + status = 0; + } + + protected String readMockFile(String path) throws IOException { + return super.readFile("src/test/resources/dynapi/mock/" + path); + } + + protected void mockParameterIntoMap(String name, String value) { + if (value == null) { + parameterMap.put(name, new String[] { }); + } else { + parameterMap.put(name, new String[] { value }); + } + } + + protected void mockStatus(HttpServletResponse response) { + doAnswer(invocation -> { + if (invocation.getArguments().length == 1) { + status = invocation.getArgument(0); + } + + return status; + }).when(response).setStatus(any(Integer.class)); + + when(response.getStatus()).thenAnswer(invocation -> { + return status; + }); + } + + protected void runCallback(Method callback) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + if (callback != null) { + callback.invoke(this); + } + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ServletContextTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ServletContextTest.java new file mode 100644 index 0000000000..e54287849d --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ServletContextTest.java @@ -0,0 +1,102 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.FULL_UNION; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.jena.ontology.OntModel; +import org.apache.jena.rdf.model.ModelFactory; +import org.junit.Before; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.ResourceKey; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoader; +import stubs.edu.cornell.mannlib.vitro.webapp.modelaccess.ContextModelAccessStub; +import stubs.edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccessFactoryStub; +import stubs.javax.servlet.ServletContextStub; + +public abstract class ServletContextTest { + + protected final static String TEST_ACTION_NAME = "test_action"; + protected final static ResourceKey TEST_RESOURCE_KEY = ResourceKey.of("test_resource", "0.1.0"); + + protected final static String TEST_PERSON_ACTION_NAME = "test_person"; + protected final static ResourceKey TEST_PERSON_RESOURCE_KEY = ResourceKey.of("test_person_resource", "1.0.0"); + + protected ServletContextStub servletContext; + protected ModelAccessFactoryStub modelAccessFactory; + protected ContextModelAccessStub contentModelAccess; + protected OntModel ontModel; + + protected ConfigurationBeanLoader loader; + + @Before + public void setup() { + servletContext = new ServletContextStub(); + modelAccessFactory = new ModelAccessFactoryStub(); + + contentModelAccess = modelAccessFactory.get(servletContext); + + ontModel = ModelFactory.createOntologyModel(); + + contentModelAccess.setOntModel(FULL_UNION, ontModel); + + loader = new ConfigurationBeanLoader(ontModel, servletContext); + } + + protected void loadModelsFromN3(String fileFormat, String... paths) throws IOException { + for(String path : paths){ + loadModel( + new RDFFile(fileFormat, path) + ); + } + } + + protected void loadTestModel() throws IOException { + // all actions reuse testSparqlQuery1 from testing action + loadModel( + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-collection.n3"), + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-concept.n3"), + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-document.n3"), + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-organization.n3"), + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person.n3"), + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-process.n3"), + new RDFFile("N3", "src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-relationship.n3") + ); + } + + protected void loadDefaultModel() throws IOException { + loadModel( + new RDFFile("N3", "../home/src/main/resources/rdf/tbox/filegraph/dynamic-api-implementation.n3"), + new RDFFile("N3", "../home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals.n3"), + new RDFFile("N3", "../home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals-testing.n3") + ); + } + + protected void loadModel(RDFFile... rdfFiles) throws IOException { + for (RDFFile rdfFile : rdfFiles) { + String rdf = readFile(rdfFile.path); + ontModel.read(new StringReader(rdf), null, rdfFile.format); + } + } + + protected String readFile(String path) throws IOException { + Path p = new File(path).toPath(); + + return new String(Files.readAllBytes(p)); + } + + protected class RDFFile { + private final String format; + private final String path; + + protected RDFFile(String format, String path) { + this.format = format; + this.path = path; + } + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/SolrQueryTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/SolrQueryTest.java new file mode 100644 index 0000000000..dfed1a7b14 --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/SolrQueryTest.java @@ -0,0 +1,209 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +import edu.cornell.mannlib.vitro.webapp.application.ApplicationImpl; +import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Action; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.Parameter; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.SolrQuery; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; +import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; +import edu.cornell.mannlib.vitro.webapp.utils.configuration.ConfigurationBeanLoaderException; +import org.junit.*; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.IOException; + +@RunWith(MockitoJUnitRunner.class) +public class SolrQueryTest extends ServletContextTest{ + + private final static String TEST_DATA_PATH="src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-solr-test.n3"; + private final static String TEST_SOLR_QUERY_URI="https://vivoweb.org/ontology/vitro-dynamic-api/solrQuery/genericSolrTextQuery"; + + @Spy + private SolrQuery solrQuery; + + private static MockedStatic applicationUtils; + + @Mock + ApplicationImpl application; + + @Mock + private Parameter parameter1; + + @Mock + private OperationData input; + + @Mock + private SearchEngine searchEngine; + + @Mock + private SearchQuery searchQuery; + + @BeforeClass + public static void setupStaticObjects() { + applicationUtils = mockStatic(ApplicationUtils.class); + } + + @AfterClass + public static void afterEach() { + applicationUtils.close(); + } + + @Before + public void setupQuery(){ + when(ApplicationUtils.instance()).thenReturn(application); + when(application.getSearchEngine()).thenReturn(searchEngine); + when(searchEngine.createQuery()).thenReturn(searchQuery); + this.solrQuery = new SolrQuery(); + } + + @Test + public void testLoadingAndPropertiesSetup() throws IOException, ConfigurationBeanLoaderException { + loadDefaultModel(); + loadModelsFromN3(TEST_DATA_PATH.split("\\.")[1],TEST_DATA_PATH); + + SolrQuery query = loader.loadInstance(TEST_SOLR_QUERY_URI, SolrQuery.class); + assertNotNull(query); + assertEquals(2, query.getSorts().size()); + assertEquals("http", query.getQueryText()); + assertEquals(1, query.getFacets().size()); + assertEquals(2, query.getFields().size()); + assertEquals("10", query.getLimit()); + assertEquals("3", query.getOffset()); + } + + @Test + public void requiredParameterMissingFromInput(){ + when(parameter1.getName()).thenReturn("testParameter"); + solrQuery.addRequiredParameter(parameter1); + when(input.has("testParameter")).thenReturn(false); + assertTrue(solrQuery.run(input).hasError()); + verify(parameter1, times(1)).getName(); + verify(input,times(1)).has("testParameter"); + } + + @Test + public void requiredParameterPresentButInvalid(){ + when(parameter1.getName()).thenReturn("testParameter"); + when(parameter1.isValid(eq("testParameter"), any(String[].class))).thenReturn(false); + solrQuery.addRequiredParameter(parameter1); + when(input.has("testParameter")).thenReturn(true); + when(input.get("testParameter")).thenReturn(new String[]{"testValue"}); + assertTrue(solrQuery.run(input).hasError()); + + verify(parameter1,times(1)).getName(); + verify(parameter1,times(1)).isValid(eq("testParameter"), any(String[].class)); + } + + @Test + public void requiredParameterPresentAndValid(){ + when(parameter1.getName()).thenReturn("testParameter"); + when(parameter1.isValid(eq("testParameter"), any(String[].class))).thenReturn(true); + solrQuery.addRequiredParameter(parameter1); + when(input.has("testParameter")).thenReturn(true); + when(input.get("testParameter")).thenReturn(new String[]{"testValue"}); + assertFalse(solrQuery.run(input).hasError()); + + verify(parameter1,times(1)).getName(); + verify(parameter1,times(1)).isValid(eq("testParameter"), any(String[].class)); + } + + @Test + public void queryWithOnlySimpleTextSearch(){ + when(searchQuery.setQuery("testSearchText")).thenReturn(searchQuery); + solrQuery.setQueryText("testSearchText"); + + assertFalse(solrQuery.run(input).hasError()); + verify(searchQuery,times(1)).setQuery("testSearchText"); + } + + @Test + public void queryWithVariableInsideTextSearch(){ + when(input.has("testParameter")).thenReturn(false); + solrQuery.setQueryText("testSearchText OR ?testParameter"); + + assertTrue(solrQuery.run(input).hasError()); + verify(searchQuery,times(0)).setQuery(any()); + verify(input,times(1)).has(any()); + } + + @Test + public void queryWithVariableInsideTextSearchWithSubstitution(){ + when(searchQuery.setQuery(any(String.class))).thenReturn(searchQuery); + when(input.has("testParameter")).thenReturn(true); + when(input.get("testParameter")).thenReturn(new String[]{"testValue"}); + solrQuery.setQueryText("testSearchText OR ?testParameter"); + + assertFalse(solrQuery.run(input).hasError()); + verify(searchQuery,times(1)).setQuery("testSearchText OR testValue"); + } + + @Test + public void queryWithMultipleVariableInsideTextSearchWithSubstitution(){ + when(searchQuery.setQuery(any(String.class))).thenReturn(searchQuery); + when(input.has(anyString())).thenReturn(true); + when(input.get("testParam1")).thenReturn(new String[]{"testValue1"}); + when(input.get("testParam2")).thenReturn(new String[]{"testValue2"}); + solrQuery.setQueryText("?testParam1 OR ?testParam2"); + + assertFalse(solrQuery.run(input).hasError()); + verify(searchQuery,times(1)).setQuery("testValue1 OR testValue2"); + } + + @Test + public void queryWithLimitAndOffsetWrongValue(){ + when(searchQuery.setStart(anyInt())).thenReturn(searchQuery); + when(input.has(anyString())).thenReturn(true); + when(input.get("testParam1")).thenReturn(new String[]{"11"}); + when(input.get("testParam2")).thenReturn(new String[]{"testValue2"}); + solrQuery.setOffset("?testParam1"); + solrQuery.setLimit("?testParam2"); + + assertTrue(solrQuery.run(input).hasError()); + verify(searchQuery,times(0)).setQuery(any()); + verify(searchQuery,times(1)).setStart(11); + verify(searchQuery,times(0)).setRows(anyInt()); + } + + @Test + public void queryWithMultipleSortsBadInput(){ + when(input.has(anyString())).thenReturn(true); + when(input.get("testParam1")).thenReturn(new String[]{"field1"}); + when(input.get("testParam2")).thenReturn(new String[]{"field2"}); + //Should fail because in this case testParam3 must be keyword desc or asc + when(input.get("testParam3")).thenReturn(new String[]{"field3"}); + //more spaces are added on purpose to simulate RDF input that's not well formatted + solrQuery.addSort(" ?testParam1 desc "); + solrQuery.addSort("?testParam2 ?testParam3"); + + assertTrue(solrQuery.run(input).hasError()); + assertEquals(solrQuery.getSorts().size(),2); + verify(searchQuery,times(1)).addSortField(anyString(),any()); + } + + @Test + public void queryWithMultipleSorts(){ + when(searchQuery.addSortField(anyString(),any())).thenReturn(searchQuery); + when(input.has(anyString())).thenReturn(true); + when(input.get("testParam1")).thenReturn(new String[]{"field1"}); + when(input.get("testParam2")).thenReturn(new String[]{"field2"}); + //Should fail because in this case testParam3 must be keyword desc or asc + when(input.get("testParam3")).thenReturn(new String[]{"asc"}); + //more spaces are added on purpose to simulate RDF input that's not well formatted + solrQuery.addSort(" ?testParam1 desc "); + solrQuery.addSort("?testParam2 ?testParam3"); + + assertFalse(solrQuery.run(input).hasError()); + assertEquals(solrQuery.getSorts().size(),2); + verify(searchQuery,times(2)).addSortField(anyString(),any()); + } +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ValidatorsTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ValidatorsTest.java new file mode 100644 index 0000000000..8d647d3f4f --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/ValidatorsTest.java @@ -0,0 +1,124 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import edu.cornell.mannlib.vitro.webapp.dynapi.components.validators.IsInteger; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.validators.IsNotBlank; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.validators.NumericRangeValidator; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.validators.RegularExpressionValidator; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.validators.StringLengthRangeValidator; +import edu.cornell.mannlib.vitro.webapp.dynapi.components.validators.Validator; + +public class ValidatorsTest { + + @Test + public void testIsIntegerValidator() { + + Validator validator = new IsInteger(); + + String fieldName = "field"; + + String[] values1 = { "1245", "-123" }; + assertTrue(validator.isValid(fieldName, values1)); + + String[] values2 = { "1245.2" }; + assertFalse(validator.isValid(fieldName, values2)); + } + + @Test + public void testIsNotBlankValidator() { + + Validator validator = new IsNotBlank(); + + String fieldName = "field"; + + String[] values1 = {}; + assertFalse(validator.isValid(fieldName, values1)); + + String[] values2 = { "" }; + assertFalse(validator.isValid(fieldName, values2)); + + String[] values3 = { "a string" }; + assertTrue(validator.isValid(fieldName, values3)); + } + + @Test + public void testNumericRangeValidator() { + + NumericRangeValidator validator1 = new NumericRangeValidator(); + validator1.setMaxValue(40.3f); + NumericRangeValidator validator2 = new NumericRangeValidator(); + validator2.setMinValue(36); + NumericRangeValidator validator3 = new NumericRangeValidator(); + validator3.setMinValue(36); + validator3.setMaxValue(40.3f); + + String fieldName = "field"; + + String[] values1 = { "35" }; + assertTrue(validator1.isValid(fieldName, values1)); + assertFalse(validator2.isValid(fieldName, values1)); + assertFalse(validator3.isValid(fieldName, values1)); + + String[] values2 = { "36.3" }; + assertTrue(validator1.isValid(fieldName, values2)); + assertTrue(validator2.isValid(fieldName, values2)); + assertTrue(validator3.isValid(fieldName, values2)); + + String[] values3 = { "42" }; + assertFalse(validator1.isValid(fieldName, values3)); + assertTrue(validator2.isValid(fieldName, values3)); + assertFalse(validator3.isValid(fieldName, values3)); + } + + @Test + public void testStringLengthRangeValidator() { + + StringLengthRangeValidator validator1 = new StringLengthRangeValidator(); + validator1.setMaxLength(7); + StringLengthRangeValidator validator2 = new StringLengthRangeValidator(); + validator2.setMinLength(5); + StringLengthRangeValidator validator3 = new StringLengthRangeValidator(); + validator3.setMinLength(5); + validator3.setMaxLength(7); + + String fieldName = "field"; + + String[] values1 = { "test" }; + assertTrue(validator1.isValid(fieldName, values1)); + assertFalse(validator2.isValid(fieldName, values1)); + assertFalse(validator3.isValid(fieldName, values1)); + + String[] values2 = { "testte" }; + assertTrue(validator1.isValid(fieldName, values2)); + assertTrue(validator2.isValid(fieldName, values2)); + assertTrue(validator3.isValid(fieldName, values2)); + + String[] values3 = { "testtest" }; + assertFalse(validator1.isValid(fieldName, values3)); + assertTrue(validator2.isValid(fieldName, values3)); + assertFalse(validator3.isValid(fieldName, values3)); + } + + @Test + public void testRegularExpressionValidator() { + + RegularExpressionValidator validator1 = new RegularExpressionValidator(); + validator1.setRegularExpression("^(.+)@(\\S+)$"); + + String fieldName = "email"; + + String[] values1 = { "dragan@uns.ac.rs" }; + assertTrue(validator1.isValid(fieldName, values1)); + + String[] values2 = { "dragan@" }; + assertFalse(validator1.isValid(fieldName, values2)); + + String[] values3 = { "uns.ac.rs" }; + assertFalse(validator1.isValid(fieldName, values3)); + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/request/RequestPathTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/request/RequestPathTest.java new file mode 100644 index 0000000000..db9f48383f --- /dev/null +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/dynapi/request/RequestPathTest.java @@ -0,0 +1,174 @@ +package edu.cornell.mannlib.vitro.webapp.dynapi.request; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.Base64; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import edu.cornell.mannlib.vitro.webapp.dynapi.request.RequestPath.RequestType; + +@RunWith(MockitoJUnitRunner.class) +public class RequestPathTest { + + private String resourceId = "https://scholars.institution.edu/individual/n1f9d4ddc"; + private String encodedResourceId = Base64.getEncoder().encodeToString(resourceId.getBytes()); + + @Mock + private HttpServletRequest request; + + @Test + public void testRpc() { + when(request.getContextPath()).thenReturn("/api/rpc/create"); + when(request.getPathInfo()).thenReturn("/create"); + + RequestPath requestPath = RequestPath.from(request); + + assertTrue(requestPath.isValid()); + assertFalse(requestPath.isCustomRestAction()); + assertEquals(RequestType.RPC, requestPath.getType()); + assertEquals("create", requestPath.getActionName()); + assertEquals(null, requestPath.getResourceVersion()); + assertEquals(null, requestPath.getResourceName()); + assertEquals(null, requestPath.getResourceId()); + + + when(request.getContextPath()).thenReturn("/api/rpc/"); + when(request.getPathInfo()).thenReturn("/"); + + assertFalse(RequestPath.from(request).isValid()); + + + when(request.getContextPath()).thenReturn(null); + when(request.getPathInfo()).thenReturn("/create"); + + assertFalse(RequestPath.from(request).isValid()); + + + when(request.getContextPath()).thenReturn("/api/rpc"); + when(request.getPathInfo()).thenReturn(null); + + assertFalse(RequestPath.from(request).isValid()); + + + when(request.getContextPath()).thenReturn(null); + when(request.getPathInfo()).thenReturn(null); + + assertFalse(RequestPath.from(request).isValid()); + } + + @Test + public void testRestCollection() { + when(request.getContextPath()).thenReturn("/api/rest/1/persons"); + when(request.getPathInfo()).thenReturn("/1/persons"); + + RequestPath requestPath = RequestPath.from(request); + + assertTrue(requestPath.isValid()); + assertFalse(requestPath.isCustomRestAction()); + assertEquals(RequestType.REST, requestPath.getType()); + assertEquals("1", requestPath.getResourceVersion()); + assertEquals("persons", requestPath.getResourceName()); + assertEquals(null, requestPath.getResourceId()); + assertEquals(null, requestPath.getActionName()); + + + when(request.getContextPath()).thenReturn("/api/rest/"); + when(request.getPathInfo()).thenReturn("/"); + + assertFalse(RequestPath.from(request).isValid()); + + + when(request.getContextPath()).thenReturn(null); + when(request.getPathInfo()).thenReturn("/1/persons"); + + assertFalse(RequestPath.from(request).isValid()); + + + when(request.getContextPath()).thenReturn("/api/rest"); + when(request.getPathInfo()).thenReturn(null); + + assertFalse(RequestPath.from(request).isValid()); + + + when(request.getContextPath()).thenReturn(null); + when(request.getPathInfo()).thenReturn(null); + + assertFalse(RequestPath.from(request).isValid()); + } + + @Test + public void testRestCustomAction() { + when(request.getContextPath()).thenReturn("/api/rest/1/persons/dedupe"); + when(request.getPathInfo()).thenReturn("/1/persons/dedupe"); + + RequestPath requestPath = RequestPath.from(request); + + assertTrue(requestPath.isValid()); + assertTrue(requestPath.isCustomRestAction()); + assertEquals(RequestType.REST, requestPath.getType()); + assertEquals("1", requestPath.getResourceVersion()); + assertEquals("persons", requestPath.getResourceName()); + assertEquals(null, requestPath.getResourceId()); + assertEquals("dedupe", requestPath.getActionName()); + } + + @Test + public void testRestIndividual() { + when(request.getContextPath()).thenReturn("/api/rest/1/persons/resource:" + encodedResourceId); + when(request.getPathInfo()).thenReturn("/1/persons/resource:" + encodedResourceId); + + RequestPath requestPath = RequestPath.from(request); + + assertTrue(requestPath.isValid()); + assertFalse(requestPath.isCustomRestAction()); + assertEquals(RequestType.REST, requestPath.getType()); + assertEquals("1", requestPath.getResourceVersion()); + assertEquals("persons", requestPath.getResourceName()); + assertEquals(resourceId, requestPath.getResourceId()); + assertEquals(null, requestPath.getActionName()); + } + + @Test + public void testRestIndividualCustomAction() { + when(request.getContextPath()).thenReturn("/api/rest/1/persons/resource:" + encodedResourceId + "/patch"); + when(request.getPathInfo()).thenReturn("/1/persons/resource:" + encodedResourceId + "/patch"); + + RequestPath requestPath = RequestPath.from(request); + + assertTrue(requestPath.isValid()); + assertTrue(requestPath.isCustomRestAction()); + assertEquals(RequestType.REST, requestPath.getType()); + assertEquals("1", requestPath.getResourceVersion()); + assertEquals("persons", requestPath.getResourceName()); + assertEquals(resourceId, requestPath.getResourceId()); + assertEquals("patch", requestPath.getActionName()); + } + + @Test + public void testNotFound() { + when(request.getContextPath()).thenReturn("/api/rest/1/persons/resource:" + encodedResourceId + "/patch/foo"); + when(request.getPathInfo()).thenReturn("/1/persons/resource:" + encodedResourceId + "/patch/foo"); + + assertFalse(RequestPath.from(request).isValid()); + + when(request.getContextPath()).thenReturn("/api/bar/1/persons"); + when(request.getPathInfo()).thenReturn("/1/persons"); + + assertFalse(RequestPath.from(request).isValid()); + + when(request.getContextPath()).thenReturn("/some/random/path"); + when(request.getPathInfo()).thenReturn("/3/2/1"); + + assertFalse(RequestPath.from(request).isValid()); + } + +} diff --git a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguageTest.java b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguageTest.java index d1f6b84337..efe8a2b9bd 100644 --- a/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguageTest.java +++ b/api/src/test/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/preprocessors/LimitRemovalsToLanguageTest.java @@ -1,12 +1,14 @@ package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.ResourceFactory; import org.apache.jena.vocabulary.RDFS; import org.junit.Test; -import org.testng.Assert; import edu.cornell.mannlib.vitro.testing.AbstractTestClass; @@ -26,10 +28,10 @@ public void testPreprocess() { retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es")); additions.add(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US")); preproc.preprocess(retractions, additions, null); - Assert.assertEquals(retractions.size(), 1); - Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); - Assert.assertEquals(additions.size(), 1); - Assert.assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US"))); + assertEquals(retractions.size(), 1); + assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); + assertEquals(additions.size(), 1); + assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US"))); additions.removeAll(); retractions.removeAll(); // Keep all retractions unmolested if no labels at all are being re-added. @@ -37,10 +39,10 @@ public void testPreprocess() { retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US")); retractions.add(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es")); preproc.preprocess(retractions, additions, null); - Assert.assertEquals(retractions.size(), 2); - Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); - Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es"))); - Assert.assertEquals(additions.size(), 0); + assertEquals(retractions.size(), 2); + assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); + assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es"))); + assertEquals(additions.size(), 0); additions.removeAll(); retractions.removeAll(); // Keep both retractions if the form supplies new values for both languages @@ -49,12 +51,12 @@ public void testPreprocess() { additions.add(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US")); additions.add(res, RDFS.label, ResourceFactory.createLangLiteral("es2", "es")); preproc.preprocess(retractions, additions, null); - Assert.assertEquals(retractions.size(), 2); - Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); - Assert.assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es"))); - Assert.assertEquals(additions.size(), 2); - Assert.assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US"))); - Assert.assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es2", "es"))); + assertEquals(retractions.size(), 2); + assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US1", "en-US"))); + assertTrue(retractions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es1", "es"))); + assertEquals(additions.size(), 2); + assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("en-US2", "en-US"))); + assertTrue(additions.contains(res, RDFS.label, ResourceFactory.createLangLiteral("es2", "es"))); additions.removeAll(); retractions.removeAll(); } diff --git a/api/src/test/resources/dynapi/mock/sparql/response/json/sparql-empty-success.json b/api/src/test/resources/dynapi/mock/sparql/response/json/sparql-empty-success.json new file mode 100644 index 0000000000..5a0962e173 --- /dev/null +++ b/api/src/test/resources/dynapi/mock/sparql/response/json/sparql-empty-success.json @@ -0,0 +1,10 @@ +{ + "head": { + "vars": [ + "title" + ] + }, + "results": { + "bindings": [] + } +} diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-collection.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-collection.n3 new file mode 100644 index 0000000000..5867a07496 --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-collection.n3 @@ -0,0 +1,74 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test collection action 1"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test collection operational step 1"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create collection 1" ; + dynapi:rpcName "test_collection"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get collection 1" ; + dynapi:rpcName "test_collection"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update collection 1" ; + dynapi:rpcName "test_collection"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch collection 1" ; + dynapi:rpcName "test_collection"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete collection 1" ; + dynapi:rpcName "test_collection"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test collection custom action 1" ; + dynapi:customRESTActionName "test_collection_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_collection_resource" ; + rdfs:label "Test collection resource 1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "1.0.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-concept.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-concept.n3 new file mode 100644 index 0000000000..d7a698ea7b --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-concept.n3 @@ -0,0 +1,74 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test concept action 1"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test concept operational step 1"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create concept 1" ; + dynapi:rpcName "test_concept"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get concept 1" ; + dynapi:rpcName "test_concept"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update concept 1" ; + dynapi:rpcName "test_concept"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch concept 1" ; + dynapi:rpcName "test_concept"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete concept 1" ; + dynapi:rpcName "test_concept"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test concept custom action 1" ; + dynapi:customRESTActionName "test_concept_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_concept_resource" ; + rdfs:label "Test concept resource 1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "1.0.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-document.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-document.n3 new file mode 100644 index 0000000000..f93ee8fac7 --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-document.n3 @@ -0,0 +1,74 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test document action 1"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test document operational step 1"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create document 1" ; + dynapi:rpcName "test_document"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get document 1" ; + dynapi:rpcName "test_document"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update document 1" ; + dynapi:rpcName "test_document"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch document 1" ; + dynapi:rpcName "test_document"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete document 1" ; + dynapi:rpcName "test_document"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test document custom action 1" ; + dynapi:customRESTActionName "test_document_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_document_resource" ; + rdfs:label "Test document resource 1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "1.0.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-organization.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-organization.n3 new file mode 100644 index 0000000000..ae98e452e1 --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-organization.n3 @@ -0,0 +1,74 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test organization action 1"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test organization operational step 1"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create organization 1" ; + dynapi:rpcName "test_organization"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get organization 1" ; + dynapi:rpcName "test_organization"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update organization 1" ; + dynapi:rpcName "test_organization"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch organization 1" ; + dynapi:rpcName "test_organization"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete organization 1" ; + dynapi:rpcName "test_organization"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test organization custom action 1" ; + dynapi:customRESTActionName "test_organization_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_organization_resource" ; + rdfs:label "Test organization resource 1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "1.0.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person.n3 new file mode 100644 index 0000000000..b558b17ef9 --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person.n3 @@ -0,0 +1,74 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test person action 1"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test person operational step 1"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create person 1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get person 1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update person 1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch person 1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete person 1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test person custom action 1" ; + dynapi:customRESTActionName "test_person_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_person_resource" ; + rdfs:label "Test person resource 1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "1.0.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person1_1.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person1_1.n3 new file mode 100644 index 0000000000..902db64c6a --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person1_1.n3 @@ -0,0 +1,92 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test person action 1.1"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test person operational step 1.1"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create person 1.1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.1.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get person 1.1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.1.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update person 1.1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.1.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch person 1.1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.1.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete person 1" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.1.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test person custom action 1.1" ; + dynapi:customRESTActionName "test_person_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.1.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_person_resource" ; + rdfs:label "Test person resource 1.1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "1.1.0" . + + + + dynapi:rpcAPIVersionMax "1.1.0" . + + dynapi:rpcAPIVersionMax "1.1.0" . + + dynapi:rpcAPIVersionMax "1.1.0" . + + dynapi:rpcAPIVersionMax "1.1.0" . + + dynapi:rpcAPIVersionMax "1.1.0" . + + + dynapi:rpcAPIVersionMax "1.1.0" . + + + dynapi:restAPIVersionMax "1.1.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person2.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person2.n3 new file mode 100644 index 0000000000..7bb2f73e7e --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person2.n3 @@ -0,0 +1,91 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test person action 2"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test person operational step 2"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create person 2" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "2.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get person 2" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "2.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update person 2" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "2.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch person 2" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "2.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete person 2" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "2.0.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test person custom action 2" ; + dynapi:customRESTActionName "test_person_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "2.0.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_person_resource" ; + rdfs:label "Test person resource 2"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "2.0.0" . + + + dynapi:rpcAPIVersionMax "1.1.0" . + + dynapi:rpcAPIVersionMax "1.1.0" . + + dynapi:rpcAPIVersionMax "1.1.0" . + + dynapi:rpcAPIVersionMax "1.1.0" . + + dynapi:rpcAPIVersionMax "1.1.0" . + + + dynapi:rpcAPIVersionMax "1.1.0" . + + + dynapi:restAPIVersionMax "1.1.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person4_3_7.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person4_3_7.n3 new file mode 100644 index 0000000000..ae8b961142 --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-person4_3_7.n3 @@ -0,0 +1,91 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test person action 4"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test person operational step 4"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create person 4" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "4.3.7" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get person 4" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "4.3.7" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update person 4" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "4.3.7" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch person 4" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "4.3.7" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete person 4" ; + dynapi:rpcName "test_person"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "4.3.7" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test person custom action 4" ; + dynapi:customRESTActionName "test_person_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "4.3.7" . + + + a dynapi:resource ; + dynapi:resourceName "test_person_resource" ; + rdfs:label "Test person resource 4"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "4.3.7" . + + + dynapi:rpcAPIVersionMax "2.0.0" . + + dynapi:rpcAPIVersionMax "2.0.0" . + + dynapi:rpcAPIVersionMax "2.0.0" . + + dynapi:rpcAPIVersionMax "2.0.0" . + + dynapi:rpcAPIVersionMax "2.0.0" . + + + dynapi:rpcAPIVersionMax "2.0.0" . + + + dynapi:restAPIVersionMax "2.0.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-process.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-process.n3 new file mode 100644 index 0000000000..3c3a29599b --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-process.n3 @@ -0,0 +1,74 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test process action 1"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test process operational step 1"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create process 1" ; + dynapi:rpcName "test_process"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get process 1" ; + dynapi:rpcName "test_process"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update process 1" ; + dynapi:rpcName "test_process"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch process 1" ; + dynapi:rpcName "test_process"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete process 1" ; + dynapi:rpcName "test_process"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test process custom action 1" ; + dynapi:customRESTActionName "test_process_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_process_resource" ; + rdfs:label "Test process resource 1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "1.0.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-relationship.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-relationship.n3 new file mode 100644 index 0000000000..72628d7a1d --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-relationship.n3 @@ -0,0 +1,74 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:action ; + rdfs:label "Test relationship action 1"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test relationship operational step 1"@en-US ; + dynapi:hasOperation . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create relationship 1" ; + dynapi:rpcName "test_relationship"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get relationship 1" ; + dynapi:rpcName "test_relationship"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update relationship 1" ; + dynapi:rpcName "test_relationship"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch relationship 1" ; + dynapi:rpcName "test_relationship"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete relationship 1" ; + dynapi:rpcName "test_relationship"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:customRESTAction ; + dynapi:resourceName "Test relationship custom action 1" ; + dynapi:customRESTActionName "test_relationship_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "1.0.0" . + + + a dynapi:resource ; + dynapi:resourceName "test_relationship_resource" ; + rdfs:label "Test relationship resource 1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "1.0.0" . + diff --git a/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-solr-test.n3 b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-solr-test.n3 new file mode 100644 index 0000000000..263db492ca --- /dev/null +++ b/api/src/test/resources/rdf/abox/filegraph/dynamic-api-individuals-solr-test.n3 @@ -0,0 +1,56 @@ +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix vitro: . +@prefix dynapi: . +@prefix xsd: . + + + + a dynapi:operation, dynapi:solrQuery ; + rdfs:label "Test solr query 1"@en-US ; + dynapi:requiresParameter ; + dynapi:solrQueryText "http" ; + dynapi:solrField "Field1" , "Field2" ; + dynapi:solrFilter "Field2:?textSearchParam" , "ALTTEXT:get" ; + dynapi:solrOffset "3" ; + dynapi:solrLimit "10" ; + dynapi:solrSort "testField ASC" , "?sortFieldParam ?sortFieldDirection" ; + dynapi:solrFacet "ALLTEXT" . + + + a dynapi:parameter; + dynapi:paramName "searchText"; + dynapi:hasParameterType ; + rdfs:label "Search text for solr query 1"@en-US . + + + a dynapi:parameter; + dynapi:paramName "fildForSorting"; + dynapi:hasParameterType ; + rdfs:label "Choose document field for sorting"@en-US . + + + a dynapi:parameter; + dynapi:paramName "sordDirection"; + dynapi:hasParameterType ; + rdfs:label "Choose sort direction"@en-US . + + + a dynapi:action ; + rdfs:label "Solr test action"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . + + + a dynapi:rpc ; + dynapi:resourceName "Solr Test rpc individual" ; + dynapi:rpcName "solr_test_action"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "0.1.0" . + + + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Solr Test operational step 1"@en-US ; + dynapi:hasOperation . + diff --git a/home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals-testing.n3 b/home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals-testing.n3 index 98bb5de872..d5172a8bb5 100644 --- a/home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals-testing.n3 +++ b/home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals-testing.n3 @@ -2,60 +2,113 @@ @prefix rdfs: . @prefix owl: . @prefix vitro: . +@prefix dynapi_java: . @prefix dynapi: . @prefix xsd: . - a dynapi:action ; - rdfs:label "Test action"@en-US ; - dynapi:assignedRPC ; - dynapi:firstStep . + a dynapi:action ; + rdfs:label "Test action"@en-US ; + dynapi:assignedRPC ; + dynapi:firstStep . - a dynapi:step, dynapi:operationalStep ; - rdfs:label "Test operational step 1"@en-US ; - dynapi:hasOperation . + a dynapi:step, dynapi:operationalStep ; + rdfs:label "Test operational step 1"@en-US ; + dynapi:hasOperation . - a dynapi:operation, dynapi:sparqlQuery ; - rdfs:label "Test sparql query 1"@en-US ; - dynapi:hasQueryModel ; - dynapi:requiresParameter ; - dynapi:sparqlQueryText "SELECT ?geoLocation ?label\nWHERE\n{\n ?geoLocation \n OPTIONAL { ?geoLocation ?label } \n}\nLIMIT ?limit" . + a dynapi:operation, dynapi:sparqlQuery ; + rdfs:label "Test sparql query 1"@en-US ; + dynapi:hasQueryModel ; + dynapi:requiresParameter , + ; + dynapi:sparqlQueryText "SELECT ?geoLocation ?label\nWHERE\n{\n ?geoLocation \n OPTIONAL { ?geoLocation ?label } \n}\nLIMIT ?limit" . - a dynapi:parameter; - dynapi:paramName "limit"; - dynapi:hasParameterType ; - dynapi:hasValidator ; - rdfs:label "Test parameter 1 for query 1"@en-US . - - - a dynapi:rpc ; - dynapi:resourceName "Test rpc individual" ; - dynapi:rpcName "test_action"@en-US ; - dynapi:defaultMethod ; - dynapi:rpcAPIVersionMin "0.1.0" . + a dynapi:parameter ; + dynapi:paramName "limit" ; + dynapi:hasParameterType ; + dynapi:hasValidator , + ; + rdfs:label "Test parameter 1 for query 1"@en-US . + + + a dynapi:parameter ; + dynapi:paramName "email" ; + dynapi:hasParameterType ; + dynapi:hasValidator , + ; + rdfs:label "Test parameter 2 for query 1"@en-US . + + + a dynapi:validator , + dynapi_java:validators.Validator , + dynapi_java:validators.NumericRangeValidator ; + dynapi:validatorMinNumericValue 10 ; + dynapi:validatorMaxNumericValue 30.5 ; + rdfs:label "Test range validator for parameter 1"@en-US . + + + a dynapi:validator , + dynapi_java:validators.Validator , + dynapi_java:validators.StringLengthRangeValidator ; + dynapi:validatorMinLengthValue 5 ; + rdfs:label "Test string length range validator for parameter 2"@en-US . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc create individual" ; + dynapi:rpcName "test_action"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "0.1.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc get individual" ; + dynapi:rpcName "test_action"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "0.1.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc update individual" ; + dynapi:rpcName "test_action"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "0.1.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc patch individual" ; + dynapi:rpcName "test_action"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "0.1.0" . + + + a dynapi:rpc ; + dynapi:resourceName "Test rpc delete individual" ; + dynapi:rpcName "test_action"@en-US ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "0.1.0" . - a dynapi:customAction ; - dynapi:resourceName "Test custom action" ; - dynapi:customActionName "test_custom_action_name" ; - dynapi:forwardTo ; - dynapi:defaultMethod ; - dynapi:rpcAPIVersionMin "0.1.0" . + a dynapi:customRESTAction ; + dynapi:resourceName "Test custom action" ; + dynapi:customRESTActionName "test_custom_action_name" ; + dynapi:forwardTo ; + dynapi:defaultMethod ; + dynapi:rpcAPIVersionMin "0.1.0" . - a dynapi:resource ; - dynapi:resourceName "test_resource" ; - rdfs:label "Test resource 1"@en-US ; - dynapi:onGet ; - dynapi:onPost ; - dynapi:onDelete ; - dynapi:onPut ; - dynapi:onPatch ; - dynapi:hasCustomAction ; - dynapi:restAPIVersionMin "0.1.0" . - + a dynapi:resource ; + dynapi:resourceName "test_resource" ; + rdfs:label "Test resource 1"@en-US ; + dynapi:onPost ; + dynapi:onGet ; + dynapi:onPut ; + dynapi:onPatch ; + dynapi:onDelete ; + dynapi:hasCustomRESTAction ; + dynapi:restAPIVersionMin "0.1.0" . diff --git a/home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals.n3 b/home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals.n3 index 97e3655ee4..aeae4029c5 100644 --- a/home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals.n3 +++ b/home/src/main/resources/rdf/abox/filegraph/dynamic-api-individuals.n3 @@ -20,22 +20,65 @@ a dynapi:validator , dynapi_java:validators.Validator , - dynapi_java:validators.IsInteger; + dynapi_java:validators.IsInteger ; rdfs:label "Is integer"@en-US ; vitro:mostSpecificType dynapi:validator . + + a dynapi:validator , + dynapi_java:validators.Validator , + dynapi_java:validators.RegularExpressionValidator ; + dynapi:validatorRegularExpressionValue "^(.+)@(\\S+)$" ; + rdfs:label "Is email"@en-US ; + vitro:mostSpecificType dynapi:validator . + #------------------------------------------------------------------------------- # # Parameter types # + + a dynapi:parameterType , + dynapi_java:ParameterType ; + dynapi:typeName "boolean" ; + rdfs:label "Boolean type"@en-US ; + vitro:mostSpecificType dynapi:parameterType . + + + a dynapi:parameterType , + dynapi_java:ParameterType ; + dynapi:typeName "decimal" ; + rdfs:label "Decimal type"@en-US ; + vitro:mostSpecificType dynapi:parameterType . + a dynapi:parameterType , - dynapi_java:ParameterType; - dynapi:typeName "integer" ; + dynapi_java:ParameterType ; + dynapi:typeName "integer" ; rdfs:label "Integer type"@en-US ; vitro:mostSpecificType dynapi:parameterType . + + a dynapi:parameterType , + dynapi_java:ParameterType ; + dynapi:typeName "individual" ; + rdfs:label "Individual type"@en-US ; + vitro:mostSpecificType dynapi:parameterType . + + + a dynapi:parameterType , + dynapi_java:ParameterType ; + dynapi:typeName "list" ; + rdfs:label "List type"@en-US ; + vitro:mostSpecificType dynapi:parameterType . + + + a dynapi:parameterType , + dynapi_java:ParameterType ; + dynapi:typeName "string" ; + rdfs:label "String type"@en-US ; + vitro:mostSpecificType dynapi:parameterType . + #------------------------------------------------------------------------------- # # Models @@ -43,43 +86,43 @@ a dynapi:model ; - dynapi:modelName "TBOX_ASSERTIONS" ; + dynapi:modelName "TBOX_ASSERTIONS" ; rdfs:label "tbox assertions model"@en-US ; vitro:mostSpecificType dynapi:model . a dynapi:model ; - dynapi:modelName "ABOX_ASSERTIONS"; + dynapi:modelName "ABOX_ASSERTIONS" ; rdfs:label "abox assertions model"@en-US ; vitro:mostSpecificType dynapi:model . a dynapi:model ; - dynapi:modelName "ABOX_UNION" ; + dynapi:modelName "ABOX_UNION" ; rdfs:label "abox union model"@en-US ; vitro:mostSpecificType dynapi:model . a dynapi:model ; - dynapi:modelName "TBOX_UNION" ; + dynapi:modelName "TBOX_UNION" ; rdfs:label "tbox union model"@en-US ; vitro:mostSpecificType dynapi:model . a dynapi:model ; - dynapi:modelName "FULL_UNION" ; + dynapi:modelName "FULL_UNION" ; rdfs:label "full union model"@en-US ; vitro:mostSpecificType dynapi:model . a dynapi:model ; - dynapi:modelName "APPLICATION_METADATA" ; + dynapi:modelName "APPLICATION_METADATA" ; rdfs:label "application metadata model"@en-US ; vitro:mostSpecificType dynapi:model . a dynapi:model ; - dynapi:modelName "USER_ACCOUNTS" ; + dynapi:modelName "USER_ACCOUNTS" ; rdfs:label "user accounts model"@en-US ; vitro:mostSpecificType dynapi:model . @@ -90,42 +133,36 @@ a dynapi:httpMethod ; - dynapi:methodName "GET" ; + dynapi:methodName "GET" ; rdfs:label "HTTP GET method"@en-US ; vitro:mostSpecificType dynapi:httpMethod . a dynapi:httpMethod ; - dynapi:methodName "POST" ; + dynapi:methodName "POST" ; rdfs:label "HTTP POST method"@en-US ; vitro:mostSpecificType dynapi:httpMethod . a dynapi:httpMethod ; - dynapi:methodName "PUT" ; + dynapi:methodName "PUT" ; rdfs:label "HTTP PUT method"@en-US ; vitro:mostSpecificType dynapi:httpMethod . a dynapi:httpMethod ; - dynapi:methodName "PATCH" ; + dynapi:methodName "PATCH" ; rdfs:label "HTTP PATCH method"@en-US ; vitro:mostSpecificType dynapi:httpMethod . a dynapi:httpMethod ; - dynapi:methodName "DELETE" ; + dynapi:methodName "DELETE" ; rdfs:label "HTTP DELETE method"@en-US ; vitro:mostSpecificType dynapi:httpMethod . a dynapi:httpMethod ; - dynapi:methodName "OPTIONS" ; + dynapi:methodName "OPTIONS" ; rdfs:label "HTTP OPTIONS method"@en-US ; vitro:mostSpecificType dynapi:httpMethod . - - - - - - diff --git a/home/src/main/resources/rdf/tbox/filegraph/dynamic-api-implementation.n3 b/home/src/main/resources/rdf/tbox/filegraph/dynamic-api-implementation.n3 index 45bb4620c5..27fa6d7726 100644 --- a/home/src/main/resources/rdf/tbox/filegraph/dynamic-api-implementation.n3 +++ b/home/src/main/resources/rdf/tbox/filegraph/dynamic-api-implementation.n3 @@ -15,6 +15,8 @@ dynapi:n3Template rdfs:subClassOf dynapi_java:N3Template . dynapi:sparqlQuery rdfs:subClassOf dynapi_java:SPARQLQuery, dynapi_java:Operation . +dynapi:solrQuery rdfs:subClassOf dynapi_java:SolrQuery, dynapi_java:Operation . + dynapi:model rdfs:subClassOf dynapi_java:ModelComponent . dynapi:parameter rdfs:subClassOf dynapi_java:Parameter . @@ -27,5 +29,5 @@ dynapi:rpc rdfs:subClassOf dynapi_java:RPC . dynapi:httpMethod rdfs:subClassOf dynapi_java:HTTPMethod . -dynapi:customAction rdfs:subClassOf dynapi_java:CustomAction . +dynapi:customRESTAction rdfs:subClassOf dynapi_java:CustomRESTAction . diff --git a/home/src/main/resources/rdf/tbox/filegraph/vitro-dynamic-api.owl b/home/src/main/resources/rdf/tbox/filegraph/vitro-dynamic-api.owl index 49e7dfdaae..adaebace0f 100644 --- a/home/src/main/resources/rdf/tbox/filegraph/vitro-dynamic-api.owl +++ b/home/src/main/resources/rdf/tbox/filegraph/vitro-dynamic-api.owl @@ -90,7 +90,7 @@ - + provides parameter @@ -112,7 +112,7 @@ - + requires parameter @@ -199,12 +199,12 @@ - + - + - + has custom action @@ -214,7 +214,7 @@ - + forward to RPC @@ -270,9 +270,10 @@ - sparql query text + SPARQL query text - + + @@ -283,6 +284,62 @@ solr query text + + + + + + + solr query fields to be returned + + + + + + + + + solr query filter + + + + + + + + + + solr query limit parameter + + + + + + + + + + solr query offset parameter + + + + + + + + + solr query sort parameter + + + + + + + + + solr query facet parameter + + @@ -306,28 +363,63 @@ - + rpc name - + - + model name - + - + parameter name - + + + + + + + validator min value + + + + + + + validator max value + + + + + + + validator min length value + + + + + + + validator max length value + + + + + + + validator regular expression value + @@ -378,9 +470,9 @@ http method name - + - + custom action name @@ -399,6 +491,8 @@ + + Action @@ -436,13 +530,25 @@ Role + + + + Parameter provider interface + + + + + Parameter receiver interface + + + - SPARQLQuery + sparql Query @@ -450,6 +556,8 @@ + + SolrQuery @@ -472,13 +580,14 @@ - Operation + Operation interface + Template @@ -498,6 +607,27 @@ Validator + + + + + Numeric Range Validator + + + + + + + String Length Range Validator + + + + + + + Regular Expression Validator + + @@ -524,10 +654,10 @@ HTTP method - + - - Custom action + + Custom REST action