modules/enterprise/gui/rest-war/src/main/webapp/WEB-INF/web.xml | 4 modules/enterprise/server/container/src/main/resources/jbossas/server/default/deploy/rhq-cache-service.xml | 170 +++++++ modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/AbstractRestBean.java | 135 +++++ modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerBean.java | 80 ++- modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerLocal.java | 4 modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerBean.java | 236 ++++++++++ modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerLocal.java | 76 +++ modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerBean.java | 140 ++++- modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerLocal.java | 35 - modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationDefinitionRest.java | 52 ++ modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationRest.java | 117 ++++ modules/enterprise/server/jar/src/main/resources/rest_templates/metricSchedule.ftl | 9 12 files changed, 989 insertions(+), 69 deletions(-)
New commits: commit e23db61ad37d962ce5ba746a2864854d1ce4fc78 Author: Heiko W. Rupp hwr@redhat.com Date: Sat Nov 5 21:40:57 2011 +0100
More REST work, consisting of: Introduce a cache (at the moment for schedules) to reduce database load.
Map the cache int JNDI and have it injected by the container.
Check the caller when accessing cache entries, so that only entries are returned that the caller has access to.
Clean up some methods, add support for links to operation definitions for resources.
First cut at triggering operations.
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/WEB-INF/web.xml b/modules/enterprise/gui/rest-war/src/main/webapp/WEB-INF/web.xml index 9652232..5d39d99 100644 --- a/modules/enterprise/gui/rest-war/src/main/webapp/WEB-INF/web.xml +++ b/modules/enterprise/gui/rest-war/src/main/webapp/WEB-INF/web.xml @@ -7,7 +7,7 @@
<context-param> <param-name>resteasy.jndi.resources</param-name> - <param-value>rhq/ResourceHandlerBean/local,rhq/MetricHandlerBean/local,rhq/UserHandlerBean/local,rhq/StatusHandlerBean/local,rhq/AlertHandlerBean/local</param-value> + <param-value>rhq/ResourceHandlerBean/local,rhq/MetricHandlerBean/local,rhq/UserHandlerBean/local,rhq/StatusHandlerBean/local,rhq/AlertHandlerBean/local,rhq/OperationsHandlerBean/local</param-value> <description>List of jndi names of EJBs local interfaces that define REST stuff</description> </context-param> <context-param> @@ -64,6 +64,7 @@ <web-resource-collection> <web-resource-name>REST</web-resource-name> <url-pattern>/*</url-pattern> +<!-- <http-method>DELETE</http-method> <http-method>PUT</http-method> <http-method>HEAD</http-method> @@ -71,6 +72,7 @@ <http-method>TRACE</http-method> <http-method>GET</http-method> <http-method>POST</http-method> +--> </web-resource-collection> <auth-constraint> <role-name>all</role-name> diff --git a/modules/enterprise/server/container/src/main/resources/jbossas/server/default/deploy/rhq-cache-service.xml b/modules/enterprise/server/container/src/main/resources/jbossas/server/default/deploy/rhq-cache-service.xml new file mode 100644 index 0000000..f458a8e --- /dev/null +++ b/modules/enterprise/server/container/src/main/resources/jbossas/server/default/deploy/rhq-cache-service.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<server> + + <mbean code="org.jboss.cache.TreeCache" + name="rhq.cache:service=cache"> + <depends>jboss:service=TransactionManager</depends> + + <!-- Configure the TransactionManager --> + <attribute name="TransactionManagerLookupClass">org.jboss.cache.JBossTransactionManagerLookup</attribute> + + <!-- Isolation level : SERIALIZABLE + REPEATABLE_READ (default) + READ_COMMITTED + READ_UNCOMMITTED + NONE + --> + <attribute name="IsolationLevel">REPEATABLE_READ</attribute> + + <!-- + Node locking scheme: + OPTIMISTIC + PESSIMISTIC (default) + --> + <attribute name="NodeLockingScheme">OPTIMISTIC</attribute> + + <!-- Valid modes are LOCAL, REPL_ASYNC and REPL_SYNC --> + <attribute name="CacheMode">REPL_ASYNC</attribute> + + <!-- Just used for async repl: use a replication queue --> + <attribute name="UseReplQueue">false</attribute> + + <!-- Replication interval for replication queue (in ms) --> + <attribute name="ReplQueueInterval">100</attribute> + + <!-- Max number of elements which trigger replication --> + <attribute name="ReplQueueMaxElements">10</attribute> + + + <!-- Name of cluster. Needs to be the same for all clusters, in order + to find each other + --> + <attribute name="ClusterName">${jboss.partition.name:DefaultPartition}-RHQ-Cache</attribute> + + <!-- JGroups protocol stack properties. Can also be a URL, + e.g. file:/home/bela/default.xml + <attribute name="ClusterProperties"></attribute> + --> + <attribute name="ClusterConfig"> + + <config> + <!-- UDP: if you have a multihomed machine, + set the bind_addr attribute to the appropriate NIC IP address, e.g bind_addr="192.168.0.2" + --> + <!-- UDP: On Windows machines, because of the media sense feature + being broken with multicast (even after disabling media sense) + set the loopback attribute to true + + TODO need to parametrize and add to installer + + --> + <UDP + mcast_addr="${jgroups.udp.mcast_addr:230.1.2.3}" + mcast_port="${jboss.alertcachepartition.mcast_port:48899}" + ip_ttl="${jgroups.udp.ip_ttl:2}" + bind_addr="${jgroups.bind_addr}" + ip_mcast="true" + tos="8" + mcast_send_buf_size="150000" + mcast_recv_buf_size="80000" + ucast_send_buf_size="150000" + ucast_recv_buf_size="80000" + loopback="${rhq.server.startup.partition.udpLoopback:false}" /> + <PING timeout="2000" + num_initial_members="3" + up_thread="false" + down_thread="false" /> + <MERGE2 min_interval="10000" + max_interval="20000" /> + <FD_SOCK /> + <VERIFY_SUSPECT timeout="1500" + up_thread="false" + down_thread="false" /> + <pbcast.NAKACK gc_lag="50" + retransmit_timeout="600,1200,2400,4800" + max_xmit_size="8192" + up_thread="false" + down_thread="false" /> + <UNICAST timeout="600,1200,2400" + down_thread="false" /> + <pbcast.STABLE desired_avg_gossip="20000" + up_thread="false" + down_thread="false" /> + <FRAG frag_size="8192" down_thread="false" + up_thread="false" /> + <pbcast.GMS join_timeout="5000" join_retry_timeout="2000" + shun="true" print_local_addr="true" /> + <pbcast.STATE_TRANSFER up_thread="true" down_thread="true" /> + </config> + </attribute> + + <!-- Whether or not to fetch state on joining a cluster --> + <attribute name="FetchStateOnStartup">true</attribute> + + <!-- The max amount of time (in milliseconds) we wait until the + initial state (ie. the contents of the cache) are retrieved from + existing members in a clustered environment + + --> + <attribute name="InitialStateRetrievalTimeout">5000</attribute> + + <!-- Number of milliseconds to wait until all responses for a + synchronous call have been received. + --> + <attribute name="SyncReplTimeout">15000</attribute> + + <!-- Max number of milliseconds to wait for a lock acquisition --> + <attribute name="LockAcquisitionTimeout">10000</attribute> + + <attribute name="EvictionPolicyClass">org.jboss.cache.eviction.LRUPolicy</attribute> + <!-- Specific eviction policy configurations. This is LRU --> + <attribute name="EvictionPolicyConfig"> + <config> + <attribute name="wakeUpIntervalSeconds">5</attribute> + <!-- Cache wide default --> + <region name="/_default_"> + <attribute name="maxNodes">5000</attribute> + <attribute name="timeToLiveSeconds">1000</attribute> + </region> + <region name="/schedules"> + <attribute name="maxNodes">5000</attribute> + <attribute name="timeToLiveSeconds">1000</attribute> + </region> + <region name="/org/jboss/test/data"> + <attribute name="maxNodes">5</attribute> + <attribute name="timeToLiveSeconds">4</attribute> + </region> + <region name="/test/"> + <attribute name="maxNodes">10000</attribute> + <attribute name="timeToLiveSeconds">5</attribute> + </region> + <region name="/maxAgeTest/"> + <attribute name="maxNodes">10000</attribute> + <attribute name="timeToLiveSeconds">8</attribute> + <attribute name="maxAgeSeconds">10</attribute> + </region> + </config> + </attribute> + </mbean> + + + <!-- Map the cache into JNDI --> + <mbean + code="org.jboss.invocation.jrmp.server.JRMPProxyFactory" + name="mydomain:service=proxyFactory,type=jrmp,target=factory"> + <attribute name="InvokerName">jboss:service=invoker,type=jrmp</attribute> + <attribute name="TargetName">rhq.cache:service=cache</attribute> + <attribute name="JndiName">RhqCache</attribute> + <attribute name="InvokeTargetMethod">true</attribute> + <attribute name="ExportedInterface">org.jboss.cache.TreeCacheMBean</attribute> + <attribute name="ClientInterceptors"> + <iterceptors> + <interceptor>org.jboss.proxy.ClientMethodInterceptor</interceptor> + <interceptor>org.jboss.proxy.SecurityInterceptor</interceptor> + <interceptor>org.jboss.invocation.InvokerInterceptor</interceptor> + </iterceptors> + </attribute> + <depends>jboss:service=invoker,type=jrmp</depends> + <depends>rhq.cache:service=cache</depends> + </mbean> +</server> diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/AbstractRestBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/AbstractRestBean.java index d1d3c89..124e12f 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/AbstractRestBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/AbstractRestBean.java @@ -25,7 +25,12 @@ package org.rhq.enterprise.server.rest; import java.io.IOException; import java.io.StringWriter; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; + +import javax.annotation.Resource; +import javax.ejb.Stateless;
import freemarker.cache.ClassTemplateLoader; import freemarker.cache.MultiTemplateLoader; @@ -37,12 +42,20 @@ import freemarker.template.TemplateException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory;
+import org.jboss.cache.CacheException; +import org.jboss.cache.Fqn; +import org.jboss.cache.Node; +import org.jboss.cache.TreeCacheMBean; + import org.rhq.core.domain.auth.Subject;
/** * Abstract base class for EJB classes that implement REST methods. + * For the cache and its evicion policies see rhq-cache-service.xml * @author Heiko W. Rupp */ +@Stateless +@Resource(name="cache",type= TreeCacheMBean.class,mappedName = "RhqCache") public class AbstractRestBean {
Log log = LogFactory.getLog(getClass().getName()); @@ -50,6 +63,10 @@ public class AbstractRestBean { /** Subject of the caller that gets injected via {@link SetCallerInterceptor} */ Subject caller;
+ /** The cache to use */ + @Resource(name="cache") + TreeCacheMBean treeCache; + /** * Renders the passed object with the help of a freemarker template into a string. Freemarket templates * are searched in the class path in a directory called "/rest_templates". In the usual Maven tree structure, @@ -92,4 +109,122 @@ public class AbstractRestBean { } return null; } + + /** + * Retrieve an object from the cache. This is identified by its class and id + * @param id Id of the object to load. + * @param clazz Wanted return type + * @return Object if found and the caller has access to it. + * @see #getFqn(int, Class) + */ + protected <T>T getFromCache(int id,Class<T> clazz) { + Fqn fqn = getFqn(id, clazz); + return getFromCache(fqn,clazz); + } + + + /** + * Retrieve an object from the cache if present or null otherwise. + * We need to be careful here as we must not return objects the current + * caller has no access to. We do this by checking the "readers" attribute + * of the selected node to see if the caller has put the object there + * @param fqn FullyQualified name (=path in cache) ot the object to retrieve + * @param clazz Return type + * @return The desired object if found and valid for the current caller. Null otherwise. + * @see #putToCache(org.jboss.cache.Fqn, Object) + */ + @SuppressWarnings("unchecked") + protected <T>T getFromCache(Fqn fqn,Class<T> clazz) { + Object o=null; + if (treeCache.exists(fqn)) { + log.info("Hit for " + fqn.toString()); + try{ + Node n = treeCache.get(fqn); + Set<Integer> readers= (Set<Integer>) n.get("readers"); + if (readers.contains(caller.getId())) { + o = n.get("item"); + } + else { + log.info("No object for caller " +caller.toString() + " found"); + } + } catch (CacheException e) { + e.printStackTrace(); // TODO: Customise this generated block + log.info("Miss for " + fqn.toString()); + } + } + return (T)o; + } + + /** + * Put an object into the cache. We need to record the caller so that we can later + * check if the caller can access that object or not. + * @param fqn Fully qualified name (=path to object) + * @param o Object to put + * @return true if put was successful + * @see #getFromCache(org.jboss.cache.Fqn, Class) + */ + @SuppressWarnings("unchecked") + protected <T>boolean putToCache(Fqn fqn,T o) { + boolean success = false; + try { + Set<Integer> readers; + if (treeCache.exists(fqn)) { + Node n = treeCache.get(fqn); + readers = (Set<Integer>) n.get("readers"); + } else { + readers = new HashSet<Integer>(); + } + readers.add(caller.getId()); + treeCache.put(fqn,"readers",readers); + treeCache.put(fqn,"item",o); + success = true; + log.info("Put " + fqn); + } catch (CacheException e) { + e.printStackTrace(); // TODO: Customise this generated block + } + return success; + } + + /** + * Put an object into the cache identified by its type and id + * @param id Id of the object to put + * @param clazz Type to put in + * @param o Object to put + * @return true if put was successful + * @see #putToCache(org.jboss.cache.Fqn, Object) + */ + protected <T>boolean putToCache(int id,Class<T> clazz,T o) { + Fqn fqn = getFqn(id, clazz); + return putToCache(fqn,o); + } + + /** + * Construct a Fqn object from the passed data + * @param id Id of the target object + * @param clazz Type of object for that node + * @return Fqn object + */ + protected <T> Fqn getFqn(int id, Class<T> clazz) { + return new Fqn(new Object[]{clazz.getName(), String.valueOf(id)}); + } + + /** + * Remove an item from the cache + * @param operationId Id of the item + * @param clazz Type of object for that node + * @return true if object is no longer in cache + */ + protected <T> boolean removeFromCache(int operationId, Class<T> clazz) { + Fqn fqn = getFqn(operationId,clazz); + if (treeCache.exists(fqn)) { + try { + treeCache.remove(fqn); + log.info("Cancel " + fqn); + return true; + } catch (CacheException e) { + return false; + } + } + return true; + } } diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerBean.java index 9908b7d..f21a90c 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerBean.java @@ -36,6 +36,8 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo;
+import org.jboss.cache.Fqn; + import org.rhq.core.domain.measurement.DataType; import org.rhq.core.domain.measurement.MeasurementDefinition; import org.rhq.core.domain.measurement.MeasurementSchedule; @@ -80,7 +82,17 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle }
- MeasurementSchedule schedule = scheduleManager.getScheduleById(caller,scheduleId); + MeasurementSchedule schedule; + schedule = getFromCache(scheduleId,MeasurementSchedule.class); + if (schedule==null) { + schedule = scheduleManager.getScheduleById(caller,scheduleId); + if (schedule==null) { + throw new StuffNotFoundException("Schedule with id " + scheduleId); + } + else + putToCache(scheduleId,MeasurementSchedule.class,schedule); + } + if (schedule.getDefinition().getDataType()!= DataType.MEASUREMENT) throw new IllegalArgumentException("Schedule [" + scheduleId + "] is not a (numerical) metric");
@@ -140,14 +152,43 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle * @param scheduleId ID of the schedule * @param request the REST request - injected by the REST framework * @param headers the REST request http headers - injected by the REST framework - * @param uriInfo + * @param uriInfo info about the called uri to build links * @return Schedule with respective headers */ public Response getSchedule(int scheduleId, Request request, HttpHeaders headers, UriInfo uriInfo) {
- MeasurementSchedule schedule = scheduleManager.getScheduleById(caller, scheduleId); - if (schedule==null) - throw new StuffNotFoundException("Schedule with id " + scheduleId); + MeasurementSchedule schedule=null; + Response.ResponseBuilder builder=null; + + // Create a cache control + CacheControl cc = new CacheControl(); + cc.setMaxAge(300); // Schedules are valid for 5 mins + cc.setPrivate(false); // Proxies may cache this + + + Fqn fqn = getFqn(scheduleId,MeasurementSchedule.class); + schedule = getFromCache(fqn,MeasurementSchedule.class); + if (schedule!=null) { + // If it is on cache, quickly return if match + long tim = schedule.getMtime() != null ? schedule.getMtime() : 0; + EntityTag eTag = new EntityTag(Long.toOctalString(schedule.hashCode()+tim)); // factor in mtime in etag + builder = request.evaluatePreconditions(new Date(tim),eTag); + + if (builder!=null) { + builder.cacheControl(cc); + return builder.build(); + } + + + } + + if (schedule==null) { + schedule = scheduleManager.getScheduleById(caller, scheduleId); + if (schedule==null) + throw new StuffNotFoundException("Schedule with id " + scheduleId); + else + putToCache(fqn,schedule); + }
MeasurementDefinition definition = schedule.getDefinition(); MetricSchedule metricSchedule = new MetricSchedule(schedule.getId(), definition.getName(), @@ -157,14 +198,14 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle if (schedule.getMtime()!=null) metricSchedule.setMtime(schedule.getMtime());
- // What media type does the user request? - MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
- // Check for conditional get + // Check for conditional get again // Interestingly computing the hashCode of the original schedule is slower, as it also // pulls in data from the definition and the resource - EntityTag eTag = new EntityTag(Integer.toOctalString(metricSchedule.hashCode())); - Response.ResponseBuilder builder = request.evaluatePreconditions(new Date(metricSchedule.getMtime()),eTag); + long tim = schedule.getMtime() != null ? schedule.getMtime() : 0; + EntityTag eTag = new EntityTag(Long.toOctalString(schedule.hashCode()+tim)); + builder = request.evaluatePreconditions(new Date(tim),eTag); // factor in mtime in etag +
if (builder==null) { // preconditions not met, we need to send the resource @@ -173,16 +214,24 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); uriBuilder.path("metric/data/" + scheduleId); URI uri = uriBuilder.build(); - Link link = new Link("metrics",uri.toString()); metricSchedule.addLink(link);
+ // create link to the resource + uriBuilder = uriInfo.getBaseUriBuilder(); + uriBuilder.path("resource/" + schedule.getResource().getId()); + uri = uriBuilder.build(); + link = new Link("resource",uri.toString()); + metricSchedule.addLink(link); + // Link for updates uriBuilder = uriInfo.getAbsolutePathBuilder(); uri = uriBuilder.build(); Link updateLink = new Link("edit",uri.toString()); metricSchedule.addLink(updateLink);
+ // What media type does the user request? + MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) { builder = Response.ok(renderTemplate("metricSchedule", metricSchedule), mediaType); @@ -192,10 +241,6 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle } }
- // Create a cache control - CacheControl cc = new CacheControl(); - cc.setMaxAge(300); // Schedules are valid for 5 mins - cc.setPrivate(false); // Proxies may cache this
builder.cacheControl(cc); builder.tag(eTag); @@ -208,6 +253,9 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
List<MeasurementSchedule> schedules = scheduleManager.findSchedulesForResourceAndType(caller, resourceId, DataType.MEASUREMENT, null,false); + for (MeasurementSchedule sched: schedules) { + putToCache(sched.getId(),MeasurementSchedule.class,sched); + } List<MetricAggregate> ret = new ArrayList<MetricAggregate>(schedules.size());
long now = System.currentTimeMillis(); @@ -239,6 +287,8 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle scheduleManager.updateSchedule(caller, schedule);
schedule = scheduleManager.getScheduleById(caller,scheduleId); + Fqn fqn = getFqn(scheduleId,MeasurementSchedule.class); + putToCache(fqn,schedule); MeasurementDefinition def = schedule.getDefinition();
MetricSchedule ret = new MetricSchedule(scheduleId,def.getName(),def.getDisplayName(), diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerLocal.java index 76c3f9d..368ef36 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerLocal.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerLocal.java @@ -73,8 +73,8 @@ public interface MetricHandlerLocal {
@PUT @Path("/schedule/{id}") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) + @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) MetricSchedule updateSchedule(@PathParam("id") int scheduleId, MetricSchedule in);
} diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerBean.java new file mode 100644 index 0000000..2a4f7df --- /dev/null +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerBean.java @@ -0,0 +1,236 @@ +package org.rhq.enterprise.server.rest; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.interceptor.Interceptors; +import javax.ws.rs.DELETE; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.CacheControl; +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +import org.rhq.core.domain.configuration.definition.ConfigurationDefinition; +import org.rhq.core.domain.configuration.definition.PropertyDefinition; +import org.rhq.core.domain.operation.OperationDefinition; +import org.rhq.core.domain.resource.Resource; +import org.rhq.core.domain.resource.ResourceType; +import org.rhq.enterprise.server.operation.OperationManagerLocal; +import org.rhq.enterprise.server.resource.ResourceManagerLocal; +import org.rhq.enterprise.server.rest.domain.Link; +import org.rhq.enterprise.server.rest.domain.OperationDefinitionRest; +import org.rhq.enterprise.server.rest.domain.OperationRest; + +/** + * Deal with operations + * @author Heiko W. Rupp + */ +@Stateless +@Interceptors(SetCallerInterceptor.class) +public class OperationsHandlerBean extends AbstractRestBean implements OperationsHandlerLocal { + + @EJB + OperationManagerLocal opsManager; + + @EJB + ResourceManagerLocal resourceManager; + + @Override + public Response getOperationDefinition(int definitionId, + Integer resourceId, + UriInfo uriInfo, + Request request, HttpHeaders httpHeaders) { + + + + OperationDefinition def; + def = getFromCache(definitionId, OperationDefinition.class); + if (def==null) { + def = opsManager.getOperationDefinition(caller,definitionId); + if (def==null) + throw new StuffNotFoundException("OperationDefinition with id " + definitionId); + else + putToCache(definitionId,OperationDefinition.class,def); + } + + EntityTag eTag = new EntityTag(Integer.toHexString(def.hashCode())); + Response.ResponseBuilder builder = request.evaluatePreconditions(eTag); + if (builder==null) { + + OperationDefinitionRest odr = new OperationDefinitionRest(); + odr.setId(def.getId()); + odr.setName(def.getName()); + + builder=Response.ok(odr); + + // Add some links + if (resourceId!=null) { + UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); + uriBuilder.path("/operation/definition/{id}"); + uriBuilder.queryParam("resourceId",resourceId); + Link createLink = new Link("create",uriBuilder.build(definitionId).toString()); + odr.addLink(createLink); + } + + } + + CacheControl cc = new CacheControl(); + cc.setMaxAge(1200); // Definitions are very stable, so caching for 20mins is ok + cc.setPrivate(false); + builder.tag(eTag); + builder.cacheControl(cc); + + return builder.build(); + + } + + @Override + // TODO find out why a return type of Response lets this fail for XML (even with @Wrapped annotation + public List<OperationDefinitionRest> getOperationDefinitions(Integer resourceId, UriInfo uriInfo, Request request) { + + if (resourceId == null) + throw new ParameterMissingException("resourceId"); + + Resource res =resourceManager.getResource(caller,resourceId); + if(res==null) + throw new StuffNotFoundException("resource with id " + resourceId); + + ResourceType resourceType = res.getResourceType(); + +/* + EntityTag eTag = new EntityTag(Integer.toHexString(resourceType.hashCode())); + Response.ResponseBuilder builder = request.evaluatePreconditions(eTag); + if (builder==null) { +*/ + + Set<OperationDefinition> opDefList = resourceType.getOperationDefinitions(); + List<OperationDefinitionRest> resultList = new ArrayList<OperationDefinitionRest>(opDefList.size()); + + for (OperationDefinition def : opDefList) { + OperationDefinitionRest odr = new OperationDefinitionRest(); + odr.setId(def.getId()); + odr.setName(def.getName()); + UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); + uriBuilder.path("/operation/definition/{id}"); + uriBuilder.queryParam("resourceId",resourceId); + Link createLink = new Link("create",uriBuilder.build(def.getId()).toString()); + odr.addLink(createLink); + + resultList.add(odr); + } + return resultList; + +/* + + builder = Response.ok(resultList); + } + + CacheControl cc = new CacheControl(); + cc.setMaxAge(1200); // Definitions are very stable, so caching for 20mins is ok + cc.setPrivate(false); + builder.tag(eTag); + builder.cacheControl(cc); + + return builder.build(); +*/ + + } + + @Override + public Response createOperation(@PathParam("id") int definitionId, + @QueryParam("resourceId") Integer resourceId, + UriInfo uriInfo) { + + if (resourceId == null) + throw new ParameterMissingException("resourceId"); + + Resource res =resourceManager.getResource(caller,resourceId); + if(res==null) + throw new StuffNotFoundException("resource with id " + resourceId); + + + OperationDefinition opDef = opsManager.getOperationDefinition(caller,definitionId); + if (opDef==null) { + throw new StuffNotFoundException("Operation definition with id " + definitionId); + } + OperationRest operationRest = new OperationRest(resourceId,definitionId); + operationRest.setId((int)System.currentTimeMillis()); // TODO better id (?)(we need one for pUT later on) + operationRest.setState("creating"); + operationRest.setName(opDef.getName()); + ConfigurationDefinition paramDefinition = opDef.getParametersConfigurationDefinition(); + if (paramDefinition != null) { + for (PropertyDefinition propDefs : paramDefinition.getNonGroupedProperties()) { // TODO extend to all properties ? + operationRest.getParams().put(propDefs.getName(),"TODO"); // TODO type and value of the value + } + } + + UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); + uriBuilder.path("/operation/{id}"); + URI uri = uriBuilder.build(operationRest.getId()); + Link editLink = new Link("edit",uri.toString()); + operationRest.addLink(editLink); + Response.ResponseBuilder builder = Response.ok(operationRest); + + putToCache(operationRest.getId(),OperationRest.class,operationRest); + + return builder.build(); + + } + + @Override + public Response getOperation(@PathParam("id") int operationId) { + OperationRest op = getFromCache(operationId,OperationRest.class); + if (op==null) + throw new StuffNotFoundException("Operation with id " + operationId); + + return Response.ok(op).build(); + } + + @Override + public Response updateOperation(@PathParam("id") int operationId, OperationRest operation, UriInfo uriInfo) { + + if (operation.getState().equals("creating") && operation.getDefinitionId()>0 && !operation.getName().isEmpty()) { + // TODO check all the required parameters for persence before allowing to submit + Link link = new Link("submit","TODO"); + operation.addLink(link); + + } + UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); + uriBuilder.path("/operation/{id}"); + URI uri = uriBuilder.build(operation.getId()); + Link editLink = new Link("edit",uri.toString()); + operation.addLink(editLink); + + // Update item in cache + putToCache(operation.getId(),OperationRest.class,operation); + + Response.ResponseBuilder builder = Response.ok(operation); + return builder.build(); + } + + @Override + @DELETE + @Path("{id}") + @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML}) + public Response cancelOperation(@PathParam("id") int operationId) { + + log.info("Cancel called"); + + removeFromCache(operationId,OperationRest.class); + + return null; // TODO: Customise this generated block + } + +} diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerLocal.java new file mode 100644 index 0000000..46a7f86 --- /dev/null +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerLocal.java @@ -0,0 +1,76 @@ +package org.rhq.enterprise.server.rest; + +import java.util.List; + +import javax.ejb.Local; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.jboss.resteasy.annotations.providers.jaxb.Wrapped; + +import org.rhq.enterprise.server.rest.domain.OperationDefinitionRest; +import org.rhq.enterprise.server.rest.domain.OperationRest; + +/** + * Service that deals with operations + * @author Heiko W. Rupp + */ +@Local +@Path("/operation") +public interface OperationsHandlerLocal { + + + @GET + @Path("definition/{id}") + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) + public Response getOperationDefinition(@PathParam("id") int definitionId, + @QueryParam("resourceId") Integer resourceId, + @Context UriInfo uriInfo, + @Context Request request, + @Context HttpHeaders httpHeaders); + + @GET + @Path("definitions") + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) + public List<OperationDefinitionRest> getOperationDefinitions(@QueryParam("resourceId") Integer resourceId, + @Context UriInfo uriInfo, + @Context Request request + ); + + @POST + @Path("definition/{id}") + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) + public Response createOperation(@PathParam("id") int definitionId, + @QueryParam("resourceId") Integer resourceId, + @Context UriInfo uriInfo); + + @GET + @Path("{id}") + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) + public Response getOperation(@PathParam("id") int operationId); + + @PUT + @Path("{id}") + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) + @Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) + public Response updateOperation(@PathParam("id") int operationId, OperationRest operation, @Context UriInfo uriInfo); + + @DELETE + @Path("{id}") + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) + public Response cancelOperation(@PathParam("id") int operationId); + +} diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerBean.java index 368efc0..e656c24 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerBean.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerBean.java @@ -24,12 +24,20 @@ package org.rhq.enterprise.server.rest;
import java.net.URI; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Set;
import javax.ejb.EJB; import javax.ejb.Stateless; import javax.interceptor.Interceptors; +import javax.ws.rs.core.CacheControl; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo;
@@ -71,44 +79,88 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan AlertManagerLocal alertManager;
@Override - public ResourceWithType getResource(int id) { + public Response getResource(int id, @Context Request request, @Context HttpHeaders headers, + @Context UriInfo uriInfo) {
- Resource res = resMgr.getResource(caller, id); + // Create a cache control + CacheControl cc = new CacheControl(); + cc.setMaxAge(300); // Resources are valid for 5 mins + cc.setPrivate(false); // Proxies may cache this
- ResourceWithType rwt = fillRWT(res); + Resource res; + res = getFromCache(id,Resource.class); + if (res==null) { + res = resMgr.getResource(caller, id); + if (res!=null) + putToCache(id, Resource.class,res); + else + throw new StuffNotFoundException("Resource with id " + id); + }
- return rwt; - } + Response.ResponseBuilder builder=null;
- @Override - public String getResourceHtml(int id) { - ResourceWithType rwt = getResource(id); - return renderTemplate("resourceWithType.ftl", rwt); + long mtime = res.getMtime(); + EntityTag eTag = new EntityTag(Long.toOctalString(res.hashCode()+ mtime)); // factor in mtime in etag + builder = request.evaluatePreconditions(new Date(mtime),eTag); + + if (builder!=null) { + builder.cacheControl(cc); + return builder.build(); + } + + + ResourceWithType rwt = fillRWT(res, uriInfo); + + // What media type does the user request? + MediaType mediaType = headers.getAcceptableMediaTypes().get(0); + + if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) { + builder = Response.ok(renderTemplate("resourceWithType", rwt), mediaType); + } + else { + builder = Response.ok(rwt); + } + + return builder.build(); }
+ @Override - public List<ResourceWithType> getPlatforms() { + public Response getPlatforms(@Context Request request, @Context HttpHeaders headers, + @Context UriInfo uriInfo) {
PageControl pc = new PageControl(); List<Resource> ret = resMgr.findResourcesByCategory(caller, ResourceCategory.PLATFORM, InventoryStatus.COMMITTED, pc) ; List<ResourceWithType> rwtList = new ArrayList<ResourceWithType>(ret.size()); for (Resource r: ret) { - ResourceWithType rwt = fillRWT(r); + putToCache(r.getId(),Resource.class,r); + ResourceWithType rwt = fillRWT(r, uriInfo); rwtList.add(rwt); } - return rwtList; - } + // What media type does the user request? + MediaType mediaType = headers.getAcceptableMediaTypes().get(0); + Response.ResponseBuilder builder;
- @Override - public String getPlatformsHtml() { - List<ResourceWithType> list = getPlatforms(); - return renderTemplate("listResourceWithType", list); + if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) { + builder = Response.ok(renderTemplate("listResourceWithType", rwtList), mediaType); + } + else { + builder = Response.ok(rwtList); + } + + return builder.build(); }
@Override public ResourceWithChildren getHierarchy(int baseResourceId) { // TODO optimize to do less recursion - Resource start = resMgr.getResource(caller,baseResourceId); + Resource start; + start = getFromCache(baseResourceId,Resource.class); + if (start==null) { + start = resMgr.getResource(caller,baseResourceId); + if (start!=null) + putToCache(start.getId(),Resource.class,start); + } ResourceWithChildren rwc = getHierarchy(start); /*new ResourceWithChildren(""+start.getId(),start.getName());
@@ -135,6 +187,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan for (Resource res : ret) { ResourceWithChildren child = getHierarchy(res); resList.add(child); + putToCache(res.getId(),Resource.class,res); } if (!resList.isEmpty()) rwc.setChildren(resList); @@ -151,7 +204,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan return availabilityRest; }
- private ResourceWithType fillRWT(Resource res) { + private ResourceWithType fillRWT(Resource res, UriInfo uriInfo) { ResourceType resourceType = res.getResourceType(); ResourceWithType rwt = new ResourceWithType(res.getName(),res.getId()); rwt.setTypeName(resourceType.getName()); @@ -161,6 +214,13 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan if (parent!=null) { rwt.setParentId(parent.getId()); } + UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); + uriBuilder.path("/operation/definitions"); + uriBuilder.queryParam("resourceId",res.getId()); + URI uri = uriBuilder.build(); + Link link = new Link("operationDefinitions",uri.toString()); + rwt.addLink(link); + return rwt; }
@@ -171,6 +231,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan Set<MeasurementSchedule> schedules = res.getSchedules(); List<MetricSchedule> ret = new ArrayList<MetricSchedule>(schedules.size()); for (MeasurementSchedule schedule : schedules) { + putToCache(schedule.getId(),MeasurementSchedule.class,schedule); MeasurementDefinition definition = schedule.getDefinition(); MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(), schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(), @@ -180,6 +241,13 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan URI uri = uriBuilder.build(schedule.getId()); Link metricLink = new Link("metric",uri.toString()); ms.addLink(metricLink); + // create link to the resource + uriBuilder = uriInfo.getBaseUriBuilder(); + uriBuilder.path("resource/" + schedule.getResource().getId()); + uri = uriBuilder.build(); + Link link = new Link("resource",uri.toString()); + ms.addLink(link); + ret.add(ms); }
@@ -192,24 +260,42 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan }
@Override - public List<ResourceWithType> getChildren(int id) { + public Response getChildren(int id, @Context Request request, @Context HttpHeaders headers, + @Context UriInfo uriInfo) { PageControl pc = new PageControl(); - Resource parent = resMgr.getResource(caller,id); + Resource parent; + parent = getFromCache(id,Resource.class); + if (parent==null) { + parent = resMgr.getResource(caller,id); + if (parent==null) + throw new StuffNotFoundException("Resource with id " + id); + else + putToCache(id,Resource.class,parent); + } List<Resource> ret = resMgr.findResourceByParentAndInventoryStatus(caller,parent,InventoryStatus.COMMITTED,pc); List<ResourceWithType> rwtList = new ArrayList<ResourceWithType>(ret.size()); for (Resource r: ret) { - ResourceWithType rwt = fillRWT(r); + putToCache(r.getId(),Resource.class,r); + ResourceWithType rwt = fillRWT(r, uriInfo); rwtList.add(rwt); }
- return rwtList; - } + // What media type does the user request? + MediaType mediaType = headers.getAcceptableMediaTypes().get(0); + Response.ResponseBuilder builder; + + if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) { + builder = Response.ok(renderTemplate("listResourceWithType", rwtList), mediaType); + } + else { + builder = Response.ok(rwtList); + } + + return builder.build();
- public String getChildrenHtml(int id) { - List<ResourceWithType> list = getChildren(id); - return renderTemplate("listResourceWithType", list); }
+ @Override public List<Link> getAlertsForResource(int resourceId) { AlertCriteria criteria = new AlertCriteria(); diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerLocal.java index c2e8698..d1cb0f2 100644 --- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerLocal.java +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerLocal.java @@ -26,6 +26,10 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Request; +import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.links.AddLinks; @@ -53,23 +57,15 @@ public interface ResourceHandlerLocal { }) @GET @Path("/{id}") - @Produces({"application/json","application/xml"}) - ResourceWithType getResource(@PathParam("id") int id); - - @GET - @Path("/{id}") - @Produces("text/html") - String getResourceHtml(@PathParam("id") int id); + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML}) + Response getResource(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers, + @Context UriInfo uriInfo);
@GET @Path("/platforms") - @Produces({"application/json","application/xml"}) - List<ResourceWithType> getPlatforms(); - - @GET - @Path("/platforms") - @Produces("text/html") - String getPlatformsHtml(); + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML}) + Response getPlatforms(@Context Request request, @Context HttpHeaders headers, + @Context UriInfo uriInfo);
@GET @@ -93,20 +89,17 @@ public interface ResourceHandlerLocal { @Path("/{id}/schedules") @Produces("text/html") @LinkResource(rel="schedules",value = MetricSchedule.class) + // TODO move to ScheduleManager, link from resource and pass the resource as query param String getSchedulesHtml(@PathParam("id") int resourceId, @Context UriInfo uriInfo);
@GET @Path("/{id}/children") - @Produces({"application/json","application/xml"}) + @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML}) @LinkResource(rel="children", value = ResourceWithType.class) - List<ResourceWithType> getChildren(@PathParam("id") int id); + Response getChildren(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers, + @Context UriInfo uriInfo);
- @GET - @Path("/{id}/children") - @Produces("text/html") - @LinkResource(rel="children", value = ResourceWithType.class) - String getChildrenHtml(@PathParam("id") int id);
@AddLinks @GET diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationDefinitionRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationDefinitionRest.java new file mode 100644 index 0000000..70f1a97 --- /dev/null +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationDefinitionRest.java @@ -0,0 +1,52 @@ +package org.rhq.enterprise.server.rest.domain; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * @author Heiko W. Rupp + */ +@XmlRootElement +public class OperationDefinitionRest { + + public OperationDefinitionRest() { + } + + String name; + int id; + List<Link> links = new ArrayList<Link>(); + // TODO add parameters + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + @XmlElementRef + public List<Link> getLinks() { + return links; + } + + public void setLinks(List<Link> links) { + this.links = links; + } + + public void addLink(Link link) { + links.add(link); + } + +} diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationRest.java new file mode 100644 index 0000000..50332ae --- /dev/null +++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationRest.java @@ -0,0 +1,117 @@ +package org.rhq.enterprise.server.rest.domain; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * @author Heiko W. Rupp + */ +@XmlRootElement +public class OperationRest { + + int id; + String name; + String state; // TODO Convert to enum + int resourceId; + int definitionId; + Map<String,Object> params = new HashMap<String, Object>(); + List<Link> links = new ArrayList<Link>(); + + public OperationRest() { + } + + public OperationRest(int resourceId, int definitionId) { + this.resourceId = resourceId; + this.definitionId = definitionId; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public int getResourceId() { + return resourceId; + } + + public void setResourceId(int resourceId) { + this.resourceId = resourceId; + } + + public int getDefinitionId() { + return definitionId; + } + + public void setDefinitionId(int definitionId) { + this.definitionId = definitionId; + } + + public Map<String, Object> getParams() { + return params; + } + + public void setParams(Map<String, Object> params) { + this.params = params; + } + + @XmlElementRef + public List<Link> getLinks() { + return links; + } + + public void setLinks(List<Link> links) { + this.links = links; + } + + public void addLink(Link link) { + links.add(link); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + OperationRest that = (OperationRest) o; + + if (definitionId != that.definitionId) return false; + if (id != that.id) return false; + if (resourceId != that.resourceId) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = (int) (id ^ (id >>> 32)); + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + resourceId; + result = 31 * result + definitionId; + return result; + } +} diff --git a/modules/enterprise/server/jar/src/main/resources/rest_templates/metricSchedule.ftl b/modules/enterprise/server/jar/src/main/resources/rest_templates/metricSchedule.ftl index 26360e5..0a0f187 100644 --- a/modules/enterprise/server/jar/src/main/resources/rest_templates/metricSchedule.ftl +++ b/modules/enterprise/server/jar/src/main/resources/rest_templates/metricSchedule.ftl @@ -46,7 +46,10 @@ <td>Units</td><td>${var.unit}</td> </tr> </table> - <a href="/rest/1/metric/data/${var.scheduleId}.html">Metric data</a> - <br/> - + Links<br/> + <ul> + <#list var.links as link> + <li><a href="${link.href}.html">${link.rel}</a> </li> + </#list> + </ul> </html> \ No newline at end of file