[rhq] Changes to 'lkrejci/dissalow-alert-scripts-from-accessing-local-slsbs'
by lkrejci
New branch 'lkrejci/dissalow-alert-scripts-from-accessing-local-slsbs' available with the following commits:
commit 84f032252328434e01ac68d40bec34d102533d81
Author: Lukas Krejci <lkrejci(a)redhat.com>
Date: Wed Dec 7 13:54:34 2011 +0100
Use a custom java permission and an EJB interceptor to disallow server-side CLI scripts from accessing the EJBs directly.
12 years, 5 months
[rhq] 28 commits - modules/enterprise modules/integration-tests modules/jopr pom.xml
by Heiko W. Rupp
modules/enterprise/gui/rest-war/src/main/webapp/WEB-INF/web.xml | 6
modules/enterprise/gui/rest-war/src/main/webapp/bars_simple.html | 206
modules/enterprise/gui/rest-war/src/main/webapp/index.html | 1
modules/enterprise/gui/rest-war/src/main/webapp/js/jquery.js | 9266 ++++++++++
modules/enterprise/gui/rest-war/src/main/webapp/js/resource_tree.js | 3
modules/enterprise/gui/rest-war/src/main/webapp/js/rhq.js | 120
modules/enterprise/gui/rest-war/src/main/webapp/js/whisker.js | 92
modules/enterprise/gui/rest-war/src/main/webapp/stacked1.html | 74
modules/enterprise/gui/rest-war/src/main/webapp/stacked2.html | 182
modules/enterprise/gui/rest-war/src/main/webapp/whisker.html | 92
modules/enterprise/gui/rest-war/src/main/webapp/whisker2.html | 61
modules/enterprise/server/container/pom.xml | 19
modules/enterprise/server/container/src/main/resources/jbossas/server/default/deploy/rhq-cache-service.xml | 170
modules/enterprise/server/container/src/main/scripts/rhq-container.build.xml | 21
modules/enterprise/server/ear/pom.xml | 69
modules/enterprise/server/jar/pom.xml | 32
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/AbstractRestBean.java | 285
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java | 38
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java | 286
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java | 78
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerBean.java | 265
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerLocal.java | 46
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerBean.java | 315
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/OperationsHandlerLocal.java | 94
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ParameterMissingException.java | 12
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerBean.java | 207
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerLocal.java | 60
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java | 68
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerLocal.java | 13
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerBean.java | 12
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerLocal.java | 4
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertDefinitionRest.java | 3
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertRest.java | 4
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AvailabilityRest.java | 3
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java | 82
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Link.java | 50
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java | 4
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java | 58
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/OperationHistoryRest.java | 72
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationRest.java | 117
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java | 28
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Status.java | 68
modules/enterprise/server/jar/src/main/resources/rest_templates/group.ftl | 49
modules/enterprise/server/jar/src/main/resources/rest_templates/listGroup.ftl | 32
modules/enterprise/server/jar/src/main/resources/rest_templates/metricData.ftl | 4
modules/enterprise/server/jar/src/main/resources/rest_templates/metricSchedule.ftl | 9
modules/enterprise/server/jar/src/main/resources/rest_templates/resourceWithType.ftl | 2
modules/enterprise/server/jar/src/main/resources/rest_templates/status.ftl | 21
modules/integration-tests/rest-api/README.md | 13
modules/integration-tests/rest-api/pom.xml | 56
modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/GetStatusTest.java | 78
modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/ResourcesTest.java | 104
modules/jopr/modules-plugins-pom.xml | 148
modules/jopr/modules-pom.xml | 248
pom.xml | 3
56 files changed, 12517 insertions(+), 988 deletions(-)
New commits:
commit 95a713e15f42a5be3d800b800012482b8ee555c2
Merge: f74b119 bf5446c
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Dec 7 12:04:38 2011 +0100
Merge remote-tracking branch 'origin/master' into feature/rest
commit f74b119c57c4fe7721127b2faa9a5b0b046a8811
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Dec 7 12:04:02 2011 +0100
Initial set of integration tests for the REST api.
diff --git a/modules/integration-tests/rest-api/README.md b/modules/integration-tests/rest-api/README.md
new file mode 100644
index 0000000..b5551be
--- /dev/null
+++ b/modules/integration-tests/rest-api/README.md
@@ -0,0 +1,13 @@
+# REST-API Integration tests
+
+These require a running server with at least one platform as resource id 10001 in inventory.
+Also required are two accounts
+
+* rhqadmin with password rhqadmin in the superuser role
+* user with password name23 as a restricted user
+
+The server to connect to can be given on the command line via `-Drest.server=<server>` as in
+
+ `mvn -Pdev -Drest.server=pintsize test`
+
+If no server is given, localhost is assumed.
\ No newline at end of file
diff --git a/modules/integration-tests/rest-api/pom.xml b/modules/integration-tests/rest-api/pom.xml
new file mode 100644
index 0000000..4a2c4a6
--- /dev/null
+++ b/modules/integration-tests/rest-api/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.rhq</groupId>
+ <artifactId>rhq-integration-tests</artifactId>
+ <version>4.3.0-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+
+ <groupId>org.rhq</groupId>
+ <artifactId>rest-api-tests</artifactId>
+
+ <name>RHQ REST API tests</name>
+
+ <properties>
+ <surefire-plugin.version>2.10</surefire-plugin.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire-plugin.version}</version>
+ <dependencies>
+ <dependency>
+ <!-- explicitly choose the junit4.7+ provider -->
+ <groupId>org.apache.maven.surefire</groupId>
+ <artifactId>surefire-junit47</artifactId>
+ <version>${surefire-plugin.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.restfuse</groupId>
+ <artifactId>com.eclipsesource.restfuse</artifactId>
+ <version>1.0.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.8.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/GetStatusTest.java b/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/GetStatusTest.java
new file mode 100644
index 0000000..b59bd11
--- /dev/null
+++ b/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/GetStatusTest.java
@@ -0,0 +1,78 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation, and/or the GNU Lesser
+ * General Public License, version 2.1, also as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License and the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * and the GNU Lesser General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.rhq.modules.integrationTests.restApi;
+
+
+import com.eclipsesource.restfuse.AuthenticationType;
+import com.eclipsesource.restfuse.Destination;
+import com.eclipsesource.restfuse.HttpJUnitRunner;
+import com.eclipsesource.restfuse.Method;
+import com.eclipsesource.restfuse.Response;
+import com.eclipsesource.restfuse.annotation.Authentication;
+import com.eclipsesource.restfuse.annotation.Context;
+import com.eclipsesource.restfuse.annotation.HttpTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test the status part of the API
+ */
+(a)RunWith(HttpJUnitRunner.class)
+public class GetStatusTest {
+
+
+
+ @Rule
+ public Destination destination = new Destination("http://" + System.getProperty("rest.server","localhost") + ":7080/rest/1");
+
+ @Context
+ private Response response;
+
+
+ @Test
+ @HttpTest( method = Method.GET, path = "/status" )
+ public void testAuthRequired() {
+ com.eclipsesource.restfuse.Assert.assertUnauthorized(response);
+ }
+
+
+ @Test
+ @HttpTest( method = Method.GET, path = "/status" ,authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin")
+ )
+ public void testAuthRhqadmin() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ }
+
+ @Test
+ @HttpTest( method = Method.GET, path = "/status" ,authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "user", password = "name23")
+ )
+ public void testAuthRestricted() {
+ com.eclipsesource.restfuse.Assert.assertForbidden(response);
+ }
+
+
+}
diff --git a/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/ResourcesTest.java b/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/ResourcesTest.java
new file mode 100644
index 0000000..88cfaaf
--- /dev/null
+++ b/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/ResourcesTest.java
@@ -0,0 +1,104 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation, and/or the GNU Lesser
+ * General Public License, version 2.1, also as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License and the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * and the GNU Lesser General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.rhq.modules.integrationTests.restApi;
+
+import com.eclipsesource.restfuse.AuthenticationType;
+import com.eclipsesource.restfuse.Destination;
+import com.eclipsesource.restfuse.HttpJUnitRunner;
+import com.eclipsesource.restfuse.Method;
+import com.eclipsesource.restfuse.Response;
+import com.eclipsesource.restfuse.annotation.Authentication;
+import com.eclipsesource.restfuse.annotation.Context;
+import com.eclipsesource.restfuse.annotation.Header;
+import com.eclipsesource.restfuse.annotation.HttpTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * Test the resources part
+ * @author Heiko W. Rupp
+ */
+(a)RunWith(HttpJUnitRunner.class)
+public class ResourcesTest {
+
+ @Rule
+ public Destination destination = new Destination("http://" + System.getProperty("rest.server","localhost") + ":7080/rest/1");
+
+ @Context
+ private Response response;
+
+
+ @HttpTest( method = Method.GET, path = "/resource/platforms" )
+ public void testAuthRequired() {
+ com.eclipsesource.restfuse.Assert.assertUnauthorized(response);
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/platforms",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin")
+ )
+ public void testPlatformsPresent() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/10001",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin"),
+ headers = {@Header(name = "Accept",value ="application/json")}
+ )
+ public void testGetPlatformJson() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/10001",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin"),
+ headers = {@Header(name = "Accept",value ="application/xml")}
+ )
+ public void testGetPlatformXml() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/10001/schedules",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin"),
+ headers = {@Header(name = "Accept",value ="application/json")}
+ )
+ public void testGetPlatformSchedules() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/10001/children",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin"),
+ headers = {@Header(name = "Accept",value ="application/json")}
+ )
+ public void testGetPlatformChildren() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+}
commit 56eab8fd42ea5c3cfece191a71fef81fd8bf9c54
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Dec 7 12:03:28 2011 +0100
Improve Exception mapping
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
index d951767..58ccf30 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
@@ -18,11 +18,11 @@
*/
package org.rhq.enterprise.server.rest;
-import javax.ejb.EJBException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
+import org.rhq.enterprise.server.authz.PermissionException;
import org.rhq.enterprise.server.resource.ResourceNotFoundException;
/**
@@ -30,31 +30,39 @@ import org.rhq.enterprise.server.resource.ResourceNotFoundException;
* @author Heiko W. Rupp
*/
@Provider
-public class CustomExceptionMapper implements ExceptionMapper<EJBException> {
+public class CustomExceptionMapper implements ExceptionMapper<Exception> {
@Override
- public Response toResponse(EJBException e) {
+ public Response toResponse(Exception e) {
Throwable cause = e.getCause();
+ Response.ResponseBuilder builder;
if (cause !=null) {
+ Response.Status status;
if (cause instanceof StuffNotFoundException)
- return Response.status(Response.Status.NOT_FOUND)
- .entity(cause.getMessage())
- .build();
+ status =Response.Status.NOT_FOUND;
else if (cause instanceof ResourceNotFoundException)
- return Response.status(Response.Status.NOT_FOUND)
- .entity(cause.getMessage())
- .build();
+ status = Response.Status.NOT_FOUND;
else if (cause instanceof ParameterMissingException)
- return Response.status(Response.Status.NOT_ACCEPTABLE)
- .entity(cause.getMessage())
- .build();
+ status = Response.Status.NOT_ACCEPTABLE;
+ else if (cause instanceof PermissionException)
+ status = Response.Status.FORBIDDEN;
else
- return Response.status(Response.Status.SERVICE_UNAVAILABLE)
- .entity(cause.getMessage())
- .build();
+ status = Response.Status.SERVICE_UNAVAILABLE;
+
+ builder = Response.status(status);
+ builder.entity(cause.getMessage());
+ }
+ else {
+ if (e instanceof PermissionException) {
+ builder = Response.status(Response.Status.FORBIDDEN);
+ } else {
+ builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR);
+ }
+ if (e.getMessage()!=null)
+ builder.entity(e.getMessage());
}
- throw e;
+ return builder.build();
}
}
commit 8c1ab57450bc314e7a469ff968d4c24a806905ef
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Dec 7 09:30:45 2011 +0100
Allow to obtain metrics for multiple schedules at once.
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 8be8070..5934dbe 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
@@ -26,9 +26,14 @@ import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
@@ -38,6 +43,7 @@ import javax.ws.rs.core.UriInfo;
import org.jboss.cache.Fqn;
+import org.rhq.core.domain.common.EntityContext;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementSchedule;
@@ -73,7 +79,7 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
@Context Request request,
@Context HttpHeaders headers) {
- if (dataPoints<0)
+ if (dataPoints<=0)
throw new IllegalArgumentException("dataPoints must be >0 ");
if (startTime==0) {
@@ -103,11 +109,14 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
List<List<MeasurementDataNumericHighLowComposite>> listList = dataManager.findDataForResource(caller,
schedule.getResource().getId(), new int[]{definitionId}, startTime, endTime, dataPoints);
+/*
long minTime=Long.MAX_VALUE;
long maxTime=0;
+*/
if (!listList.isEmpty()) {
List<MeasurementDataNumericHighLowComposite> list = listList.get(0);
+/*
for (MeasurementDataNumericHighLowComposite c : list) {
long timestamp = c.getTimestamp();
if (!Double.isNaN(c.getValue()) || !hideEmpty) {
@@ -123,6 +132,9 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
}
res.setMaxTimeStamp(maxTime);
res.setMinTimeStamp(minTime);
+*/
+ res = fill(list,scheduleId,hideEmpty);
+ }
CacheControl cc = new CacheControl();
int maxAge = (int) (schedule.getInterval() / 1000L)/2; // millis ; half of schedule interval
@@ -144,6 +156,76 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
return builder.build();
}
+ private MetricAggregate fill(List<MeasurementDataNumericHighLowComposite> list,int scheduleId , boolean hideEmpty) {
+ long minTime=Long.MAX_VALUE;
+ long maxTime=0;
+ MetricAggregate res = new MetricAggregate();
+ res.setScheduleId(scheduleId);
+
+ for (MeasurementDataNumericHighLowComposite c : list) {
+ long timestamp = c.getTimestamp();
+ if (!Double.isNaN(c.getValue()) || !hideEmpty) {
+ MetricAggregate.DataPoint dp = new MetricAggregate.DataPoint(timestamp,c.getValue(),c.getHighValue(),c.getLowValue());
+ res.addDataPoint(dp);
+ }
+ if (timestamp <minTime)
+ minTime= timestamp;
+ if (timestamp >maxTime)
+ maxTime= timestamp;
+ }
+ res.setNumDataPoints(list.size());
+ res.setMaxTimeStamp(maxTime);
+ res.setMinTimeStamp(minTime);
+
+ return res;
+ }
+
+ @Override
+ @GET
+ @Path("data")
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_HTML})
+ public Response getMetricDataMulti(@QueryParam("sid") String schedules, @QueryParam("startTime") long startTime,
+ @QueryParam("endTime") long endTime, @QueryParam("dataPoints") int dataPoints,
+ @QueryParam("hideEmpty") boolean hideEmpty, @Context Request request,
+ @Context HttpHeaders headers) {
+
+ if (startTime==0) {
+ endTime = System.currentTimeMillis();
+ startTime = endTime - EIGHT_HOURS;
+ }
+
+ String[] tmp = schedules.split(",");
+ Integer[] scheduleIds = new Integer[tmp.length];
+ try {
+ for (int i = 0; i < tmp.length ; i++)
+ scheduleIds[i] = Integer.parseInt(tmp[i]);
+ }
+ catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("Bad input: " + nfe.getMessage());
+ }
+
+ List<MetricAggregate> resList = new ArrayList<MetricAggregate>(scheduleIds.length);
+ for (Integer scheduleId : scheduleIds) {
+ MeasurementSchedule sched = scheduleManager.getScheduleById(caller,scheduleId);
+ if (sched==null)
+ throw new StuffNotFoundException("Schedule with id " + scheduleId);
+ int definitionId = sched.getDefinition().getId();
+ List<List<MeasurementDataNumericHighLowComposite>> listList =
+ dataManager.findDataForContext(caller, EntityContext.forResource(sched.getResource().getId()),definitionId,startTime,endTime,dataPoints);
+ if (!listList.isEmpty()) {
+ List<MeasurementDataNumericHighLowComposite> list = listList.get(0);
+ MetricAggregate ma = fill(list,scheduleId,hideEmpty);
+ resList.add(ma);
+ }
+ else
+ throw new StuffNotFoundException("Metrics for schedule " + scheduleId);
+ }
+
+ GenericEntity<List<MetricAggregate>> metAgg = new GenericEntity<List<MetricAggregate>>(resList) {};
+
+ return Response.ok(metAgg).build();
+
+ }
/**
* Return a metric schedule with the respective status codes for cache validation
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 44844ae..7e908ad 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
@@ -18,7 +18,6 @@
*/
package org.rhq.enterprise.server.rest;
-import java.io.InputStream;
import java.util.List;
import javax.ejb.Local;
@@ -60,6 +59,17 @@ public interface MetricHandlerLocal {
@Context Request request,
@Context HttpHeaders headers);
+ @GET
+ @Path("data")
+ @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML})
+ Response getMetricDataMulti(@QueryParam("sid") String scheduleIds,
+ @QueryParam("startTime") long startTime,
+ @QueryParam("endTime") long endTime,
+ @QueryParam("dataPoints") int dataPoints,
+ @QueryParam("hideEmpty") boolean hideEmpty,
+ @Context Request request,
+ @Context HttpHeaders headers);
+
@GET
@Path("data/resource/{resourceId}")
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java
index d6af76e..16b6d40 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java
@@ -41,15 +41,17 @@ public class MetricAggregate {
long maxTimeStamp;
public MetricAggregate() {
+ dataPoints = new ArrayList<DataPoint>();
}
public MetricAggregate(Integer scheduleId, double min, double avg, double max) {
+ this();
this.scheduleId = scheduleId;
this.min = min;
this.avg = avg;
this.max = max;
- dataPoints = new ArrayList<DataPoint>();
+
}
@XmlElement
commit 71db2833a06ad1537d8a27fe5a0399850d6f81bd
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Tue Dec 6 16:48:56 2011 +0100
Allow to limit schedules by name
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 59efeba..64cdea3 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
@@ -195,6 +195,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
public Response getSchedules(int resourceId,
String scheduleType,
boolean enabledOnly,
+ String name,
@Context Request request,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) {
@@ -216,26 +217,28 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
if ("all".equals(scheduleType) ||
scheduleType.toLowerCase().equals(definition.getDataType().toString().toLowerCase()) ) {
if (!enabledOnly || (enabledOnly && schedule.isEnabled() )) {
- MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
- schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
- definition.getDataType().toString());
- UriBuilder uriBuilder;
- URI uri;
- if (definition.getDataType()== DataType.MEASUREMENT) {
+ if (name==null || (name!=null && name.equals(definition.getName()))) {
+ MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
+ schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
+ definition.getDataType().toString());
+ UriBuilder uriBuilder;
+ URI uri;
+ if (definition.getDataType()== DataType.MEASUREMENT) {
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/metric/data/{id}");
+ 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("/metric/data/{id}");
- uri = uriBuilder.build(schedule.getId());
- Link metricLink = new Link("metric",uri.toString());
- ms.addLink(metricLink);
+ uriBuilder.path("resource/" + schedule.getResource().getId());
+ uri = uriBuilder.build();
+ Link link = new Link("resource",uri.toString());
+ ms.addLink(link);
+
+ ret.add(ms);
}
- // 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);
}
}
}
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 ab9ea62..77f268f 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
@@ -88,6 +88,7 @@ public interface ResourceHandlerLocal {
Response getSchedules(@PathParam("id") int resourceId,
@QueryParam("type") @DefaultValue("all") String scheduleType,
@QueryParam("enabledOnly") @DefaultValue("true") boolean enabledOnly,
+ @QueryParam("name") String name,
@Context Request request,
@Context HttpHeaders headers,
@Context UriInfo uriInfo);
commit 01902864918073d86e08d088061ffc85efc593da
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Tue Dec 6 12:16:42 2011 +0100
Make ResourceWithType show up as <resource> in xml output
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
index 132fc34..735efa1 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
@@ -39,7 +39,7 @@ import org.jboss.resteasy.spi.touri.URITemplate;
* A (partial) resource with some type information
* @author Heiko W. Rupp
*/
-@XmlRootElement
+@XmlRootElement(name="resource")
/*
@XmlAccessorType(XmlAccessType.NONE)
@URITemplate("{id}")
commit 49eaa2bfe7d58efad3881522715f288d2ee01763
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Sun Nov 27 21:32:54 2011 +0100
Adapt the examples to the latest changes in the API.
Make the simple examples more stable by selecting active schedules via ajax callbacks.
Move whisker() into the rhq namespace.
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/bars_simple.html b/modules/enterprise/gui/rest-war/src/main/webapp/bars_simple.html
index 710b19b..e4e96d9 100644
--- a/modules/enterprise/gui/rest-war/src/main/webapp/bars_simple.html
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/bars_simple.html
@@ -3,6 +3,7 @@
<head>
<title>Bar Chart</title>
<script type="text/javascript" src="js/d3.js"></script>
+ <script type="text/javascript" src="js/jquery.js"></script>
<style type="text/css">
body {
@@ -16,107 +17,114 @@ svg {
</style>
</head>
<body>
- <h2>Schedule id 10003</h2>
+ <h1 id="resId">Resource</h1>
+ <h2 id="header">Schedule</h2>
<script type="text/javascript">
-//var data = d3.range(60).map(Math.random);
-
-d3.json(
- '/rest/1/metric/data/10003.json',
- function (jsondata) {
-
- // create the chart here with
- // the returned data
-
-// console.log(jsondata);
- var points = jsondata.metricAggregate.dataPoints;
- var min = jsondata.metricAggregate.min;
- var max = jsondata.metricAggregate.max;
-
- console.log(min);
- console.log(max);
-
- console.log(points);
-
- var data = points.map(function(d) {
- var t = d.value - min;
- if (t<0)
- t=0;
- return t;
- });
- console.log(data);
-
-
-
-var w = 430,
- h = 630,
- x = d3.scale.linear().domain([0, max-min]).range([0, w]),
- y = d3.scale.ordinal().domain(d3.range(data.length)).rangeBands([0, h], .2);
-
-var vis = d3.select("body")
- .append("svg:svg")
- .attr("width", w + 40)
- .attr("height", h + 20)
- .append("svg:g")
- .attr("transform", "translate(20,0)");
-
-var bars = vis.selectAll("g.bar")
- .data(data)
- .enter().append("svg:g")
- .attr("class", "bar")
- .attr("transform", function(d, i) { return "translate(0," + y(i) + ")"; });
-
-bars.append("svg:rect")
- .attr("fill", "steelblue")
- .attr("width", x)
- .attr("height", y.rangeBand());
-
-bars.append("svg:text")
- .attr("x", x)
- .attr("y", y.rangeBand() / 2)
- .attr("dx", -6)
- .attr("dy", ".35em")
- .attr("fill", "white")
- .attr("text-anchor", "end")
- .text(x.tickFormat(100));
-
-bars.append("svg:text")
- .attr("x", 0)
- .attr("y", y.rangeBand() / 2)
- .attr("dx", -6)
- .attr("dy", ".35em")
- .attr("text-anchor", "end")
- .text(function(d, i) { return String.fromCharCode(65 + i); });
-
-var rules = vis.selectAll("g.rule")
- .data(x.ticks(10))
- .enter().append("svg:g")
- .attr("class", "rule")
- .attr("transform", function(d) { return "translate(" + x(d) + ",0)"; });
-
-rules.append("svg:line")
- .attr("y1", h)
- .attr("y2", h + 6)
- .attr("stroke", "black");
-
-rules.append("svg:line")
- .attr("y1", 0)
- .attr("y2", h)
- .attr("stroke", "white")
- .attr("stroke-opacity", .3);
-
-rules.append("svg:text")
- .attr("y", h + 9)
- .attr("dy", ".71em")
- .attr("text-anchor", "middle")
- .text(x.tickFormat(10));
-
-vis.append("svg:line")
- .attr("y1", 0)
- .attr("y2", h)
- .attr("stroke", "black");
-
- });
+ // Get the platforms and then the first one
+ $.getJSON('/rest/1/resource/platforms.json', function (json) {
+ var res = json[0];
+ var rid = res.resourceId;
+ $("#resId").text("Resource " + rid + " (" + res.resourceName + ")");
+
+ // Now get the metrical schedules of the platform
+ $.getJSON("/rest/1/resource/"+rid+"/schedules.json?type=metric", function (json2) {
+
+ // Take the first
+ var schId = json2[0].scheduleId;
+ $("#header").text("Schedule " + schId + " (" + json2[0].scheduleName + ")");
+
+ // And call d3 graphing on it
+ d3.json(
+ '/rest/1/metric/data/'+schId+'.json',
+ function (jsondata) {
+
+ // create the chart here with
+ // the returned data
+
+ var points = jsondata.dataPoints;
+ var min = jsondata.min;
+ var max = jsondata.max;
+
+ var data = points.map(function(d) {
+ var t = d.value - min;
+ if (t<0)
+ t=0;
+ return t;
+ });
+
+ var w = 430,
+ h = 630,
+ x = d3.scale.linear().domain([0, max-min]).range([0, w]),
+ y = d3.scale.ordinal().domain(d3.range(data.length)).rangeBands([0, h], .2);
+
+ var vis = d3.select("body")
+ .append("svg:svg")
+ .attr("width", w + 40)
+ .attr("height", h + 20)
+ .append("svg:g")
+ .attr("transform", "translate(20,0)");
+
+ var bars = vis.selectAll("g.bar")
+ .data(data)
+ .enter().append("svg:g")
+ .attr("class", "bar")
+ .attr("transform", function(d, i) { return "translate(0," + y(i) + ")"; });
+
+ bars.append("svg:rect")
+ .attr("fill", "steelblue")
+ .attr("width", x)
+ .attr("height", y.rangeBand());
+
+ bars.append("svg:text")
+ .attr("x", x)
+ .attr("y", y.rangeBand() / 2)
+ .attr("dx", -6)
+ .attr("dy", ".35em")
+ .attr("fill", "white")
+ .attr("text-anchor", "end")
+ .text(x.tickFormat(100));
+
+ bars.append("svg:text")
+ .attr("x", 0)
+ .attr("y", y.rangeBand() / 2)
+ .attr("dx", -6)
+ .attr("dy", ".35em")
+ .attr("text-anchor", "end")
+ .text(function(d, i) { return String.fromCharCode(65 + i); });
+
+ var rules = vis.selectAll("g.rule")
+ .data(x.ticks(10))
+ .enter().append("svg:g")
+ .attr("class", "rule")
+ .attr("transform", function(d) { return "translate(" + x(d) + ",0)"; });
+
+ rules.append("svg:line")
+ .attr("y1", h)
+ .attr("y2", h + 6)
+ .attr("stroke", "black");
+
+ rules.append("svg:line")
+ .attr("y1", 0)
+ .attr("y2", h)
+ .attr("stroke", "white")
+ .attr("stroke-opacity", .3);
+
+ rules.append("svg:text")
+ .attr("y", h + 9)
+ .attr("dy", ".71em")
+ .attr("text-anchor", "middle")
+ .text(x.tickFormat(10));
+
+ vis.append("svg:line")
+ .attr("y1", 0)
+ .attr("y2", h)
+ .attr("stroke", "black");
+
+ });
+
+ })
+ })
</script>
</body>
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/jquery.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/jquery.js
new file mode 100644
index 0000000..8ccd0ea
--- /dev/null
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/jquery.js
@@ -0,0 +1,9266 @@
+/*!
+ * jQuery JavaScript Library v1.7.1
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Nov 21 21:11:03 2011 -0500
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+ navigator = window.navigator,
+ location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // A simple way to check for HTML strings or ID strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Check if a string has a non-whitespace character in it
+ rnotwhite = /\S/,
+
+ // Used for trimming whitespace
+ trimLeft = /^\s+/,
+ trimRight = /\s+$/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+ // Useragent RegExp
+ rwebkit = /(webkit)[ \/]([\w.]+)/,
+ ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+ rmsie = /(msie) ([\w.]+)/,
+ rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+ // Matches dashed string for camelizing
+ rdashAlpha = /-([a-z]|[0-9])/ig,
+ rmsPrefix = /^-ms-/,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // Keep a UserAgent string for use with jQuery.browser
+ userAgent = navigator.userAgent,
+
+ // For matching the engine and version of the browser
+ browserMatch,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // The ready event handler
+ DOMContentLoaded,
+
+ // Save a reference to some core methods
+ toString = Object.prototype.toString,
+ hasOwn = Object.prototype.hasOwnProperty,
+ push = Array.prototype.push,
+ slice = Array.prototype.slice,
+ trim = String.prototype.trim,
+ indexOf = Array.prototype.indexOf,
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), or $(undefined)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // The body element only exists once, optimize finding it
+ if ( selector === "body" && !context && document.body ) {
+ this.context = document;
+ this[0] = document.body;
+ this.selector = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ // Are we dealing with HTML string or an ID?
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = quickExpr.exec( selector );
+ }
+
+ // Verify a match, and that no context was specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context ? context.ownerDocument || context : document );
+
+ // If a single string is passed in and it's a single tag
+ // just do a createElement and skip the rest
+ ret = rsingleTag.exec( selector );
+
+ if ( ret ) {
+ if ( jQuery.isPlainObject( context ) ) {
+ selector = [ document.createElement( ret[1] ) ];
+ jQuery.fn.attr.call( selector, context, true );
+
+ } else {
+ selector = [ doc.createElement( ret[1] ) ];
+ }
+
+ } else {
+ ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+ selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $("#id")
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.7.1",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return slice.call( this, 0 );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+ // Build a new jQuery matched element set
+ var ret = this.constructor();
+
+ if ( jQuery.isArray( elems ) ) {
+ push.apply( ret, elems );
+
+ } else {
+ jQuery.merge( ret, elems );
+ }
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Attach the listeners
+ jQuery.bindReady();
+
+ // Add the callback
+ readyList.add( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( slice.apply( this, arguments ),
+ "slice", slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+ // Either a released hold or an DOMready/load event and not yet ready
+ if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.fireWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger( "ready" ).off( "ready" );
+ }
+ }
+ },
+
+ bindReady: function() {
+ if ( readyList ) {
+ return;
+ }
+
+ readyList = jQuery.Callbacks( "once memory" );
+
+ // Catch cases where $(document).ready() is called after the
+ // browser event has already occurred.
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Mozilla, Opera and webkit nightlies currently support this event
+ if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else if ( document.attachEvent ) {
+ // ensure firing before onload,
+ // maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var toplevel = false;
+
+ try {
+ toplevel = window.frameElement == null;
+ } catch(e) {}
+
+ if ( document.documentElement.doScroll && toplevel ) {
+ doScrollCheck();
+ }
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ // A crude way of determining if an object is a window
+ isWindow: function( obj ) {
+ return obj && typeof obj === "object" && "setInterval" in obj;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !hasOwn.call(obj, "constructor") &&
+ !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ parseJSON: function( data ) {
+ if ( typeof data !== "string" || !data ) {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-...
+ globalEval: function( data ) {
+ if ( data && rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+ },
+
+ // args is for internal usage only
+ each: function( object, callback, args ) {
+ var name, i = 0,
+ length = object.length,
+ isObj = length === undefined || jQuery.isFunction( object );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.apply( object[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( object[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in object ) {
+ if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return object;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: trim ?
+ function( text ) {
+ return text == null ?
+ "" :
+ trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( array, results ) {
+ var ret = results || [];
+
+ if ( array != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ var type = jQuery.type( array );
+
+ if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+ push.call( ret, array );
+ } else {
+ jQuery.merge( ret, array );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, array, i ) {
+ var len;
+
+ if ( array ) {
+ if ( indexOf ) {
+ return indexOf.call( array, elem, i );
+ }
+
+ len = array.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in array && array[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var i = first.length,
+ j = 0;
+
+ if ( typeof second.length === "number" ) {
+ for ( var l = second.length; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var ret = [], retVal;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( var i = 0, length = elems.length; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key, ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ if ( typeof context === "string" ) {
+ var tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ var args = slice.call( arguments, 2 ),
+ proxy = function() {
+ return fn.apply( context, args.concat( slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Mutifunctional method to get and set values to a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, key, value, exec, fn, pass ) {
+ var length = elems.length;
+
+ // Setting many attributes
+ if ( typeof key === "object" ) {
+ for ( var k in key ) {
+ jQuery.access( elems, k, key[k], exec, fn, value );
+ }
+ return elems;
+ }
+
+ // Setting one attribute
+ if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = !pass && exec && jQuery.isFunction(value);
+
+ for ( var i = 0; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+
+ return elems;
+ }
+
+ // Getting an attribute
+ return length ? fn( elems[0], key ) : undefined;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ },
+
+ // Use of jQuery.browser is frowned upon.
+ // More details: http://docs.jquery.com/Utilities/jQuery.browser
+ uaMatch: function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = rwebkit.exec( ua ) ||
+ ropera.exec( ua ) ||
+ rmsie.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+ [];
+
+ return { browser: match[1] || "", version: match[2] || "0" };
+ },
+
+ sub: function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+ },
+
+ browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+ jQuery.browser[ browserMatch.browser ] = true;
+ jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+ jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+ trimLeft = /^[\s\xA0]+/;
+ trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+ DOMContentLoaded = function() {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ };
+
+} else if ( document.attachEvent ) {
+ DOMContentLoaded = function() {
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( document.readyState === "complete" ) {
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ };
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+ if ( jQuery.isReady ) {
+ return;
+ }
+
+ try {
+ // If IE is used, use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ document.documentElement.doScroll("left");
+ } catch(e) {
+ setTimeout( doScrollCheck, 1 );
+ return;
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+ var object = flagsCache[ flags ] = {},
+ i, length;
+ flags = flags.split( /\s+/ );
+ for ( i = 0, length = flags.length; i < length; i++ ) {
+ object[ flags[i] ] = true;
+ }
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * flags: an optional list of space-separated flags that will change how
+ * the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+ // Convert flags from String-formatted to Object-formatted
+ // (we check in cache first)
+ flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+ var // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = [],
+ // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Add one or several callbacks to the list
+ add = function( args ) {
+ var i,
+ length,
+ elem,
+ type,
+ actual;
+ for ( i = 0, length = args.length; i < length; i++ ) {
+ elem = args[ i ];
+ type = jQuery.type( elem );
+ if ( type === "array" ) {
+ // Inspect recursively
+ add( elem );
+ } else if ( type === "function" ) {
+ // Add if not in unique mode and callback is not in
+ if ( !flags.unique || !self.has( elem ) ) {
+ list.push( elem );
+ }
+ }
+ }
+ },
+ // Fire callbacks
+ fire = function( context, args ) {
+ args = args || [];
+ memory = !flags.memory || [ context, args ];
+ firing = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+ memory = true; // Mark as halted
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( !flags.once ) {
+ if ( stack && stack.length ) {
+ memory = stack.shift();
+ self.fireWith( memory[ 0 ], memory[ 1 ] );
+ }
+ } else if ( memory === true ) {
+ self.disable();
+ } else {
+ list = [];
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ var length = list.length;
+ add( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away, unless previous
+ // firing was halted (stopOnFalse)
+ } else if ( memory && memory !== true ) {
+ firingStart = length;
+ fire( memory[ 0 ], memory[ 1 ] );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ var args = arguments,
+ argIndex = 0,
+ argLength = args.length;
+ for ( ; argIndex < argLength ; argIndex++ ) {
+ for ( var i = 0; i < list.length; i++ ) {
+ if ( args[ argIndex ] === list[ i ] ) {
+ // Handle firingIndex and firingLength
+ if ( firing ) {
+ if ( i <= firingLength ) {
+ firingLength--;
+ if ( i <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ // Remove the element
+ list.splice( i--, 1 );
+ // If we have some unicity property then
+ // we only need to do this once
+ if ( flags.unique ) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ if ( list ) {
+ var i = 0,
+ length = list.length;
+ for ( ; i < length; i++ ) {
+ if ( fn === list[ i ] ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory || memory === true ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ if ( stack ) {
+ if ( firing ) {
+ if ( !flags.once ) {
+ stack.push( [ context, args ] );
+ }
+ } else if ( !( flags.once && memory ) ) {
+ fire( context, args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!memory;
+ }
+ };
+
+ return self;
+};
+
+
+
+
+var // Static reference to slice
+ sliceDeferred = [].slice;
+
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var doneList = jQuery.Callbacks( "once memory" ),
+ failList = jQuery.Callbacks( "once memory" ),
+ progressList = jQuery.Callbacks( "memory" ),
+ state = "pending",
+ lists = {
+ resolve: doneList,
+ reject: failList,
+ notify: progressList
+ },
+ promise = {
+ done: doneList.add,
+ fail: failList.add,
+ progress: progressList.add,
+
+ state: function() {
+ return state;
+ },
+
+ // Deprecated
+ isResolved: doneList.fired,
+ isRejected: failList.fired,
+
+ then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+ deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+ return this;
+ },
+ always: function() {
+ deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+ return this;
+ },
+ pipe: function( fnDone, fnFail, fnProgress ) {
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( {
+ done: [ fnDone, "resolve" ],
+ fail: [ fnFail, "reject" ],
+ progress: [ fnProgress, "notify" ]
+ }, function( handler, data ) {
+ var fn = data[ 0 ],
+ action = data[ 1 ],
+ returned;
+ if ( jQuery.isFunction( fn ) ) {
+ deferred[ handler ](function() {
+ returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ });
+ } else {
+ deferred[ handler ]( newDefer[ action ] );
+ }
+ });
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ if ( obj == null ) {
+ obj = promise;
+ } else {
+ for ( var key in promise ) {
+ obj[ key ] = promise[ key ];
+ }
+ }
+ return obj;
+ }
+ },
+ deferred = promise.promise({}),
+ key;
+
+ for ( key in lists ) {
+ deferred[ key ] = lists[ key ].fire;
+ deferred[ key + "With" ] = lists[ key ].fireWith;
+ }
+
+ // Handle state
+ deferred.done( function() {
+ state = "resolved";
+ }, failList.disable, progressList.lock ).fail( function() {
+ state = "rejected";
+ }, doneList.disable, progressList.lock );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( firstParam ) {
+ var args = sliceDeferred.call( arguments, 0 ),
+ i = 0,
+ length = args.length,
+ pValues = new Array( length ),
+ count = length,
+ pCount = length,
+ deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+ firstParam :
+ jQuery.Deferred(),
+ promise = deferred.promise();
+ function resolveFunc( i ) {
+ return function( value ) {
+ args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ if ( !( --count ) ) {
+ deferred.resolveWith( deferred, args );
+ }
+ };
+ }
+ function progressFunc( i ) {
+ return function( value ) {
+ pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+ deferred.notifyWith( promise, pValues );
+ };
+ }
+ if ( length > 1 ) {
+ for ( ; i < length; i++ ) {
+ if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+ args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+ } else {
+ --count;
+ }
+ }
+ if ( !count ) {
+ deferred.resolveWith( deferred, args );
+ }
+ } else if ( deferred !== firstParam ) {
+ deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+ }
+ return promise;
+ }
+});
+
+
+
+
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ marginDiv,
+ fragment,
+ tds,
+ events,
+ eventName,
+ i,
+ isSupported,
+ div = document.createElement( "div" ),
+ documentElement = document.documentElement;
+
+ // Preliminary tests
+ div.setAttribute("className", "t");
+ div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+ all = div.getElementsByTagName( "*" );
+ a = div.getElementsByTagName( "a" )[ 0 ];
+
+ // Can't get basic test support
+ if ( !all || !all.length || !a ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement( "select" );
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName( "input" )[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.55/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent( "onclick" );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute("type", "radio");
+ support.radioValue = input.value === "t";
+
+ input.setAttribute("checked", "checked");
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ div.innerHTML = "";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ if ( window.getComputedStyle ) {
+ marginDiv = document.createElement( "div" );
+ marginDiv.style.width = "0";
+ marginDiv.style.marginRight = "0";
+ div.style.width = "2px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+ }
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for( i in {
+ submit: 1,
+ change: 1,
+ focusin: 1
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ fragment.removeChild( div );
+
+ // Null elements to avoid leaks in IE
+ fragment = select = opt = marginDiv = div = input = null;
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, outer, inner, table, td, offsetSupport,
+ conMarginTop, ptlm, vb, style, html,
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ conMarginTop = 1;
+ ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";
+ vb = "visibility:hidden;border:0;";
+ style = "style='" + ptlm + "border:5px solid #000;padding:0;'";
+ html = "<div " + style + "><div></div></div>" +
+ "<table " + style + " cellpadding='0' cellspacing='0'>" +
+ "<tr><td></td></tr></table>";
+
+ container = document.createElement("div");
+ container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+ tds = div.getElementsByTagName( "td" );
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Figure out if the W3C box model works as expected
+ div.innerHTML = "";
+ div.style.width = div.style.paddingLeft = "1px";
+ jQuery.boxModel = support.boxModel = div.offsetWidth === 2;
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.style.display = "inline";
+ div.style.zoom = 1;
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "";
+ div.innerHTML = "<div style='width:4px;'></div>";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
+ }
+
+ div.style.cssText = ptlm + vb;
+ div.innerHTML = html;
+
+ outer = div.firstChild;
+ inner = outer.firstChild;
+ td = outer.nextSibling.firstChild.firstChild;
+
+ offsetSupport = {
+ doesNotAddBorder: ( inner.offsetTop !== 5 ),
+ doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+ };
+
+ inner.style.position = "fixed";
+ inner.style.top = "20px";
+
+ // safari subtracts parent border width here which is 5px
+ offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+ inner.style.position = inner.style.top = "";
+
+ outer.style.overflow = "hidden";
+ outer.style.position = "relative";
+
+ offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+ offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+ body.removeChild( container );
+ div = container = null;
+
+ jQuery.extend( support, offsetSupport );
+ });
+
+ return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ // Please use with caution
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var privateCache, thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+ isEvents = name === "events";
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = ++jQuery.uuid;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ privateCache = thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Users should not attempt to inspect the internal events object using jQuery.data,
+ // it is undocumented and subject to change. But does anyone listen? No.
+ if ( isEvents && !thisCache[ name ] ) {
+ return privateCache.events;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ // Reference to internal data cache key
+ internalKey = jQuery.expando,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+
+ // See jQuery.data for more information
+ id = isNode ? elem[ internalKey ] : internalKey;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split( " " );
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject(cache[ id ]) ) {
+ return;
+ }
+ }
+
+ // Browsers that fail expando deletion also refuse to delete expandos on
+ // the window, but it will allow it on all other JS objects; other browsers
+ // don't care
+ // Ensure that `cache` is not a window object #10080
+ if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+ delete cache[ id ];
+ } else {
+ cache[ id ] = null;
+ }
+
+ // We destroyed the cache and need to eliminate the expando on the node to avoid
+ // false lookups in the cache for entries that no longer exist
+ if ( isNode ) {
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( jQuery.support.deleteExpando ) {
+ delete elem[ internalKey ];
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+ } else {
+ elem[ internalKey ] = null;
+ }
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ if ( elem.nodeName ) {
+ var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ if ( match ) {
+ return !(match === true || elem.getAttribute("classid") !== match);
+ }
+ }
+
+ return true;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, attr, name,
+ data = null;
+
+ if ( typeof key === "undefined" ) {
+ if ( this.length ) {
+ data = jQuery.data( this[0] );
+
+ if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
+ attr = this[0].attributes;
+ for ( var i = 0, l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( name.indexOf( "data-" ) === 0 ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( this[0], name, data[ name ] );
+ }
+ }
+ jQuery._data( this[0], "parsedAttrs", true );
+ }
+ }
+
+ return data;
+
+ } else if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split(".");
+ parts[1] = parts[1] ? "." + parts[1] : "";
+
+ if ( value === undefined ) {
+ data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && this.length ) {
+ data = jQuery.data( this[0], key );
+ data = dataAttr( this[0], key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+
+ } else {
+ return this.each(function() {
+ var self = jQuery( this ),
+ args = [ parts[0], value ];
+
+ self.triggerHandler( "setData" + parts[1] + "!", args );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + parts[1] + "!", args );
+ });
+ }
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ jQuery.isNumeric( data ) ? parseFloat( data ) :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ for ( var name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+ var deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ defer = jQuery._data( elem, deferDataKey );
+ if ( defer &&
+ ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+ ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+ // Give room for hard-coded callbacks to fire first
+ // and eventually mark/queue something else on the element
+ setTimeout( function() {
+ if ( !jQuery._data( elem, queueDataKey ) &&
+ !jQuery._data( elem, markDataKey ) ) {
+ jQuery.removeData( elem, deferDataKey, true );
+ defer.fire();
+ }
+ }, 0 );
+ }
+}
+
+jQuery.extend({
+
+ _mark: function( elem, type ) {
+ if ( elem ) {
+ type = ( type || "fx" ) + "mark";
+ jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+ }
+ },
+
+ _unmark: function( force, elem, type ) {
+ if ( force !== true ) {
+ type = elem;
+ elem = force;
+ force = false;
+ }
+ if ( elem ) {
+ type = type || "fx";
+ var key = type + "mark",
+ count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+ if ( count ) {
+ jQuery._data( elem, key, count );
+ } else {
+ jQuery.removeData( elem, key, true );
+ handleQueueMarkDefer( elem, type, "mark" );
+ }
+ }
+ },
+
+ queue: function( elem, type, data ) {
+ var q;
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ q = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !q || jQuery.isArray(data) ) {
+ q = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ q.push( data );
+ }
+ }
+ return q || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ fn = queue.shift(),
+ hooks = {};
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ }
+
+ if ( fn ) {
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ jQuery._data( elem, type + ".run", hooks );
+ fn.call( elem, function() {
+ jQuery.dequeue( elem, type );
+ }, hooks );
+ }
+
+ if ( !queue.length ) {
+ jQuery.removeData( elem, type + "queue " + type + ".run", true );
+ handleQueueMarkDefer( elem, type, "queue" );
+ }
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ }
+
+ if ( data === undefined ) {
+ return jQuery.queue( this[0], type );
+ }
+ return this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, object ) {
+ if ( typeof type !== "string" ) {
+ object = type;
+ type = undefined;
+ }
+ type = type || "fx";
+ var defer = jQuery.Deferred(),
+ elements = this,
+ i = elements.length,
+ count = 1,
+ deferDataKey = type + "defer",
+ queueDataKey = type + "queue",
+ markDataKey = type + "mark",
+ tmp;
+ function resolve() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ }
+ while( i-- ) {
+ if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+ ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+ jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+ jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+ count++;
+ tmp.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise();
+ }
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+ rspace = /\s+/,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea)?$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute,
+ nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.attr );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, name, value, true, jQuery.prop );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var classNames, i, l, elem, className, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( (value && typeof value === "string") || value === undefined ) {
+ classNames = ( value || "" ).split( rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 && elem.className ) {
+ if ( value ) {
+ className = (" " + elem.className + " ").replace( rclass, " " );
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ className = className.replace(" " + classNames[ c ] + " ", " ");
+ }
+ elem.className = jQuery.trim( className );
+
+ } else {
+ elem.className = "";
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space seperated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var self = jQuery(this), val;
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ attrFn: {
+ val: true,
+ css: true,
+ html: true,
+ text: true,
+ data: true,
+ width: true,
+ height: true,
+ offset: true
+ },
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && name in jQuery.attrFn ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, "" + value );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, l,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+ attrNames = value.toLowerCase().split( rspace );
+ l = attrNames.length;
+
+ for ( ; i < l; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ jQuery.attr( elem, name, "" );
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( rboolean.test( name ) && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabi...
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+ ret.nodeValue :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.nodeValue = value + "" );
+ }
+ };
+
+ // Apply the nodeHook to tabindex
+ jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = "" + value );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+ rhoverHack = /\bhover(\.\S+)?\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+ quickParse = function( selector ) {
+ var quick = rquickIs.exec( selector );
+ if ( quick ) {
+ // 0 1 2 3
+ // [ _, tag, id, class ]
+ quick[1] = ( quick[1] || "" ).toLowerCase();
+ quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+ }
+ return quick;
+ },
+ quickIs = function( elem, m ) {
+ var attrs = elem.attributes || {};
+ return (
+ (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+ (!m[2] || (attrs.id || {}).value === m[2]) &&
+ (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+ );
+ },
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, quick, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ quick: quickParse( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+ t, tns, type, origType, namespaces, origCount,
+ j, events, special, handle, eventType, handleObj;
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ handle = elemData.handle;
+ if ( handle ) {
+ handle.elem = null;
+ }
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, [ "events", "handle" ], true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var type = event.type || event,
+ namespaces = [],
+ cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ old = null;
+ for ( ; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old && old === elem.ownerDocument ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = [].slice.call( arguments, 0 ),
+ run_all = !event.exclusive && !event.namespace,
+ handlerQueue = [],
+ i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) {
+
+ // Pregenerate a single jQuery object for reuse with .is()
+ jqcur = jQuery(this);
+ jqcur.context = this.ownerDocument || this;
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+ selMatch = {};
+ matches = [];
+ jqcur[0] = cur;
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = (
+ handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+ );
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+ if ( event.metaKey === undefined ) {
+ event.metaKey = event.ctrlKey;
+ }
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ ready: {
+ // Make sure the ready event is setup
+ setup: jQuery.bindReady
+ },
+
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ if ( elem.detachEvent ) {
+ elem.detachEvent( "on" + type, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-bind...
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector,
+ ret;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !form._submit_attached ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ });
+ form._submit_attached = true;
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ jQuery.event.simulate( "change", this, event, true );
+ }
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ elem._change_attached = true;
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) {
+ // ( types-Object, data )
+ data = selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on.call( this, types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ var handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( var type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( jQuery.attrFn ) {
+ jQuery.attrFn[ name ] = true;
+ }
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ * More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+ expando = "sizcache" + (Math.random() + '').replace('.', ''),
+ done = 0,
+ toString = Object.prototype.toString,
+ hasDuplicate = false,
+ baseHasDuplicate = true,
+ rBackslash = /\\/g,
+ rReturn = /\r\n/g,
+ rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+// Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+ baseHasDuplicate = false;
+ return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+
+ var origContext = context;
+
+ if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+ return [];
+ }
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ var m, set, checkSet, extra, ret, cur, pop, i,
+ prune = true,
+ contextXML = Sizzle.isXML( context ),
+ parts = [],
+ soFar = selector;
+
+ // Reset the position of the chunker regexp (start from head)
+ do {
+ chunker.exec( "" );
+ m = chunker.exec( soFar );
+
+ if ( m ) {
+ soFar = m[3];
+
+ parts.push( m[1] );
+
+ if ( m[2] ) {
+ extra = m[3];
+ break;
+ }
+ }
+ } while ( m );
+
+ if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+ if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+ set = posProcess( parts[0] + parts[1], context, seed );
+
+ } else {
+ set = Expr.relative[ parts[0] ] ?
+ [ context ] :
+ Sizzle( parts.shift(), context );
+
+ while ( parts.length ) {
+ selector = parts.shift();
+
+ if ( Expr.relative[ selector ] ) {
+ selector += parts.shift();
+ }
+
+ set = posProcess( selector, set, seed );
+ }
+ }
+
+ } else {
+ // Take a shortcut and set the context if the root selector is an ID
+ // (but not if it'll be faster if the inner selector is an ID)
+ if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+ Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+ ret = Sizzle.find( parts.shift(), context, contextXML );
+ context = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set )[0] :
+ ret.set[0];
+ }
+
+ if ( context ) {
+ ret = seed ?
+ { expr: parts.pop(), set: makeArray(seed) } :
+ Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+ set = ret.expr ?
+ Sizzle.filter( ret.expr, ret.set ) :
+ ret.set;
+
+ if ( parts.length > 0 ) {
+ checkSet = makeArray( set );
+
+ } else {
+ prune = false;
+ }
+
+ while ( parts.length ) {
+ cur = parts.pop();
+ pop = cur;
+
+ if ( !Expr.relative[ cur ] ) {
+ cur = "";
+ } else {
+ pop = parts.pop();
+ }
+
+ if ( pop == null ) {
+ pop = context;
+ }
+
+ Expr.relative[ cur ]( checkSet, pop, contextXML );
+ }
+
+ } else {
+ checkSet = parts = [];
+ }
+ }
+
+ if ( !checkSet ) {
+ checkSet = set;
+ }
+
+ if ( !checkSet ) {
+ Sizzle.error( cur || selector );
+ }
+
+ if ( toString.call(checkSet) === "[object Array]" ) {
+ if ( !prune ) {
+ results.push.apply( results, checkSet );
+
+ } else if ( context && context.nodeType === 1 ) {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+ results.push( set[i] );
+ }
+ }
+
+ } else {
+ for ( i = 0; checkSet[i] != null; i++ ) {
+ if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+ results.push( set[i] );
+ }
+ }
+ }
+
+ } else {
+ makeArray( checkSet, results );
+ }
+
+ if ( extra ) {
+ Sizzle( extra, origContext, results, seed );
+ Sizzle.uniqueSort( results );
+ }
+
+ return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+ if ( sortOrder ) {
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( var i = 1; i < results.length; i++ ) {
+ if ( results[i] === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+ return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+ return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+ var set, i, len, match, type, left;
+
+ if ( !expr ) {
+ return [];
+ }
+
+ for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+ type = Expr.order[i];
+
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+ left = match[1];
+ match.splice( 1, 1 );
+
+ if ( left.substr( left.length - 1 ) !== "\\" ) {
+ match[1] = (match[1] || "").replace( rBackslash, "" );
+ set = Expr.find[ type ]( match, context, isXML );
+
+ if ( set != null ) {
+ expr = expr.replace( Expr.match[ type ], "" );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !set ) {
+ set = typeof context.getElementsByTagName !== "undefined" ?
+ context.getElementsByTagName( "*" ) :
+ [];
+ }
+
+ return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+ var match, anyFound,
+ type, found, item, filter, left,
+ i, pass,
+ old = expr,
+ result = [],
+ curLoop = set,
+ isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+ while ( expr && set.length ) {
+ for ( type in Expr.filter ) {
+ if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+ filter = Expr.filter[ type ];
+ left = match[1];
+
+ anyFound = false;
+
+ match.splice(1,1);
+
+ if ( left.substr( left.length - 1 ) === "\\" ) {
+ continue;
+ }
+
+ if ( curLoop === result ) {
+ result = [];
+ }
+
+ if ( Expr.preFilter[ type ] ) {
+ match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+ if ( !match ) {
+ anyFound = found = true;
+
+ } else if ( match === true ) {
+ continue;
+ }
+ }
+
+ if ( match ) {
+ for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+ if ( item ) {
+ found = filter( item, match, i, curLoop );
+ pass = not ^ found;
+
+ if ( inplace && found != null ) {
+ if ( pass ) {
+ anyFound = true;
+
+ } else {
+ curLoop[i] = false;
+ }
+
+ } else if ( pass ) {
+ result.push( item );
+ anyFound = true;
+ }
+ }
+ }
+ }
+
+ if ( found !== undefined ) {
+ if ( !inplace ) {
+ curLoop = result;
+ }
+
+ expr = expr.replace( Expr.match[ type ], "" );
+
+ if ( !anyFound ) {
+ return [];
+ }
+
+ break;
+ }
+ }
+ }
+
+ // Improper expression
+ if ( expr === old ) {
+ if ( anyFound == null ) {
+ Sizzle.error( expr );
+
+ } else {
+ break;
+ }
+ }
+
+ old = expr;
+ }
+
+ return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+ var i, node,
+ nodeType = elem.nodeType,
+ ret = "";
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 ) {
+ // Use textContent || innerText for elements
+ if ( typeof elem.textContent === 'string' ) {
+ return elem.textContent;
+ } else if ( typeof elem.innerText === 'string' ) {
+ // Replace IE's carriage returns
+ return elem.innerText.replace( rReturn, '' );
+ } else {
+ // Traverse it's children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( i = 0; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ if ( node.nodeType !== 8 ) {
+ ret += getText( node );
+ }
+ }
+ }
+ return ret;
+};
+
+var Expr = Sizzle.selectors = {
+ order: [ "ID", "NAME", "TAG" ],
+
+ match: {
+ ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+ NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+ ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+ TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+ CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+ POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+ PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+ },
+
+ leftMatch: {},
+
+ attrMap: {
+ "class": "className",
+ "for": "htmlFor"
+ },
+
+ attrHandle: {
+ href: function( elem ) {
+ return elem.getAttribute( "href" );
+ },
+ type: function( elem ) {
+ return elem.getAttribute( "type" );
+ }
+ },
+
+ relative: {
+ "+": function(checkSet, part){
+ var isPartStr = typeof part === "string",
+ isTag = isPartStr && !rNonWord.test( part ),
+ isPartStrNotTag = isPartStr && !isTag;
+
+ if ( isTag ) {
+ part = part.toLowerCase();
+ }
+
+ for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+ if ( (elem = checkSet[i]) ) {
+ while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+ checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+ elem || false :
+ elem === part;
+ }
+ }
+
+ if ( isPartStrNotTag ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ },
+
+ ">": function( checkSet, part ) {
+ var elem,
+ isPartStr = typeof part === "string",
+ i = 0,
+ l = checkSet.length;
+
+ if ( isPartStr && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ var parent = elem.parentNode;
+ checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+ }
+ }
+
+ } else {
+ for ( ; i < l; i++ ) {
+ elem = checkSet[i];
+
+ if ( elem ) {
+ checkSet[i] = isPartStr ?
+ elem.parentNode :
+ elem.parentNode === part;
+ }
+ }
+
+ if ( isPartStr ) {
+ Sizzle.filter( part, checkSet, true );
+ }
+ }
+ },
+
+ "": function(checkSet, part, isXML){
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+ },
+
+ "~": function( checkSet, part, isXML ) {
+ var nodeCheck,
+ doneName = done++,
+ checkFn = dirCheck;
+
+ if ( typeof part === "string" && !rNonWord.test( part ) ) {
+ part = part.toLowerCase();
+ nodeCheck = part;
+ checkFn = dirNodeCheck;
+ }
+
+ checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+ }
+ },
+
+ find: {
+ ID: function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ },
+
+ NAME: function( match, context ) {
+ if ( typeof context.getElementsByName !== "undefined" ) {
+ var ret = [],
+ results = context.getElementsByName( match[1] );
+
+ for ( var i = 0, l = results.length; i < l; i++ ) {
+ if ( results[i].getAttribute("name") === match[1] ) {
+ ret.push( results[i] );
+ }
+ }
+
+ return ret.length === 0 ? null : ret;
+ }
+ },
+
+ TAG: function( match, context ) {
+ if ( typeof context.getElementsByTagName !== "undefined" ) {
+ return context.getElementsByTagName( match[1] );
+ }
+ }
+ },
+ preFilter: {
+ CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+ match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+ if ( isXML ) {
+ return match;
+ }
+
+ for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+ if ( elem ) {
+ if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+ if ( !inplace ) {
+ result.push( elem );
+ }
+
+ } else if ( inplace ) {
+ curLoop[i] = false;
+ }
+ }
+ }
+
+ return false;
+ },
+
+ ID: function( match ) {
+ return match[1].replace( rBackslash, "" );
+ },
+
+ TAG: function( match, curLoop ) {
+ return match[1].replace( rBackslash, "" ).toLowerCase();
+ },
+
+ CHILD: function( match ) {
+ if ( match[1] === "nth" ) {
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ match[2] = match[2].replace(/^\+|\s*/g, '');
+
+ // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+ var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+ match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+ !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+ // calculate the numbers (first)n+(last) including if they are negative
+ match[2] = (test[1] + (test[2] || 1)) - 0;
+ match[3] = test[3] - 0;
+ }
+ else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // TODO: Move to normal caching system
+ match[0] = done++;
+
+ return match;
+ },
+
+ ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+ var name = match[1] = match[1].replace( rBackslash, "" );
+
+ if ( !isXML && Expr.attrMap[name] ) {
+ match[1] = Expr.attrMap[name];
+ }
+
+ // Handle if an un-quoted value was used
+ match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[4] = " " + match[4] + " ";
+ }
+
+ return match;
+ },
+
+ PSEUDO: function( match, curLoop, inplace, result, not ) {
+ if ( match[1] === "not" ) {
+ // If we're dealing with a complex expression, or a simple one
+ if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+ match[3] = Sizzle(match[3], null, null, curLoop);
+
+ } else {
+ var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+ if ( !inplace ) {
+ result.push.apply( result, ret );
+ }
+
+ return false;
+ }
+
+ } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+ return true;
+ }
+
+ return match;
+ },
+
+ POS: function( match ) {
+ match.unshift( true );
+
+ return match;
+ }
+ },
+
+ filters: {
+ enabled: function( elem ) {
+ return elem.disabled === false && elem.type !== "hidden";
+ },
+
+ disabled: function( elem ) {
+ return elem.disabled === true;
+ },
+
+ checked: function( elem ) {
+ return elem.checked === true;
+ },
+
+ selected: function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ parent: function( elem ) {
+ return !!elem.firstChild;
+ },
+
+ empty: function( elem ) {
+ return !elem.firstChild;
+ },
+
+ has: function( elem, i, match ) {
+ return !!Sizzle( match[3], elem ).length;
+ },
+
+ header: function( elem ) {
+ return (/h\d/i).test( elem.nodeName );
+ },
+
+ text: function( elem ) {
+ var attr = elem.getAttribute( "type" ), type = elem.type;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+ },
+
+ radio: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+ },
+
+ checkbox: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+ },
+
+ file: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+ },
+
+ password: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+ },
+
+ submit: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "submit" === elem.type;
+ },
+
+ image: function( elem ) {
+ return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+ },
+
+ reset: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && "reset" === elem.type;
+ },
+
+ button: function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && "button" === elem.type || name === "button";
+ },
+
+ input: function( elem ) {
+ return (/input|select|textarea|button/i).test( elem.nodeName );
+ },
+
+ focus: function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ }
+ },
+ setFilters: {
+ first: function( elem, i ) {
+ return i === 0;
+ },
+
+ last: function( elem, i, match, array ) {
+ return i === array.length - 1;
+ },
+
+ even: function( elem, i ) {
+ return i % 2 === 0;
+ },
+
+ odd: function( elem, i ) {
+ return i % 2 === 1;
+ },
+
+ lt: function( elem, i, match ) {
+ return i < match[3] - 0;
+ },
+
+ gt: function( elem, i, match ) {
+ return i > match[3] - 0;
+ },
+
+ nth: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ },
+
+ eq: function( elem, i, match ) {
+ return match[3] - 0 === i;
+ }
+ },
+ filter: {
+ PSEUDO: function( elem, match, i, array ) {
+ var name = match[1],
+ filter = Expr.filters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+
+ } else if ( name === "contains" ) {
+ return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+ } else if ( name === "not" ) {
+ var not = match[3];
+
+ for ( var j = 0, l = not.length; j < l; j++ ) {
+ if ( not[j] === elem ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ } else {
+ Sizzle.error( name );
+ }
+ },
+
+ CHILD: function( elem, match ) {
+ var first, last,
+ doneName, parent, cache,
+ count, diff,
+ type = match[1],
+ node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+
+ case "nth":
+ first = match[2];
+ last = match[3];
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ doneName = match[0];
+ parent = elem.parentNode;
+
+ if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+ count = 0;
+
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ node.nodeIndex = ++count;
+ }
+ }
+
+ parent[ expando ] = doneName;
+ }
+
+ diff = elem.nodeIndex - last;
+
+ if ( first === 0 ) {
+ return diff === 0;
+
+ } else {
+ return ( diff % first === 0 && diff / first >= 0 );
+ }
+ }
+ },
+
+ ID: function( elem, match ) {
+ return elem.nodeType === 1 && elem.getAttribute("id") === match;
+ },
+
+ TAG: function( elem, match ) {
+ return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+ },
+
+ CLASS: function( elem, match ) {
+ return (" " + (elem.className || elem.getAttribute("class")) + " ")
+ .indexOf( match ) > -1;
+ },
+
+ ATTR: function( elem, match ) {
+ var name = match[1],
+ result = Sizzle.attr ?
+ Sizzle.attr( elem, name ) :
+ Expr.attrHandle[ name ] ?
+ Expr.attrHandle[ name ]( elem ) :
+ elem[ name ] != null ?
+ elem[ name ] :
+ elem.getAttribute( name ),
+ value = result + "",
+ type = match[2],
+ check = match[4];
+
+ return result == null ?
+ type === "!=" :
+ !type && Sizzle.attr ?
+ result != null :
+ type === "=" ?
+ value === check :
+ type === "*=" ?
+ value.indexOf(check) >= 0 :
+ type === "~=" ?
+ (" " + value + " ").indexOf(check) >= 0 :
+ !check ?
+ value && result !== false :
+ type === "!=" ?
+ value !== check :
+ type === "^=" ?
+ value.indexOf(check) === 0 :
+ type === "$=" ?
+ value.substr(value.length - check.length) === check :
+ type === "|=" ?
+ value === check || value.substr(0, check.length + 1) === check + "-" :
+ false;
+ },
+
+ POS: function( elem, match, i, array ) {
+ var name = match[2],
+ filter = Expr.setFilters[ name ];
+
+ if ( filter ) {
+ return filter( elem, i, match, array );
+ }
+ }
+ }
+};
+
+var origPOS = Expr.match.POS,
+ fescape = function(all, num){
+ return "\\" + (num - 0 + 1);
+ };
+
+for ( var type in Expr.match ) {
+ Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+ Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+ array = Array.prototype.slice.call( array, 0 );
+
+ if ( results ) {
+ results.push.apply( results, array );
+ return results;
+ }
+
+ return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+ Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+ makeArray = function( array, results ) {
+ var i = 0,
+ ret = results || [];
+
+ if ( toString.call(array) === "[object Array]" ) {
+ Array.prototype.push.apply( ret, array );
+
+ } else {
+ if ( typeof array.length === "number" ) {
+ for ( var l = array.length; i < l; i++ ) {
+ ret.push( array[i] );
+ }
+
+ } else {
+ for ( ; array[i]; i++ ) {
+ ret.push( array[i] );
+ }
+ }
+ }
+
+ return ret;
+ };
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+ sortOrder = function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+ return a.compareDocumentPosition ? -1 : 1;
+ }
+
+ return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+ };
+
+} else {
+ sortOrder = function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+ siblingCheck = function( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+ // We're going to inject a fake input element with a specified name
+ var form = document.createElement("div"),
+ id = "script" + (new Date()).getTime(),
+ root = document.documentElement;
+
+ form.innerHTML = "<a name='" + id + "'/>";
+
+ // Inject it into the root element, check its status, and remove it quickly
+ root.insertBefore( form, root.firstChild );
+
+ // The workaround has to do additional checks after a getElementById
+ // Which slows things down for other browsers (hence the branching)
+ if ( document.getElementById( id ) ) {
+ Expr.find.ID = function( match, context, isXML ) {
+ if ( typeof context.getElementById !== "undefined" && !isXML ) {
+ var m = context.getElementById(match[1]);
+
+ return m ?
+ m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+ [m] :
+ undefined :
+ [];
+ }
+ };
+
+ Expr.filter.ID = function( elem, match ) {
+ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+ return elem.nodeType === 1 && node && node.nodeValue === match;
+ };
+ }
+
+ root.removeChild( form );
+
+ // release memory in IE
+ root = form = null;
+})();
+
+(function(){
+ // Check to see if the browser returns only elements
+ // when doing getElementsByTagName("*")
+
+ // Create a fake element
+ var div = document.createElement("div");
+ div.appendChild( document.createComment("") );
+
+ // Make sure no comments are found
+ if ( div.getElementsByTagName("*").length > 0 ) {
+ Expr.find.TAG = function( match, context ) {
+ var results = context.getElementsByTagName( match[1] );
+
+ // Filter out possible comments
+ if ( match[1] === "*" ) {
+ var tmp = [];
+
+ for ( var i = 0; results[i]; i++ ) {
+ if ( results[i].nodeType === 1 ) {
+ tmp.push( results[i] );
+ }
+ }
+
+ results = tmp;
+ }
+
+ return results;
+ };
+ }
+
+ // Check to see if an attribute returns normalized href attributes
+ div.innerHTML = "<a href='#'></a>";
+
+ if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+ div.firstChild.getAttribute("href") !== "#" ) {
+
+ Expr.attrHandle.href = function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ };
+ }
+
+ // release memory in IE
+ div = null;
+})();
+
+if ( document.querySelectorAll ) {
+ (function(){
+ var oldSizzle = Sizzle,
+ div = document.createElement("div"),
+ id = "__sizzle__";
+
+ div.innerHTML = "<p class='TEST'></p>";
+
+ // Safari can't handle uppercase or unicode characters when
+ // in quirks mode.
+ if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+ return;
+ }
+
+ Sizzle = function( query, context, extra, seed ) {
+ context = context || document;
+
+ // Only use querySelectorAll on non-XML documents
+ // (ID selectors don't work in non-HTML documents)
+ if ( !seed && !Sizzle.isXML(context) ) {
+ // See if we find a selector to speed up
+ var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+ if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+ // Speed-up: Sizzle("TAG")
+ if ( match[1] ) {
+ return makeArray( context.getElementsByTagName( query ), extra );
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+ return makeArray( context.getElementsByClassName( match[2] ), extra );
+ }
+ }
+
+ if ( context.nodeType === 9 ) {
+ // Speed-up: Sizzle("body")
+ // The body element only exists once, optimize finding it
+ if ( query === "body" && context.body ) {
+ return makeArray( [ context.body ], extra );
+
+ // Speed-up: Sizzle("#ID")
+ } else if ( match && match[3] ) {
+ var elem = context.getElementById( match[3] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id === match[3] ) {
+ return makeArray( [ elem ], extra );
+ }
+
+ } else {
+ return makeArray( [], extra );
+ }
+ }
+
+ try {
+ return makeArray( context.querySelectorAll(query), extra );
+ } catch(qsaError) {}
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ var oldContext = context,
+ old = context.getAttribute( "id" ),
+ nid = old || id,
+ hasParent = context.parentNode,
+ relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+ if ( !old ) {
+ context.setAttribute( "id", nid );
+ } else {
+ nid = nid.replace( /'/g, "\\$&" );
+ }
+ if ( relativeHierarchySelector && hasParent ) {
+ context = context.parentNode;
+ }
+
+ try {
+ if ( !relativeHierarchySelector || hasParent ) {
+ return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+ }
+
+ } catch(pseudoError) {
+ } finally {
+ if ( !old ) {
+ oldContext.removeAttribute( "id" );
+ }
+ }
+ }
+ }
+
+ return oldSizzle(query, context, extra, seed);
+ };
+
+ for ( var prop in oldSizzle ) {
+ Sizzle[ prop ] = oldSizzle[ prop ];
+ }
+
+ // release memory in IE
+ div = null;
+ })();
+}
+
+(function(){
+ var html = document.documentElement,
+ matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+ if ( matches ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9 fails this)
+ var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+ pseudoWorks = false;
+
+ try {
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ matches.call( document.documentElement, "[test!='']:sizzle" );
+
+ } catch( pseudoError ) {
+ pseudoWorks = true;
+ }
+
+ Sizzle.matchesSelector = function( node, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+ if ( !Sizzle.isXML( node ) ) {
+ try {
+ if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+ var ret = matches.call( node, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || !disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9, so check for that
+ node.document && node.document.nodeType !== 11 ) {
+ return ret;
+ }
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle(expr, null, null, [node]).length > 0;
+ };
+ }
+})();
+
+(function(){
+ var div = document.createElement("div");
+
+ div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+ // Opera can't find a second classname (in 9.6)
+ // Also, make sure that getElementsByClassName actually exists
+ if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+ return;
+ }
+
+ // Safari caches class attributes, doesn't catch changes (in 3.2)
+ div.lastChild.className = "e";
+
+ if ( div.getElementsByClassName("e").length === 1 ) {
+ return;
+ }
+
+ Expr.order.splice(1, 0, "CLASS");
+ Expr.find.CLASS = function( match, context, isXML ) {
+ if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+ return context.getElementsByClassName(match[1]);
+ }
+ };
+
+ // release memory in IE
+ div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 && !isXML ){
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( elem.nodeName.toLowerCase() === cur ) {
+ match = elem;
+ break;
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+ for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+ var elem = checkSet[i];
+
+ if ( elem ) {
+ var match = false;
+
+ elem = elem[dir];
+
+ while ( elem ) {
+ if ( elem[ expando ] === doneName ) {
+ match = checkSet[elem.sizset];
+ break;
+ }
+
+ if ( elem.nodeType === 1 ) {
+ if ( !isXML ) {
+ elem[ expando ] = doneName;
+ elem.sizset = i;
+ }
+
+ if ( typeof cur !== "string" ) {
+ if ( elem === cur ) {
+ match = true;
+ break;
+ }
+
+ } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+ match = elem;
+ break;
+ }
+ }
+
+ elem = elem[dir];
+ }
+
+ checkSet[i] = match;
+ }
+ }
+}
+
+if ( document.documentElement.contains ) {
+ Sizzle.contains = function( a, b ) {
+ return a !== b && (a.contains ? a.contains(b) : true);
+ };
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+ Sizzle.contains = function( a, b ) {
+ return !!(a.compareDocumentPosition(b) & 16);
+ };
+
+} else {
+ Sizzle.contains = function() {
+ return false;
+ };
+}
+
+Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+ var match,
+ tmpSet = [],
+ later = "",
+ root = context.nodeType ? [context] : context;
+
+ // Position selectors must be done after the filter
+ // And so must :not(positional) so we move all PSEUDOs to the end
+ while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+ later += match[0];
+ selector = selector.replace( Expr.match.PSEUDO, "" );
+ }
+
+ selector = Expr.relative[selector] ? selector + "*" : selector;
+
+ for ( var i = 0, l = root.length; i < l; i++ ) {
+ Sizzle( selector, root[i], tmpSet, seed );
+ }
+
+ return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+ // Note: This RegExp should be improved, or likely pulled from Sizzle
+ rmultiselector = /,/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ slice = Array.prototype.slice,
+ POS = jQuery.expr.match.POS,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var self = this,
+ i, l;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ var ret = this.pushStack( "", "find", selector ),
+ length, n, r;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var targets = jQuery( target );
+ return this.filter(function() {
+ for ( var i = 0, l = targets.length; i < l; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ POS.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var ret = [], i, l, cur = this[0];
+
+ // Array (deprecated as of jQuery 1.7)
+ if ( jQuery.isArray( selectors ) ) {
+ var level = 1;
+
+ while ( cur && cur.ownerDocument && cur !== context ) {
+ for ( i = 0; i < selectors.length; i++ ) {
+
+ if ( jQuery( cur ).is( selectors[ i ] ) ) {
+ ret.push({ selector: selectors[ i ], elem: cur, level: level });
+ }
+ }
+
+ cur = cur.parentNode;
+ level++;
+ }
+
+ return ret;
+ }
+
+ // String
+ var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+
+ } else {
+ cur = cur.parentNode;
+ if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+ break;
+ }
+ }
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ andSelf: function() {
+ return this.add( this.prevObject );
+ }
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return jQuery.nth( elem, 2, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return jQuery.nth( elem, 2, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( elem.parentNode.firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.makeArray( elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ nth: function( cur, result, dir, elem ) {
+ result = result || 1;
+ var num = 0;
+
+ for ( ; cur; cur = cur[dir] ) {
+ if ( cur.nodeType === 1 && ++num === result ) {
+ break;
+ }
+ }
+
+ return cur;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+
+
+
+
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnoInnerhtml = /<(?:script|style)/i,
+ rnocache = /<(?:script|object|embed|option|style)/i,
+ rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"),
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( text ) {
+ if ( jQuery.isFunction(text) ) {
+ return this.each(function(i) {
+ var self = jQuery( this );
+
+ self.text( text.call(this, i, self.text()) );
+ });
+ }
+
+ if ( typeof text !== "object" && text !== undefined ) {
+ return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+ }
+
+ return jQuery.text( this );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ } else if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ set.push.apply( set, this.toArray() );
+ return this.pushStack( set, "before", arguments );
+ }
+ },
+
+ after: function() {
+ if ( this[0] && this[0].parentNode ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ } else if ( arguments.length ) {
+ var set = this.pushStack( this, "after", arguments );
+ set.push.apply( set, jQuery.clean(arguments) );
+ return set;
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ if ( value === undefined ) {
+ return this[0] && this[0].nodeType === 1 ?
+ this[0].innerHTML.replace(rinlinejQuery, "") :
+ null;
+
+ // See if we can take a shortcut and just use innerHTML
+ } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+ !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+ value = value.replace(rxhtmlTag, "<$1></$2>");
+
+ try {
+ for ( var i = 0, l = this.length; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( this[i].nodeType === 1 ) {
+ jQuery.cleanData( this[i].getElementsByTagName("*") );
+ this[i].innerHTML = value;
+ }
+ }
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {
+ this.empty().append( value );
+ }
+
+ } else if ( jQuery.isFunction( value ) ) {
+ this.each(function(i){
+ var self = jQuery( this );
+
+ self.html( value.call(this, i, self.html()) );
+ });
+
+ } else {
+ this.empty().append( value );
+ }
+
+ return this;
+ },
+
+ replaceWith: function( value ) {
+ if ( this[0] && this[0].parentNode ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ } else {
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ }
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+ var results, first, fragment, parent,
+ value = args[0],
+ scripts = [];
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback, true );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call(this, i, table ? self.html() : undefined);
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ parent = value && value.parentNode;
+
+ // If we're in a fragment, just use that instead of building a new one
+ if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+ results = { fragment: parent };
+
+ } else {
+ results = jQuery.buildFragment( args, this, scripts );
+ }
+
+ fragment = results.fragment;
+
+ if ( fragment.childNodes.length === 1 ) {
+ first = fragment = fragment.firstChild;
+ } else {
+ first = fragment.firstChild;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+ callback.call(
+ table ?
+ root(this[i], first) :
+ this[i],
+ // Make sure that we do not leak memory by inadvertently discarding
+ // the original fragment (which might have attached data) instead of
+ // using it; in addition, use the original fragment object for the last
+ // item instead of first because it can end up being emptied incorrectly
+ // in certain situations (Bug #8070).
+ // Fragments from the fragment cache must always be cloned and never used
+ // in place.
+ results.cacheable || ( l > 1 && i < lastIndex ) ?
+ jQuery.clone( fragment, true, true ) :
+ fragment
+ );
+ }
+ }
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, evalScript );
+ }
+ }
+
+ return this;
+ }
+});
+
+function root( elem, cur ) {
+ return jQuery.nodeName(elem, "table") ?
+ (elem.getElementsByTagName("tbody")[0] ||
+ elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+ elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ // IE6-8 fail to clone children inside object elements that use
+ // the proprietary classid attribute value (rather than the type
+ // attribute) to identify the type of content to display
+ if ( nodeName === "object" ) {
+ dest.outerHTML = src.outerHTML;
+
+ } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+ if ( src.checked ) {
+ dest.defaultChecked = dest.checked = src.checked;
+ }
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+ var fragment, cacheable, cacheresults, doc,
+ first = args[ 0 ];
+
+ // nodes may contain either an explicit document object,
+ // a jQuery collection or context object.
+ // If nodes[0] contains a valid object to assign to doc
+ if ( nodes && nodes[0] ) {
+ doc = nodes[0].ownerDocument || nodes[0];
+ }
+
+ // Ensure that an attr object doesn't incorrectly stand in as a document object
+ // Chrome and Firefox seem to allow this to occur and will throw exception
+ // Fixes #8950
+ if ( !doc.createDocumentFragment ) {
+ doc = document;
+ }
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+ if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
+ first.charAt(0) === "<" && !rnocache.test( first ) &&
+ (jQuery.support.checkClone || !rchecked.test( first )) &&
+ (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+ cacheable = true;
+
+ cacheresults = jQuery.fragments[ first ];
+ if ( cacheresults && cacheresults !== 1 ) {
+ fragment = cacheresults;
+ }
+ }
+
+ if ( !fragment ) {
+ fragment = doc.createDocumentFragment();
+ jQuery.clean( args, doc, fragment, scripts );
+ }
+
+ if ( cacheable ) {
+ jQuery.fragments[ first ] = cacheresults ? fragment : 1;
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [],
+ insert = jQuery( selector ),
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+
+ } else {
+ for ( var i = 0, l = insert.length; i < l; i++ ) {
+ var elems = ( i > 0 ? this.clone(true) : this ).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( elem.type === "checkbox" || elem.type === "radio" ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+ var nodeName = ( elem.nodeName || "" ).toLowerCase();
+ if ( nodeName === "input" ) {
+ fixDefaultChecked( elem );
+ // Skip scripts, get other children
+ } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+}
+
+// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
+function shimCloneNode( elem ) {
+ var div = document.createElement( "div" );
+ safeFragment.appendChild( div );
+
+ div.innerHTML = elem.outerHTML;
+ return div.firstChild;
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var srcElements,
+ destElements,
+ i,
+ // IE<=8 does not properly clone detached, unknown element nodes
+ clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ?
+ elem.cloneNode( true ) :
+ shimCloneNode( elem );
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var checkScriptType;
+
+ context = context || document;
+
+ // !context.createElement fails in IE with an error but returns typeof 'object'
+ if ( typeof context.createElement === "undefined" ) {
+ context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+ }
+
+ var ret = [], j;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+ // Trim whitespace, otherwise indexOf won't work as expected
+ var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
+ wrap = wrapMap[ tag ] || wrapMap._default,
+ depth = wrap[0],
+ div = context.createElement("div");
+
+ // Append wrapper element to unknown element safe doc fragment
+ if ( context === document ) {
+ // Use the fragment we've already created for this document
+ safeFragment.appendChild( div );
+ } else {
+ // Use a fragment created with the owner document
+ createSafeFragment( context ).appendChild( div );
+ }
+
+ // Go to html and back, then peel off extra wrappers
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ var hasBody = rtbody.test(elem),
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+ }
+ }
+
+ // Resets defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ var len;
+ if ( !jQuery.support.appendChecked ) {
+ if ( elem[0] && typeof (len = elem.length) === "number" ) {
+ for ( j = 0; j < len; j++ ) {
+ findInputs( elem[j] );
+ }
+ } else {
+ findInputs( elem );
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ ret = jQuery.merge( ret, elem );
+ }
+ }
+
+ if ( fragment ) {
+ checkScriptType = function( elem ) {
+ return !elem.type || rscriptType.test( elem.type );
+ };
+ for ( i = 0; ret[i]; i++ ) {
+ if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+ scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+ } else {
+ if ( ret[i].nodeType === 1 ) {
+ var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );
+
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ }
+ fragment.appendChild( ret[i] );
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems ) {
+ var data, id,
+ cache = jQuery.cache,
+ special = jQuery.event.special,
+ deleteExpando = jQuery.support.deleteExpando;
+
+ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+ if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+ continue;
+ }
+
+ id = elem[ jQuery.expando ];
+
+ if ( id ) {
+ data = cache[ id ];
+
+ if ( data && data.events ) {
+ for ( var type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+
+ // Null the DOM reference to avoid IE6/7/8 leak (#7054)
+ if ( data.handle ) {
+ data.handle.elem = null;
+ }
+ }
+
+ if ( deleteExpando ) {
+ delete elem[ jQuery.expando ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( jQuery.expando );
+ }
+
+ delete cache[ id ];
+ }
+ }
+ }
+});
+
+function evalScript( i, elem ) {
+ if ( elem.src ) {
+ jQuery.ajax({
+ url: elem.src,
+ async: false,
+ dataType: "script"
+ });
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+}
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ // fixed for IE9, see #8346
+ rupper = /([A-Z]|^ms)/g,
+ rnumpx = /^-?\d+(?:px)?$/i,
+ rnum = /^-?\d/,
+ rrelNum = /^([\-+])=([\-+.\de]+)/,
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssWidth = [ "Left", "Right" ],
+ cssHeight = [ "Top", "Bottom" ],
+ curCSS,
+
+ getComputedStyle,
+ currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+ // Setting 'undefined' is a no-op
+ if ( arguments.length === 2 && value === undefined ) {
+ return this;
+ }
+
+ return jQuery.access( this, name, value, true, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ });
+};
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity", "opacity" );
+ return ret === "" ? "1" : ret;
+
+ } else {
+ return elem.style.opacity;
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, origName = jQuery.camelCase( name ),
+ style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+ name = jQuery.cssProps[ origName ] || origName;
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, extra ) {
+ var ret, hooks;
+
+ // Make sure that we're working with the right name
+ name = jQuery.camelCase( name );
+ hooks = jQuery.cssHooks[ name ];
+ name = jQuery.cssProps[ name ] || name;
+
+ // cssFloat needs a special treatment
+ if ( name === "cssFloat" ) {
+ name = "float";
+ }
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+ return ret;
+
+ // Otherwise, if a way to get the computed value exists, use that
+ } else if ( curCSS ) {
+ return curCSS( elem, name );
+ }
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( var name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+ }
+});
+
+// DEPRECATED, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+jQuery.each(["height", "width"], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ var val;
+
+ if ( computed ) {
+ if ( elem.offsetWidth !== 0 ) {
+ return getWH( elem, name, extra );
+ } else {
+ jQuery.swap( elem, cssShow, function() {
+ val = getWH( elem, name, extra );
+ });
+ }
+
+ return val;
+ }
+ },
+
+ set: function( elem, value ) {
+ if ( rnumpx.test( value ) ) {
+ // ignore negative width and height values #1599
+ value = parseFloat( value );
+
+ if ( value >= 0 ) {
+ return value + "px";
+ }
+
+ } else {
+ return value;
+ }
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( parseFloat( RegExp.$1 ) / 100 ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+jQuery(function() {
+ // This hook cannot be added until DOM ready because the support test
+ // for it is not run until after DOM ready
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ var ret;
+ jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ ret = curCSS( elem, "margin-right", "marginRight" );
+ } else {
+ ret = elem.style.marginRight;
+ }
+ });
+ return ret;
+ }
+ };
+ }
+});
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+ getComputedStyle = function( elem, name ) {
+ var ret, defaultView, computedStyle;
+
+ name = name.replace( rupper, "-$1" ).toLowerCase();
+
+ if ( (defaultView = elem.ownerDocument.defaultView) &&
+ (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+ ret = computedStyle.getPropertyValue( name );
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+ }
+
+ return ret;
+ };
+}
+
+if ( document.documentElement.currentStyle ) {
+ currentStyle = function( elem, name ) {
+ var left, rsLeft, uncomputed,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret === null && style && (uncomputed = style[ name ]) ) {
+ ret = uncomputed;
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ( ret || 0 );
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWH( elem, name, extra ) {
+
+ // Start with offset property
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ which = name === "width" ? cssWidth : cssHeight,
+ i = 0,
+ len = which.length;
+
+ if ( val > 0 ) {
+ if ( extra !== "border" ) {
+ for ( ; i < len; i++ ) {
+ if ( !extra ) {
+ val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
+ } else {
+ val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val + "px";
+ }
+
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ] || 0;
+ }
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+
+ // Add padding, border, margin
+ if ( extra ) {
+ for ( ; i < len; i++ ) {
+ val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
+ if ( extra !== "padding" ) {
+ val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
+ }
+ if ( extra === "margin" ) {
+ val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
+ }
+ }
+ }
+
+ return val + "px";
+}
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ var width = elem.offsetWidth,
+ height = elem.offsetHeight;
+
+ return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+
+
+
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ rselectTextarea = /^(?:select|textarea)/i,
+ rspacesAjax = /\s+/,
+ rts = /([?&])_=[^&]*/,
+ rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Document location
+ ajaxLocation,
+
+ // Document location segments
+ ajaxLocParts,
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ if ( jQuery.isFunction( func ) ) {
+ var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+ i = 0,
+ length = dataTypes.length,
+ dataType,
+ list,
+ placeBefore;
+
+ // For each dataType in the dataTypeExpression
+ for ( ; i < length; i++ ) {
+ dataType = dataTypes[ i ];
+ // We control if we're asked to add before
+ // any existing element
+ placeBefore = /^\+/.test( dataType );
+ if ( placeBefore ) {
+ dataType = dataType.substr( 1 ) || "*";
+ }
+ list = structure[ dataType ] = structure[ dataType ] || [];
+ // then we add to the structure accordingly
+ list[ placeBefore ? "unshift" : "push" ]( func );
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+ dataType /* internal */, inspected /* internal */ ) {
+
+ dataType = dataType || options.dataTypes[ 0 ];
+ inspected = inspected || {};
+
+ inspected[ dataType ] = true;
+
+ var list = structure[ dataType ],
+ i = 0,
+ length = list ? list.length : 0,
+ executeOnly = ( structure === prefilters ),
+ selection;
+
+ for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+ selection = list[ i ]( options, originalOptions, jqXHR );
+ // If we got redirected to another dataType
+ // we try there if executing only and not done already
+ if ( typeof selection === "string" ) {
+ if ( !executeOnly || inspected[ selection ] ) {
+ selection = undefined;
+ } else {
+ options.dataTypes.unshift( selection );
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, selection, inspected );
+ }
+ }
+ }
+ // If we're only executing or nothing was selected
+ // we try the catchall dataType if not done already
+ if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, "*", inspected );
+ }
+ // unnecessary when only executing (prefilters)
+ // but it'll be ignored by the caller in that case
+ return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+}
+
+jQuery.fn.extend({
+ load: function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+
+ // Don't do a request if no elements are being requested
+ } else if ( !this.length ) {
+ return this;
+ }
+
+ var off = url.indexOf( " " );
+ if ( off >= 0 ) {
+ var selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // Default to a GET request
+ var type = "GET";
+
+ // If the second parameter was provided
+ if ( params ) {
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( typeof params === "object" ) {
+ params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+ type = "POST";
+ }
+ }
+
+ var self = this;
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+ type: type,
+ dataType: "html",
+ data: params,
+ // Complete callback (responseText is used internally)
+ complete: function( jqXHR, status, responseText ) {
+ // Store the response as specified by the jqXHR object
+ responseText = jqXHR.responseText;
+ // If successful, inject the HTML into all the matched elements
+ if ( jqXHR.isResolved() ) {
+ // #4825: Get the actual response in case
+ // a dataFilter is present in ajaxSettings
+ jqXHR.done(function( r ) {
+ responseText = r;
+ });
+ // See if a selector was specified
+ self.html( selector ?
+ // Create a dummy div to hold the results
+ jQuery("<div>")
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append(responseText.replace(rscript, ""))
+
+ // Locate the specified elements
+ .find(selector) :
+
+ // If not, just inject the full result
+ responseText );
+ }
+
+ if ( callback ) {
+ self.each( callback, [ responseText, status, jqXHR ] );
+ }
+ }
+ });
+
+ return this;
+ },
+
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+ jQuery.fn[ o ] = function( f ){
+ return this.on( o, f );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ type: method,
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ };
+});
+
+jQuery.extend({
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ if ( settings ) {
+ // Building a settings object
+ ajaxExtend( target, jQuery.ajaxSettings );
+ } else {
+ // Extending ajaxSettings
+ settings = target;
+ target = jQuery.ajaxSettings;
+ }
+ ajaxExtend( target, settings );
+ return target;
+ },
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ text: "text/plain",
+ json: "application/json, text/javascript",
+ "*": allTypes
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // List of data converters
+ // 1) key format is "source_type destination_type" (a single space in-between)
+ // 2) the catchall symbol "*" can be used for source_type
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ context: true,
+ url: true
+ }
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events
+ // It's the callbackContext if one was provided in the options
+ // and if it's a DOM node or a jQuery collection
+ globalEventContext = callbackContext !== s &&
+ ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+ jQuery( callbackContext ) : jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks( "once memory" ),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // ifModified key
+ ifModifiedKey,
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // transport
+ transport,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // The jqXHR state
+ state = 0,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Fake xhr
+ jqXHR = {
+
+ readyState: 0,
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( !state ) {
+ var lname = name.toLowerCase();
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match === undefined ? null : match;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ statusText = statusText || "abort";
+ if ( transport ) {
+ transport.abort( statusText );
+ }
+ done( 0, statusText );
+ return this;
+ }
+ };
+
+ // Callback for when everything is done
+ // It is defined here because jslint complains if it is declared
+ // at the end of the function (which would be more logical and readable)
+ function done( status, nativeStatusText, responses, headers ) {
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ var isSuccess,
+ success,
+ error,
+ statusText = nativeStatusText,
+ response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+ lastModified,
+ etag;
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+
+ if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+ jQuery.lastModified[ ifModifiedKey ] = lastModified;
+ }
+ if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+ jQuery.etag[ ifModifiedKey ] = etag;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+
+ statusText = "notmodified";
+ isSuccess = true;
+
+ // If we have data
+ } else {
+
+ try {
+ success = ajaxConvert( s, response );
+ statusText = "success";
+ isSuccess = true;
+ } catch(e) {
+ // We have a parsererror
+ statusText = "parsererror";
+ error = e;
+ }
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( !statusText || status ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+ jqXHR.complete = completeDeferred.add;
+
+ // Status-dependent callbacks
+ jqXHR.statusCode = function( map ) {
+ if ( map ) {
+ var tmp;
+ if ( state < 2 ) {
+ for ( tmp in map ) {
+ statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+ }
+ } else {
+ tmp = map[ jqXHR.status ];
+ jqXHR.then( tmp, tmp );
+ }
+ }
+ return this;
+ };
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+ // Determine if a cross-domain request is in order
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefiler, stop there
+ if ( state === 2 ) {
+ return false;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Get ifModifiedKey before adding the anti-cache parameter
+ ifModifiedKey = s.url;
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+
+ var ts = jQuery.now(),
+ // try replacing _= if it is there
+ ret = s.url.replace( rts, "$1_=" + ts );
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ ifModifiedKey = ifModifiedKey || s.url;
+ if ( jQuery.lastModified[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+ }
+ if ( jQuery.etag[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+ }
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already
+ jqXHR.abort();
+ return false;
+
+ }
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout( function(){
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch (e) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ // Serialize an array of form elements or a set of
+ // key/values into a query string
+ param: function( a, traditional ) {
+ var s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : value;
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( var prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+ }
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && obj != null && typeof obj === "object" ) {
+ // Serialize object item.
+ for ( var name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields,
+ ct,
+ type,
+ finalDataType,
+ firstDataType;
+
+ // Fill responseXXX fields
+ for ( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ var dataTypes = s.dataTypes,
+ converters = {},
+ i,
+ key,
+ length = dataTypes.length,
+ tmp,
+ // Current and previous dataTypes
+ current = dataTypes[ 0 ],
+ prev,
+ // Conversion expression
+ conversion,
+ // Conversion function
+ conv,
+ // Conversion functions (transitive conversion)
+ conv1,
+ conv2;
+
+ // For each dataType in the chain
+ for ( i = 1; i < length; i++ ) {
+
+ // Create converters map
+ // with lowercased keys
+ if ( i === 1 ) {
+ for ( key in s.converters ) {
+ if ( typeof key === "string" ) {
+ converters[ key.toLowerCase() ] = s.converters[ key ];
+ }
+ }
+ }
+
+ // Get the dataTypes
+ prev = current;
+ current = dataTypes[ i ];
+
+ // If current is auto dataType, update it to prev
+ if ( current === "*" ) {
+ current = prev;
+ // If no auto and dataTypes are actually different
+ } else if ( prev !== "*" && prev !== current ) {
+
+ // Get the converter
+ conversion = prev + " " + current;
+ conv = converters[ conversion ] || converters[ "* " + current ];
+
+ // If there is no direct converter, search transitively
+ if ( !conv ) {
+ conv2 = undefined;
+ for ( conv1 in converters ) {
+ tmp = conv1.split( " " );
+ if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+ conv2 = converters[ tmp[1] + " " + current ];
+ if ( conv2 ) {
+ conv1 = converters[ conv1 ];
+ if ( conv1 === true ) {
+ conv = conv2;
+ } else if ( conv2 === true ) {
+ conv = conv1;
+ }
+ break;
+ }
+ }
+ }
+ }
+ // If we found no converter, dispatch an error
+ if ( !( conv || conv2 ) ) {
+ jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+ }
+ // If found converter is not an equivalence
+ if ( conv !== true ) {
+ // Convert with 1 or 2 converters accordingly
+ response = conv ? conv( response ) : conv2( conv1(response) );
+ }
+ }
+ }
+ return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+ jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ return jQuery.expando + "_" + ( jsc++ );
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
+ ( typeof s.data === "string" );
+
+ if ( s.dataTypes[ 0 ] === "jsonp" ||
+ s.jsonp !== false && ( jsre.test( s.url ) ||
+ inspectData && jsre.test( s.data ) ) ) {
+
+ var responseContainer,
+ jsonpCallback = s.jsonpCallback =
+ jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+ previous = window[ jsonpCallback ],
+ url = s.url,
+ data = s.data,
+ replace = "$1" + jsonpCallback + "$2";
+
+ if ( s.jsonp !== false ) {
+ url = url.replace( jsre, replace );
+ if ( s.url === url ) {
+ if ( inspectData ) {
+ data = data.replace( jsre, replace );
+ }
+ if ( s.data === data ) {
+ // Add callback manually
+ url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+ }
+ }
+ }
+
+ s.url = url;
+ s.data = data;
+
+ // Install callback
+ window[ jsonpCallback ] = function( response ) {
+ responseContainer = [ response ];
+ };
+
+ // Clean-up function
+ jqXHR.always(function() {
+ // Set callback back to previous value
+ window[ jsonpCallback ] = previous;
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( previous ) ) {
+ window[ jsonpCallback ]( responseContainer[ 0 ] );
+ }
+ });
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( jsonpCallback + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Delegate to script
+ return "script";
+ }
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /javascript|ecmascript/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( 0, 1 );
+ }
+ }
+ };
+ }
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject ? function() {
+ // Abort all pending requests
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( 0, 1 );
+ }
+ } : false,
+ xhrId = 0,
+ xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+ jQuery.extend( jQuery.support, {
+ ajax: !!xhr,
+ cors: !!xhr && ( "withCredentials" in xhr )
+ });
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var xhr = s.xhr(),
+ handle,
+ i;
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( _ ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occured
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:...
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+ responses = {};
+ xml = xhr.responseXML;
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+ responses.text = xhr.responseText;
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ // if we're in sync mode or it's in cache
+ // and has been retrieved directly (IE6 & IE7)
+ // we need to manually fire the callback
+ if ( !s.async || xhr.readyState === 4 ) {
+ callback();
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback(0,1);
+ }
+ }
+ };
+ }
+ });
+}
+
+
+
+
+var elemdisplay = {},
+ iframe, iframeDoc,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+ timerId,
+ fxAttrs = [
+ // height animations
+ [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+ // width animations
+ [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+ // opacity animations
+ [ "opacity" ]
+ ],
+ fxNow;
+
+jQuery.fn.extend({
+ show: function( speed, easing, callback ) {
+ var elem, display;
+
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("show", 3), speed, easing, callback );
+
+ } else {
+ for ( var i = 0, j = this.length; i < j; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.style ) {
+ display = elem.style.display;
+
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+ display = elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( display === "" && jQuery.css(elem, "display") === "none" ) {
+ jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.style ) {
+ display = elem.style.display;
+
+ if ( display === "" || display === "none" ) {
+ elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
+ }
+ }
+ }
+
+ return this;
+ }
+ },
+
+ hide: function( speed, easing, callback ) {
+ if ( speed || speed === 0 ) {
+ return this.animate( genFx("hide", 3), speed, easing, callback);
+
+ } else {
+ var elem, display,
+ i = 0,
+ j = this.length;
+
+ for ( ; i < j; i++ ) {
+ elem = this[i];
+ if ( elem.style ) {
+ display = jQuery.css( elem, "display" );
+
+ if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
+ jQuery._data( elem, "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of the elements in a second loop
+ // to avoid the constant reflow
+ for ( i = 0; i < j; i++ ) {
+ if ( this[i].style ) {
+ this[i].style.display = "none";
+ }
+ }
+
+ return this;
+ }
+ },
+
+ // Save the old toggle function
+ _toggle: jQuery.fn.toggle,
+
+ toggle: function( fn, fn2, callback ) {
+ var bool = typeof fn === "boolean";
+
+ if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+ this._toggle.apply( this, arguments );
+
+ } else if ( fn == null || bool ) {
+ this.each(function() {
+ var state = bool ? fn : jQuery(this).is(":hidden");
+ jQuery(this)[ state ? "show" : "hide" ]();
+ });
+
+ } else {
+ this.animate(genFx("toggle", 3), fn, fn2, callback);
+ }
+
+ return this;
+ },
+
+ fadeTo: function( speed, to, easing, callback ) {
+ return this.filter(":hidden").css("opacity", 0).show().end()
+ .animate({opacity: to}, speed, easing, callback);
+ },
+
+ animate: function( prop, speed, easing, callback ) {
+ var optall = jQuery.speed( speed, easing, callback );
+
+ if ( jQuery.isEmptyObject( prop ) ) {
+ return this.each( optall.complete, [ false ] );
+ }
+
+ // Do not change referenced properties as per-property easing will be lost
+ prop = jQuery.extend( {}, prop );
+
+ function doAnimation() {
+ // XXX 'this' does not always have a nodeName when running the
+ // test suite
+
+ if ( optall.queue === false ) {
+ jQuery._mark( this );
+ }
+
+ var opt = jQuery.extend( {}, optall ),
+ isElement = this.nodeType === 1,
+ hidden = isElement && jQuery(this).is(":hidden"),
+ name, val, p, e,
+ parts, start, end, unit,
+ method;
+
+ // will store per property easing and be used to determine when an animation is complete
+ opt.animatedProperties = {};
+
+ for ( p in prop ) {
+
+ // property name normalization
+ name = jQuery.camelCase( p );
+ if ( p !== name ) {
+ prop[ name ] = prop[ p ];
+ delete prop[ p ];
+ }
+
+ val = prop[ name ];
+
+ // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+ if ( jQuery.isArray( val ) ) {
+ opt.animatedProperties[ name ] = val[ 1 ];
+ val = prop[ name ] = val[ 0 ];
+ } else {
+ opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+ }
+
+ if ( val === "hide" && hidden || val === "show" && !hidden ) {
+ return opt.complete.call( this );
+ }
+
+ if ( isElement && ( name === "height" || name === "width" ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( this, "display" ) === "inline" &&
+ jQuery.css( this, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
+ this.style.display = "inline-block";
+
+ } else {
+ this.style.zoom = 1;
+ }
+ }
+ }
+ }
+
+ if ( opt.overflow != null ) {
+ this.style.overflow = "hidden";
+ }
+
+ for ( p in prop ) {
+ e = new jQuery.fx( this, opt, p );
+ val = prop[ p ];
+
+ if ( rfxtypes.test( val ) ) {
+
+ // Tracks whether to show or hide based on private
+ // data attached to the element
+ method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
+ if ( method ) {
+ jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
+ e[ method ]();
+ } else {
+ e[ val ]();
+ }
+
+ } else {
+ parts = rfxnum.exec( val );
+ start = e.cur();
+
+ if ( parts ) {
+ end = parseFloat( parts[2] );
+ unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" ) {
+ jQuery.style( this, p, (end || 1) + unit);
+ start = ( (end || 1) / e.cur() ) * start;
+ jQuery.style( this, p, start + unit);
+ }
+
+ // If a +=/-= token was provided, we're doing a relative animation
+ if ( parts[1] ) {
+ end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+ }
+
+ e.custom( start, end, unit );
+
+ } else {
+ e.custom( start, val, "" );
+ }
+ }
+ }
+
+ // For JS strict compliance
+ return true;
+ }
+
+ return optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+
+ stop: function( type, clearQueue, gotoEnd ) {
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var index,
+ hadTimers = false,
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ // clear marker counters if we know they won't be
+ if ( !gotoEnd ) {
+ jQuery._unmark( true, this );
+ }
+
+ function stopQueue( elem, data, index ) {
+ var hooks = data[ index ];
+ jQuery.removeData( elem, index, true );
+ hooks.stop( gotoEnd );
+ }
+
+ if ( type == null ) {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
+ stopQueue( this, data, index );
+ }
+ }
+ } else if ( data[ index = type + ".run" ] && data[ index ].stop ){
+ stopQueue( this, data, index );
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ if ( gotoEnd ) {
+
+ // force the next step to be the last
+ timers[ index ]( true );
+ } else {
+ timers[ index ].saveState();
+ }
+ hadTimers = true;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( !( gotoEnd && hadTimers ) ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ }
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout( clearFxNow, 0 );
+ return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+ fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+ var obj = {};
+
+ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
+ obj[ this ] = type;
+ });
+
+ return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx( "show", 1 ),
+ slideUp: genFx( "hide", 1 ),
+ slideToggle: genFx( "toggle", 1 ),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.extend({
+ speed: function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function( noUnmark ) {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ } else if ( noUnmark !== false ) {
+ jQuery._unmark( this );
+ }
+ };
+
+ return opt;
+ },
+
+ easing: {
+ linear: function( p, n, firstNum, diff ) {
+ return firstNum + diff * p;
+ },
+ swing: function( p, n, firstNum, diff ) {
+ return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum;
+ }
+ },
+
+ timers: [],
+
+ fx: function( elem, options, prop ) {
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+
+ options.orig = options.orig || {};
+ }
+
+});
+
+jQuery.fx.prototype = {
+ // Simple function for setting a style value
+ update: function() {
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
+ },
+
+ // Get the current size
+ cur: function() {
+ if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
+ return this.elem[ this.prop ];
+ }
+
+ var parsed,
+ r = jQuery.css( this.elem, this.prop );
+ // Empty strings, null, undefined and "auto" are converted to 0,
+ // complex values such as "rotate(1rad)" are returned as is,
+ // simple values such as "10px" are parsed to Float.
+ return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+ },
+
+ // Start an animation from one number to another
+ custom: function( from, to, unit ) {
+ var self = this,
+ fx = jQuery.fx;
+
+ this.startTime = fxNow || createFxNow();
+ this.end = to;
+ this.now = this.start = from;
+ this.pos = this.state = 0;
+ this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+
+ function t( gotoEnd ) {
+ return self.step( gotoEnd );
+ }
+
+ t.queue = this.options.queue;
+ t.elem = this.elem;
+ t.saveState = function() {
+ if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
+ jQuery._data( self.elem, "fxshow" + self.prop, self.start );
+ }
+ };
+
+ if ( t() && jQuery.timers.push(t) && !timerId ) {
+ timerId = setInterval( fx.tick, fx.interval );
+ }
+ },
+
+ // Simple 'show' function
+ show: function() {
+ var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
+
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
+ this.options.show = true;
+
+ // Begin the animation
+ // Make sure that we start at a small width/height to avoid any flash of content
+ if ( dataShow !== undefined ) {
+ // This show is picking up where a previous hide or show left off
+ this.custom( this.cur(), dataShow );
+ } else {
+ this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+ }
+
+ // Start by showing the element
+ jQuery( this.elem ).show();
+ },
+
+ // Simple 'hide' function
+ hide: function() {
+ // Remember where we started, so that we can go back to it later
+ this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
+ this.options.hide = true;
+
+ // Begin the animation
+ this.custom( this.cur(), 0 );
+ },
+
+ // Each step of an animation
+ step: function( gotoEnd ) {
+ var p, n, complete,
+ t = fxNow || createFxNow(),
+ done = true,
+ elem = this.elem,
+ options = this.options;
+
+ if ( gotoEnd || t >= options.duration + this.startTime ) {
+ this.now = this.end;
+ this.pos = this.state = 1;
+ this.update();
+
+ options.animatedProperties[ this.prop ] = true;
+
+ for ( p in options.animatedProperties ) {
+ if ( options.animatedProperties[ p ] !== true ) {
+ done = false;
+ }
+ }
+
+ if ( done ) {
+ // Reset the overflow
+ if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+ jQuery.each( [ "", "X", "Y" ], function( index, value ) {
+ elem.style[ "overflow" + value ] = options.overflow[ index ];
+ });
+ }
+
+ // Hide the element if the "hide" operation was done
+ if ( options.hide ) {
+ jQuery( elem ).hide();
+ }
+
+ // Reset the properties, if the item has been hidden or shown
+ if ( options.hide || options.show ) {
+ for ( p in options.animatedProperties ) {
+ jQuery.style( elem, p, options.orig[ p ] );
+ jQuery.removeData( elem, "fxshow" + p, true );
+ // Toggle data is no longer needed
+ jQuery.removeData( elem, "toggle" + p, true );
+ }
+ }
+
+ // Execute the complete function
+ // in the event that the complete function throws an exception
+ // we must ensure it won't be called twice. #5684
+
+ complete = options.complete;
+ if ( complete ) {
+
+ options.complete = false;
+ complete.call( elem );
+ }
+ }
+
+ return false;
+
+ } else {
+ // classical easing cannot be used with an Infinity duration
+ if ( options.duration == Infinity ) {
+ this.now = t;
+ } else {
+ n = t - this.startTime;
+ this.state = n / options.duration;
+
+ // Perform the easing function, defaults to swing
+ this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
+ this.now = this.start + ( (this.end - this.start) * this.pos );
+ }
+ // Perform the next step of the animation
+ this.update();
+ }
+
+ return true;
+ }
+};
+
+jQuery.extend( jQuery.fx, {
+ tick: function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ },
+
+ interval: 13,
+
+ stop: function() {
+ clearInterval( timerId );
+ timerId = null;
+ },
+
+ speeds: {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+ },
+
+ step: {
+ opacity: function( fx ) {
+ jQuery.style( fx.elem, "opacity", fx.now );
+ },
+
+ _default: function( fx ) {
+ if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+ fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+ } else {
+ fx.elem[ fx.prop ] = fx.now;
+ }
+ }
+ }
+});
+
+// Adds width/height step functions
+// Do not set anything below 0
+jQuery.each([ "width", "height" ], function( i, prop ) {
+ jQuery.fx.step[ prop ] = function( fx ) {
+ jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
+ };
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+ if ( !elemdisplay[ nodeName ] ) {
+
+ var body = document.body,
+ elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+ display = elem.css( "display" );
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // No iframe to use yet, so create it
+ if ( !iframe ) {
+ iframe = document.createElement( "iframe" );
+ iframe.frameBorder = iframe.width = iframe.height = 0;
+ }
+
+ body.appendChild( iframe );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.createElement( nodeName );
+
+ iframeDoc.body.appendChild( elem );
+
+ display = jQuery.css( elem, "display" );
+ body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+ }
+
+ return elemdisplay[ nodeName ];
+}
+
+
+
+
+var rtable = /^t(?:able|d|h)$/i,
+ rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0], box;
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ try {
+ box = elem.getBoundingClientRect();
+ } catch(e) {}
+
+ var doc = elem.ownerDocument,
+ docElem = doc.documentElement;
+
+ // Make sure we're not dealing with a disconnected DOM node
+ if ( !box || !jQuery.contains( docElem, elem ) ) {
+ return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+ }
+
+ var body = doc.body,
+ win = getWindow(doc),
+ clientTop = docElem.clientTop || body.clientTop || 0,
+ clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop,
+ scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+ top = box.top + scrollTop - clientTop,
+ left = box.left + scrollLeft - clientLeft;
+
+ return { top: top, left: left };
+ };
+
+} else {
+ jQuery.fn.offset = function( options ) {
+ var elem = this[0];
+
+ if ( options ) {
+ return this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ if ( !elem || !elem.ownerDocument ) {
+ return null;
+ }
+
+ if ( elem === elem.ownerDocument.body ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ var computedStyle,
+ offsetParent = elem.offsetParent,
+ prevOffsetParent = elem,
+ doc = elem.ownerDocument,
+ docElem = doc.documentElement,
+ body = doc.body,
+ defaultView = doc.defaultView,
+ prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+ top = elem.offsetTop,
+ left = elem.offsetLeft;
+
+ while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+ if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+ break;
+ }
+
+ computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+ top -= elem.scrollTop;
+ left -= elem.scrollLeft;
+
+ if ( elem === offsetParent ) {
+ top += elem.offsetTop;
+ left += elem.offsetLeft;
+
+ if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevOffsetParent = offsetParent;
+ offsetParent = elem.offsetParent;
+ }
+
+ if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+ top += parseFloat( computedStyle.borderTopWidth ) || 0;
+ left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+ }
+
+ prevComputedStyle = computedStyle;
+ }
+
+ if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+ top += body.offsetTop;
+ left += body.offsetLeft;
+ }
+
+ if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+ top += Math.max( docElem.scrollTop, body.scrollTop );
+ left += Math.max( docElem.scrollLeft, body.scrollLeft );
+ }
+
+ return { top: top, left: left };
+ };
+}
+
+jQuery.offset = {
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop,
+ left = body.offsetLeft;
+
+ if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+ left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[0] ) {
+ return null;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+ offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+ parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+ var method = "scroll" + name;
+
+ jQuery.fn[ method ] = function( val ) {
+ var elem, win;
+
+ if ( val === undefined ) {
+ elem = this[ 0 ];
+
+ if ( !elem ) {
+ return null;
+ }
+
+ win = getWindow( elem );
+
+ // Return the scroll offset
+ return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+ jQuery.support.boxModel && win.document.documentElement[ method ] ||
+ win.document.body[ method ] :
+ elem[ method ];
+ }
+
+ // Set the scroll offset
+ return this.each(function() {
+ win = getWindow( this );
+
+ if ( win ) {
+ win.scrollTo(
+ !i ? val : jQuery( win ).scrollLeft(),
+ i ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ this[ method ] = val;
+ }
+ });
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+ var type = name.toLowerCase();
+
+ // innerHeight and innerWidth
+ jQuery.fn[ "inner" + name ] = function() {
+ var elem = this[0];
+ return elem ?
+ elem.style ?
+ parseFloat( jQuery.css( elem, type, "padding" ) ) :
+ this[ type ]() :
+ null;
+ };
+
+ // outerHeight and outerWidth
+ jQuery.fn[ "outer" + name ] = function( margin ) {
+ var elem = this[0];
+ return elem ?
+ elem.style ?
+ parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+ this[ type ]() :
+ null;
+ };
+
+ jQuery.fn[ type ] = function( size ) {
+ // Get window width or height
+ var elem = this[0];
+ if ( !elem ) {
+ return size == null ? null : this;
+ }
+
+ if ( jQuery.isFunction( size ) ) {
+ return this.each(function( i ) {
+ var self = jQuery( this );
+ self[ type ]( size.call( this, i, self[ type ]() ) );
+ });
+ }
+
+ if ( jQuery.isWindow( elem ) ) {
+ // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+ // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+ var docElemProp = elem.document.documentElement[ "client" + name ],
+ body = elem.document.body;
+ return elem.document.compatMode === "CSS1Compat" && docElemProp ||
+ body && body[ "client" + name ] || docElemProp;
+
+ // Get document width or height
+ } else if ( elem.nodeType === 9 ) {
+ // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+ return Math.max(
+ elem.documentElement["client" + name],
+ elem.body["scroll" + name], elem.documentElement["scroll" + name],
+ elem.body["offset" + name], elem.documentElement["offset" + name]
+ );
+
+ // Get or set width or height on the element
+ } else if ( size === undefined ) {
+ var orig = jQuery.css( elem, type ),
+ ret = parseFloat( orig );
+
+ return jQuery.isNumeric( ret ) ? ret : orig;
+
+ // Set the width or height on the element (default to pixels if value is unitless)
+ } else {
+ return this.css( type, typeof size === "string" ? size : size + "px" );
+ }
+ };
+
+});
+
+
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+ define( "jquery", [], function () { return jQuery; } );
+}
+
+
+
+})( window );
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/resource_tree.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/resource_tree.js
index b8f570b..a43eee5 100644
--- a/modules/enterprise/gui/rest-war/src/main/webapp/js/resource_tree.js
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/resource_tree.js
@@ -17,8 +17,7 @@ var r = 960 / 2;
// d3.json("/rest/1/resource/10702/hierarchy.json", function(json) {
d3.json("/rest/1/resource/" + resourceId + "/hierarchy.json", function(json) {
- var inner = json.resourceWithChildren;
- var nodes = tree.nodes(inner);
+ var nodes = tree.nodes(json);
var link = vis.selectAll("path.link")
.data(tree.links(nodes))
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/rhq.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/rhq.js
new file mode 100644
index 0000000..0f1c5e6
--- /dev/null
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/rhq.js
@@ -0,0 +1,120 @@
+var rhq = {
+
+dashboard : function() {
+ // get all platforms
+ $.get('/rest/1/resource/platforms', function(data) {
+
+ var table = $("#table");
+ for (var i = 0; i < data.length ; i++ ) {
+ var resource = data[i];
+
+ var rowId = "tr" + i ;
+ table.append('<tr id="'+ rowId + '">');
+
+ // Now get the schedules for this resource
+ var resId = resource.resourceId;
+ console.log(resId);
+
+ $.getJSON("/rest/1/resource/"+resId+"/schedules.json?type=metric&enabledOnly=true", function (json2) {
+
+ var tr = $("#" + rowId);
+ for ( var n = 0 ; n < json2.length ; n ++ ) {
+ var tdid = "td_" + i + "_" + n;
+ var schedule = json2[n];
+ var td = tr.append('<td id="'+tdid+'">');
+ $("#"+tdid).html(schedule.displayName + " (" + schedule.scheduleId + ')<br/><div id="d' + tdid +'"/>');
+ rhq.whisker(schedule.scheduleId,tdid,180,90);
+ }
+ })
+ }
+ })
+ },
+whisker: function(scheduleId,divId,w,h) {
+
+ d3.json(
+ '/rest/1/metric/data/'+scheduleId+'.json?hideEmpty=true',
+ function (jsondata) {
+// console.log(scheduleId +", "+ divId);
+ var svg = d3.select("body").select("#"+divId);
+ svg=svg.append("svg:svg")
+ .attr("width", w)
+ .attr("height", h);
+
+ var points = jsondata.dataPoints;
+ var minVal = jsondata.min;
+ var maxVal = jsondata.max;
+ var avgVal = jsondata.avg;
+ var minTs = jsondata.minTimeStamp;
+ var maxTs = jsondata.maxTimeStamp;
+
+ var minTsD = new Date(minTs);
+ var maxTsD = new Date(maxTs);
+
+ // X axis goes from lowest to highest timestamp
+ var x = d3.time.scale().domain([minTsD,maxTsD]).range([0,w]);
+ // Y axis goes from lowest to highest value
+ var y = d3.scale.linear().domain([minVal, maxVal]).rangeRound([0,h]);
+
+
+ // Line for the average
+ var avgLine = svg.append("svg:line")
+ .attr("x1", x(new Date(minTs)))
+ .attr("y1", h - y(avgVal))
+ .attr("x2", x(new Date(maxTs)))
+ .attr("y2", h - y(avgVal))
+ .attr("stroke", "lightgrey")
+ .attr("stroke-dasharray", "2,4");
+
+ // TODO if present add lines for baselines
+
+ // data() loops over all entries in 'points' above for the remaining statements
+ var bars = svg.selectAll("bars").data(points);
+
+ var currX = function(d) {
+ return x(new Date(d.timeStamp))
+ };
+
+ var group = bars.enter().append("svg:g");
+
+ var line = group.append("svg:line")
+ .attr("x1", currX)
+ .attr("x2", currX)
+ .attr("y1", function(d) {
+ return h - y(d.low)
+ })
+ .attr("y2", function(d) {
+ return h - y(d.high)
+ })
+ .attr("stroke", "lightblue");
+
+ var circleLow = group.append("svg:circle")
+ .attr("cx", currX)
+ .attr("cy", function(d) {
+ return h - y(d.low)
+ })
+ .attr("r", 1)
+ .attr("stroke", "green")
+ .attr("fill", "lightgreen");
+
+ var circleHigh = group.append("svg:circle")
+ .attr("cx", currX)
+ .attr("cy", function(d) {
+ return h - y(d.high)
+ })
+ .attr("r", 1)
+ .attr("stroke", "green")
+ .attr("fill", "lightblue");
+
+ var circleVal = group.append("svg:circle")
+ .attr("cx", currX)
+ .attr("cy", function(d) {
+ return h - y(d.value)
+ })
+ .attr("r", 1.5)
+ .attr("stroke", "blue")
+ .attr("fill", "lightpink");
+
+ });
+} // whisker
+
+};//var rhq
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/whisker.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/whisker.js
deleted file mode 100644
index 6f23669..0000000
--- a/modules/enterprise/gui/rest-war/src/main/webapp/js/whisker.js
+++ /dev/null
@@ -1,92 +0,0 @@
-function whisker(scheduleId,divId) {
-// Width and height of the chart to print
- var w = 240,
- h = 125;
-
-// div#one is the div with the id 'one' ( <div id="one"/>)
- var svg = d3.select("body").select("div#"+divId).append("svg:svg")
- .attr("width", w)
- .attr("height", h);
-// .append("svg:g");
-// .attr("transform", "translate(" + p[3] + "," + (h - p[2]) + ")");
- d3.json(
- '/rest/1/metric/data/'+scheduleId+'.json?hideEmpty=true',
- function (jsondata) {
-
- var points = jsondata.dataPoints;
- var minVal = jsondata.min;
- var maxVal = jsondata.max;
- var avgVal = jsondata.avg;
- var minTs = jsondata.minTimeStamp;
- var maxTs = jsondata.maxTimeStamp;
-
- var minTsD = new Date(minTs);
- var maxTsD = new Date(maxTs);
- console.log(minTs, minTsD, maxTs, maxTsD);
-
- // X axis goes from lowest to highest timestamp
- var x = d3.time.scale().domain([minTsD,maxTsD]).range([0,w]);
- // Y axis goes from lowest to highest value
- var y = d3.scale.linear().domain([minVal, maxVal]).rangeRound([0,h]);
-
-
- // Line for the average
- var avgLine = svg.append("svg:line")
- .attr("x1", x(new Date(minTs)))
- .attr("y1", h - y(avgVal))
- .attr("x2", x(new Date(maxTs)))
- .attr("y2", h - y(avgVal))
- .attr("stroke", "lightgrey")
- .attr("stroke-dasharray", "2,4");
-
- // TODO if present add lines for baselines
-
- // data() loops over all entries in 'points' above for the remaining statements
- var bars = svg.selectAll("bars").data(points);
-
- var currX = function(d) {
- return x(new Date(d.timeStamp))
- };
-
- var group = bars.enter().append("svg:g");
-
- var line = group.append("svg:line")
- .attr("x1", currX)
- .attr("x2", currX)
- .attr("y1", function(d) {
- return h - y(d.low)
- })
- .attr("y2", function(d) {
- return h - y(d.high)
- })
- .attr("stroke", "lightblue");
-
- var circleLow = group.append("svg:circle")
- .attr("cx", currX)
- .attr("cy", function(d) {
- return h - y(d.low)
- })
- .attr("r", 1)
- .attr("stroke", "green")
- .attr("fill", "lightgreen");
-
- var circleHigh = group.append("svg:circle")
- .attr("cx", currX)
- .attr("cy", function(d) {
- return h - y(d.high)
- })
- .attr("r", 1)
- .attr("stroke", "green")
- .attr("fill", "lightblue");
-
- var circleVal = group.append("svg:circle")
- .attr("cx", currX)
- .attr("cy", function(d) {
- return h - y(d.value)
- })
- .attr("r", 1.5)
- .attr("stroke", "blue")
- .attr("fill", "lightpink");
-
- });
-}
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/stacked1.html b/modules/enterprise/gui/rest-war/src/main/webapp/stacked1.html
index 549a3ff..9413b70 100644
--- a/modules/enterprise/gui/rest-war/src/main/webapp/stacked1.html
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/stacked1.html
@@ -5,6 +5,7 @@
<script type="text/javascript" src="js/d3.js"></script>
<script type="text/javascript" src="js/d3.layout.js"></script>
<script type="text/javascript" src="js/d3.time.js"></script>
+ <script type="text/javascript" src="js/jquery.js"></script>
<style type="text/css">
svg {
@@ -18,33 +19,50 @@ svg {
</style>
</head>
<body>
- <h2>Stacked metrics (schedule 10003)</h2>
- Upper edge of green is low, upper edge of blue is the value and upper edge of pink is high.
- <br/>
- <script type="text/javascript">
-
-var w = 960,
- h = 500,
- p = [20, 50, 30, 20],
- x = d3.scale.ordinal().rangeRoundBands([0, w - p[1] - p[3]]),
- y = d3.scale.linear().range([0, h - p[0] - p[2]]),
- z = d3.scale.ordinal().range(["lightgreen", "darkblue", "lightpink"]),
- format = d3.time.format("%H:%M");
-
-var svg = d3.select("body").append("svg:svg")
- .attr("width", w)
- .attr("height", h)
- .append("svg:g")
- .attr("transform", "translate(" + p[3] + "," + (h - p[2]) + ")");
-
-
-d3.json(
- '/rest/1/metric/data/10003.json',
- function (jsondata) {
-
- var points = jsondata.metricAggregate.dataPoints;
- var min = jsondata.metricAggregate.min;
- var max = jsondata.metricAggregate.max;
+
+
+<h1 id="resId">Resource</h1>
+Upper edge of green is low, upper edge of blue is the value and upper edge of pink is high.
+<br/>
+<h2 id="header">Schedule</h2>
+ <script type="text/javascript">
+
+// Get the platforms and then the first one
+$.getJSON('/rest/1/resource/platforms.json', function (json) {
+ var res = json[0];
+ var rid = res.resourceId;
+ $("#resId").text("Resource " + rid + " (" + res.resourceName + ")");
+
+ // Now get the metrical schedules of the platform
+ $.getJSON("/rest/1/resource/"+rid+"/schedules.json?type=metric", function (json2) {
+
+ // Take the first
+ var schId = json2[0].scheduleId;
+ $("#header").text("Schedule " + schId + " (" + json2[0].scheduleName + ")");
+
+ // And call d3 graphing on it
+ d3.json(
+ '/rest/1/metric/data/'+schId+'.json',
+ function (jsondata) {
+
+ var w = 960,
+ h = 500,
+ p = [20, 50, 30, 20],
+ x = d3.scale.ordinal().rangeRoundBands([0, w - p[1] - p[3]]),
+ y = d3.scale.linear().range([0, h - p[0] - p[2]]),
+ z = d3.scale.ordinal().range(["lightgreen", "darkblue", "lightpink"]),
+ format = d3.time.format("%H:%M");
+
+ var svg = d3.select("body").append("svg:svg")
+ .attr("width", w)
+ .attr("height", h)
+ .append("svg:g")
+ .attr("transform", "translate(" + p[3] + "," + (h - p[2]) + ")");
+
+
+ var points = jsondata.dataPoints;
+ var min = jsondata.min;
+ var max = jsondata.max;
var metrics = d3.layout.stack()(["low","value","high"].map(function (point) {
return points.map(function (d) {
@@ -119,6 +137,8 @@ d3.json(
.attr("dy", ".35em")
.text(d3.format(",d"));
});
+ });
+});
</script>
</body>
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/stacked2.html b/modules/enterprise/gui/rest-war/src/main/webapp/stacked2.html
index d3f8b4d..e622f2c 100644
--- a/modules/enterprise/gui/rest-war/src/main/webapp/stacked2.html
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/stacked2.html
@@ -5,6 +5,7 @@
<script type="text/javascript" src="js/d3.js"></script>
<script type="text/javascript" src="js/d3.layout.js"></script>
<script type="text/javascript" src="js/d3.time.js"></script>
+ <script type="text/javascript" src="js/jquery.js"></script>
<style type="text/css">
svg {
@@ -18,9 +19,10 @@ svg {
</style>
</head>
<body>
- <h2>Stacked metrics (schedule 10003)</h2>
+ <h1 id="resId">Resource</h1>
Upper edge of green is low, upper edge of blue is the value and upper edge of pink is high.
<br/>
+ <h2 id="header">Schedule</h2>
<script type="text/javascript">
var w = 960,
@@ -37,89 +39,101 @@ var svg = d3.select("body").append("svg:svg")
.append("svg:g")
.attr("transform", "translate(" + p[3] + "," + (h - p[2]) + ")");
-
-d3.json(
- '/rest/1/metric/data/10003.json',
- function (jsondata) {
-
- var points = jsondata.metricAggregate.dataPoints;
- var min = jsondata.metricAggregate.min;
- var max = jsondata.metricAggregate.max;
-
- var metrics = d3.layout.stack()(["low","value","high"].map(function (point) {
- return points.map(function (d) {
- var tmp;
- if (point == "low")
- tmp = d["low"] - min;
- if (point == "value") {
- var va = d["value"];
- var lo = d["low"];
- tmp = va-lo;
- }
- if (point == "high")
- tmp = (d["high"]-d["value"]);
-
- if (tmp < 0 ) {
-// console.log(point);
- tmp=0;
- }
-
- return {x: new Date(d.timeStamp), y: tmp};
- });
- }));
-
-// console.log(metrics);
-
-
- // Compute the x-domain (by date) and y-domain (by top).
- x.domain(metrics[0].map(function(d) { return d.x; }));
- y.domain([0, d3.max(metrics[metrics.length - 1], function(d) { return d.y0 + d.y; })]);
-
- // Add a group for each cause.
- var cause = svg.selectAll("g.cause")
- .data(metrics)
- .enter().append("svg:g")
- .attr("class", "cause")
- .style("fill", function(d, i) { return z(i); })
- .style("stroke", function(d, i) { return d3.rgb(z(i)).darker(); });
-
- // Add a rect for each date.
- var rect = cause.selectAll("rect")
- .data(Object)
- .enter().append("svg:rect")
- .attr("x", function(d) { return x(d.x); })
- .attr("y", function(d) { return -y(d.y0) - y(d.y); })
- .attr("height", function(d) { return 5; }) // <<<--- HERE
- .attr("width", x.rangeBand());
-
- // Add a label per date.
- var label = svg.selectAll("text")
- .data(x.domain())
- .enter().append("svg:text")
- .attr("x", function(d) { return x(d) + x.rangeBand() / 2; })
- .attr("y", 6)
- .attr("text-anchor", "middle")
- .attr("dy", ".71em")
- .text(format);
-
- // Add y-axis rules.
- var rule = svg.selectAll("g.rule")
- .data(y.ticks(5))
- .enter().append("svg:g")
- .attr("class", "rule")
- .attr("transform", function(d) { return "translate(0," + -y(d) + ")"; });
-
- rule.append("svg:line")
- .attr("x2", w - p[1] - p[3])
- .style("stroke", function(d) { return d ? "#fff" : "#000"; })
- .style("stroke-opacity", function(d) { return d ? .7 : null; });
-
- rule.append("svg:text")
- .attr("x", w - p[1] - p[3] + 6)
- .attr("dy", ".35em")
- .text(d3.format(",d"));
-});
-
+// Get the platforms and then the first one
+$.getJSON('/rest/1/resource/platforms.json', function (json) {
+ var res = json[0];
+ var rid = res.resourceId;
+ $("#resId").text("Resource " + rid + " (" + res.resourceName + ")");
+
+ // Now get the metrical schedules of the platform
+ $.getJSON("/rest/1/resource/"+rid+"/schedules.json?type=metric", function (json2) {
+
+ // Take the first
+ var schId = json2[0].scheduleId;
+ $("#header").text("Schedule " + schId + " (" + json2[0].scheduleName + ")");
+
+ // And call d3 graphing on it
+ d3.json(
+ '/rest/1/metric/data/'+schId+'.json',
+ function (jsondata) {
+
+ var points = jsondata.dataPoints;
+ var min = jsondata.min;
+ var max = jsondata.max;
+
+ var metrics = d3.layout.stack()(["low","value","high"].map(function (point) {
+ return points.map(function (d) {
+ var tmp;
+ if (point == "low")
+ tmp = d["low"] - min;
+ if (point == "value") {
+ var va = d["value"];
+ var lo = d["low"];
+ tmp = va-lo;
+ }
+ if (point == "high")
+ tmp = (d["high"]-d["value"]);
+
+ if (tmp < 0 ) {
+ tmp=0;
+ }
+
+ return {x: new Date(d.timeStamp), y: tmp};
+ });
+ }));
+
+
+ // Compute the x-domain (by date) and y-domain (by top).
+ x.domain(metrics[0].map(function(d) { return d.x; }));
+ y.domain([0, d3.max(metrics[metrics.length - 1], function(d) { return d.y0 + d.y; })]);
+
+ // Add a group for each cause.
+ var cause = svg.selectAll("g.cause")
+ .data(metrics)
+ .enter().append("svg:g")
+ .attr("class", "cause")
+ .style("fill", function(d, i) { return z(i); })
+ .style("stroke", function(d, i) { return d3.rgb(z(i)).darker(); });
+
+ // Add a rect for each date.
+ var rect = cause.selectAll("rect")
+ .data(Object)
+ .enter().append("svg:rect")
+ .attr("x", function(d) { return x(d.x); })
+ .attr("y", function(d) { return -y(d.y0) - y(d.y); })
+ .attr("height", function(d) { return 5; }) // <<<--- HERE
+ .attr("width", x.rangeBand());
+
+ // Add a label per date.
+ var label = svg.selectAll("text")
+ .data(x.domain())
+ .enter().append("svg:text")
+ .attr("x", function(d) { return x(d) + x.rangeBand() / 2; })
+ .attr("y", 6)
+ .attr("text-anchor", "middle")
+ .attr("dy", ".71em")
+ .text(format);
+
+ // Add y-axis rules.
+ var rule = svg.selectAll("g.rule")
+ .data(y.ticks(5))
+ .enter().append("svg:g")
+ .attr("class", "rule")
+ .attr("transform", function(d) { return "translate(0," + -y(d) + ")"; });
+
+ rule.append("svg:line")
+ .attr("x2", w - p[1] - p[3])
+ .style("stroke", function(d) { return d ? "#fff" : "#000"; })
+ .style("stroke-opacity", function(d) { return d ? .7 : null; });
+
+ rule.append("svg:text")
+ .attr("x", w - p[1] - p[3] + 6)
+ .attr("dy", ".35em")
+ .text(d3.format(",d"));
+ });
+
+ })
+})
</script>
</body>
</html>
\ No newline at end of file
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/whisker.html b/modules/enterprise/gui/rest-war/src/main/webapp/whisker.html
index dc37e35..2b03349 100644
--- a/modules/enterprise/gui/rest-war/src/main/webapp/whisker.html
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/whisker.html
@@ -5,6 +5,8 @@
<script type="text/javascript" src="js/d3.js"></script>
<script type="text/javascript" src="js/d3.layout.js"></script>
<script type="text/javascript" src="js/d3.time.js"></script>
+ <script type="text/javascript" src="js/jquery.js"></script>
+ <script type="text/javascript" src="js/rhq.js"></script>
<style type="text/css">
svg {
@@ -18,91 +20,33 @@ svg {
</style>
</head>
<body>
- <h2> (schedule 10003)</h2>
+ <h1 id="resId">Resource</h1>
+ <h2 id="header">Schedule</h2>
- <br/>
- <div id="one"/>
+ <div id="one"></div>
<script type="text/javascript">
// Width and height of the chart to print
var w = 800,
h = 600;
-// div#one is the div with the id 'one' ( <div id="one"/>)
-var svg = d3.select("body").select("div#one").append("svg:svg")
- .attr("width", w)
- .attr("height", h);
-// .append("svg:g");
-// .attr("transform", "translate(" + p[3] + "," + (h - p[2]) + ")");
+// Get the platforms and then the first one
+$.getJSON('/rest/1/resource/platforms.json', function (json) {
+ var res = json[0];
+ var rid = res.resourceId;
+ $("#resId").text("Resource " + rid + " (" + res.resourceName + ")");
+ // Now get the metrical schedules of the platform
+ $.getJSON("/rest/1/resource/"+rid+"/schedules.json?type=metric", function (json2) {
-d3.json(
- '/rest/1/metric/data/10003.json?hideEmpty=true',
- function (jsondata) {
+ // Take the first and render it
+ var schId = json2[0].scheduleId;
+ $("#header").text("Schedule " + schId + " (" + json2[0].scheduleName + ")");
+ rhq.whisker(schId,"one",w,h);
+ })
+})
- var points = jsondata.metricAggregate.dataPoints;
- var minVal = jsondata.metricAggregate.min;
- var maxVal = jsondata.metricAggregate.max;
- var avgVal = jsondata.metricAggregate.avg;
- var minTs = jsondata.metricAggregate.minTimeStamp;
- var maxTs = jsondata.metricAggregate.maxTimeStamp;
- var minTsD = new Date(minTs);
- var maxTsD = new Date(maxTs);
- console.log(minTs, minTsD, maxTs, maxTsD);
-
- // X axis goes from lowest to highest timestamp
- var x = d3.time.scale().domain([minTsD,maxTsD]).range([0,w]);
- // Y axis goes from lowest to highest value
- var y = d3.scale.linear().domain([minVal, maxVal]).rangeRound([0,h]);
-
-// console.log(jsondata.metricAggregate);
-
-
- // Line for the average
- var avgLine = svg.append("svg:line")
- .attr("x1",x(new Date(minTs)))
- .attr("y1",h-y(avgVal))
- .attr("x2",x(new Date(maxTs)))
- .attr("y2",h-y(avgVal))
- .attr("stroke","lighgrey")
- .attr("stroke-dasharray","2,2");
-
- // TODO if present add lines for baselines
-
- // data() loops over all entries in 'points' above for the remaining statements
- var bars = svg.selectAll("bars").data(points);
-
- var currX = function(d) { return x(new Date(d.timeStamp)) };
-
- var circleLow = bars.enter().append("svg:circle")
- .attr("cx",currX)
- .attr("cy",function(d) {return h-y(d.low)})
- .attr("r",2)
- .attr("stroke","lightgreen")
- .attr("fill","lightgreen");
-
- var circleHigh = bars.enter().append("svg:circle")
- .attr("cx",currX)
- .attr("cy",function(d) {return h-y(d.high)})
- .attr("r",2)
- .attr("stroke","lightgreen")
- .attr("fill","lightgreen");
-
- var circleVal = bars.enter().append("svg:circle")
- .attr("cx",currX)
- .attr("cy",function(d) {return h-y(d.value)})
- .attr("r",3)
- .attr("stroke","lightpink")
- .attr("fill","lightpink");
-
- var line = bars.enter().append("svg:line")
- .attr("x1",currX)
- .attr("x2",currX)
- .attr("y1",function(d) {return h-y(d.low)})
- .attr("y2",function(d) {return h-y(d.high)})
- .attr("stroke","blue");
-});
</script>
</body>
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/whisker2.html b/modules/enterprise/gui/rest-war/src/main/webapp/whisker2.html
index d523acd..67340d5 100644
--- a/modules/enterprise/gui/rest-war/src/main/webapp/whisker2.html
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/whisker2.html
@@ -1,17 +1,18 @@
<!DOCTYPE html>
<html>
<head>
- <title>(free mem, schedule 10003)</title>
+ <title>Dashboard</title>
<script type="text/javascript" src="js/d3.js"></script>
<script type="text/javascript" src="js/d3.layout.js"></script>
<script type="text/javascript" src="js/d3.time.js"></script>
- <script type="text/javascript" src="js/whisker.js"></script>
+ <script type="text/javascript" src="js/jquery.js"></script>
+ <script type="text/javascript" src="js/rhq.js"></script>
<style type="text/css">
svg {
- width: 240px;
- height: 125px;
+ width: 182px;
+ height: 92px;
border: solid 1px #ccc;
font: 10px sans-serif;
shape-rendering: crispEdges;
@@ -20,51 +21,15 @@ svg {
</style>
</head>
<body>
- <h2>Various metrics</h2>
- <table>
- <tr>
- <td>
- Schedule 10003<br/>
- <div id="two">
- <script>whisker(10003,'two')</script>
- </div>
- </td>
- <td>
- Schedule 10004<br/>
- <div id="three">
- <script>whisker(10004,'three')</script>
- </div>
- </td>
-
- <td>
- Schedule 10007<br/>
- <div id="four">
- <script>whisker(10007,'four')</script>
- </div>
- </td>
- </tr>
- <tr>
- <td>
- Schedule 10011<br/>
- <div id="five">
- <script>whisker(10011,'five')</script>
- </div>
- </td>
- <td>
- Schedule 10009<br/>
- <div id="six">
- <script>whisker(10009,'six')</script>
- </div>
- </td>
- <td>
- Schedule 10010<br/>
- <div id="seven">
- <script>whisker(10010,'seven')</script>
- </div>
- </td>
- </tr>
+ <h2>Dashboard</h2>
+<script>
+ $(document).ready(function() {
+ rhq.dashboard();
+ })
+</script>
+ <table id="table">
</table>
</body>
-</html>
\ No newline at end of file
+</html>
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 91ed23a..59efeba 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
@@ -31,6 +31,8 @@ import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.GenericEntity;
@@ -192,6 +194,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
public Response getSchedules(int resourceId,
String scheduleType,
+ boolean enabledOnly,
@Context Request request,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) {
@@ -200,7 +203,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
if (scheduleType.equals("metric"))
scheduleType=DataType.MEASUREMENT.toString().toLowerCase();
- Resource res = resMgr.getResource(caller,resourceId); // Don't fetch(), as this would yield a LazyLoadException
+ Resource res = resMgr.getResource(caller, resourceId); // Don't fetch(), as this would yield a LazyLoadException
Set<MeasurementSchedule> schedules = res.getSchedules();
List<MetricSchedule> ret = new ArrayList<MetricSchedule>(schedules.size());
@@ -212,26 +215,28 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
if ("all".equals(scheduleType) ||
scheduleType.toLowerCase().equals(definition.getDataType().toString().toLowerCase()) ) {
- MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
- schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
- definition.getDataType().toString());
- UriBuilder uriBuilder;
- URI uri;
- if (definition.getDataType()== DataType.MEASUREMENT) {
+ if (!enabledOnly || (enabledOnly && schedule.isEnabled() )) {
+ MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
+ schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
+ definition.getDataType().toString());
+ UriBuilder uriBuilder;
+ URI uri;
+ if (definition.getDataType()== DataType.MEASUREMENT) {
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/metric/data/{id}");
+ 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("/metric/data/{id}");
- uri = uriBuilder.build(schedule.getId());
- Link metricLink = new Link("metric",uri.toString());
- ms.addLink(metricLink);
+ uriBuilder.path("resource/" + schedule.getResource().getId());
+ uri = uriBuilder.build();
+ Link link = new Link("resource",uri.toString());
+ ms.addLink(link);
+
+ ret.add(ms);
}
- // 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);
}
}
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 08a1905..ab9ea62 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
@@ -87,6 +87,7 @@ public interface ResourceHandlerLocal {
@Cache(isPrivate = true,maxAge = 60)
Response getSchedules(@PathParam("id") int resourceId,
@QueryParam("type") @DefaultValue("all") String scheduleType,
+ @QueryParam("enabledOnly") @DefaultValue("true") boolean enabledOnly,
@Context Request request,
@Context HttpHeaders headers,
@Context UriInfo uriInfo);
diff --git a/modules/enterprise/server/jar/src/main/resources/rest_templates/metricData.ftl b/modules/enterprise/server/jar/src/main/resources/rest_templates/metricData.ftl
index d426e06..58af399 100644
--- a/modules/enterprise/server/jar/src/main/resources/rest_templates/metricData.ftl
+++ b/modules/enterprise/server/jar/src/main/resources/rest_templates/metricData.ftl
@@ -24,7 +24,7 @@
<script type="text/javascript" src="/rest/js/d3.js"></script>
<script type="text/javascript" src="/rest/js/d3.layout.js"></script>
<script type="text/javascript" src="/rest/js/d3.time.js"></script>
- <script type="text/javascript" src="/rest/js/whisker.js"></script>
+ <script type="text/javascript" src="/rest/js/rhq.js"></script>
<table border=1>
<thead>
<tr>
@@ -44,7 +44,7 @@
<td>Max</td><td>${var.max}</td>
</tr>
<tr>
- <td><a align="top" href="javascript:whisker(${sched?c},'one')">DataPoints</a></td><td>
+ <td><a align="top" href="javascript:rhq.whisker(${sched?c},'one',400,200)">DataPoints</a></td><td>
<table>
<thead>
<tr>
commit ffa3c7f3e181f62107036844dacc4aef413ade80
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Sat Nov 26 19:27:37 2011 +0100
Get system status from the SystemInfoManager
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java
index 0f2a0ed..a6fce0c 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java
@@ -18,9 +18,12 @@
*/
package org.rhq.enterprise.server.rest;
+import java.util.Map;
+
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
+import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -39,6 +42,7 @@ import org.rhq.enterprise.server.alert.AlertManagerLocal;
import org.rhq.enterprise.server.measurement.MeasurementScheduleManagerLocal;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
import org.rhq.enterprise.server.rest.domain.Status;
+import org.rhq.enterprise.server.system.SystemInfoManagerLocal;
/**
* Return system status
@@ -50,48 +54,14 @@ public class StatusHandlerBean extends AbstractRestBean implements StatusHandler
@EJB
- MeasurementScheduleManagerLocal scheduleManager;
- @EJB
- ResourceManagerLocal resourceManager;
- @EJB
- AlertManagerLocal alertManager;
- @EJB
- AlertDefinitionManagerLocal alertDefinitionManager;
+ SystemInfoManagerLocal infoMgr;
@Override
public Response getStatus(HttpHeaders httpHeaders) {
+ Map<String,String> statusMap = infoMgr.getSystemInformation (caller);
Status status = new Status();
-
- ResourceCriteria criteria = new ResourceCriteria();
- criteria.addFilterResourceCategories(ResourceCategory.PLATFORM);
- criteria.setRestriction(Criteria.Restriction.COUNT_ONLY);
- PageList<Resource> resList = resourceManager.findResourcesByCriteria(caller,criteria);
- status.setPlatforms(resList.getTotalSize());
- criteria = new ResourceCriteria();
- criteria.addFilterResourceCategories(ResourceCategory.SERVER);
- criteria.setRestriction(Criteria.Restriction.COUNT_ONLY);
- resList = resourceManager.findResourcesByCriteria(caller,criteria);
- status.setServers(resList.getTotalSize());
- criteria = new ResourceCriteria();
- criteria.addFilterResourceCategories(ResourceCategory.SERVICE);
- criteria.setRestriction(Criteria.Restriction.COUNT_ONLY);
- resList = resourceManager.findResourcesByCriteria(caller,criteria);
- status.setServices(resList.getTotalSize());
-
- AlertCriteria alertCriteria = new AlertCriteria();
- alertCriteria.setRestriction(Criteria.Restriction.COUNT_ONLY);
- PageList<Alert> alertList = alertManager.findAlertsByCriteria(caller,alertCriteria);
- status.setAlerts(alertList.getTotalSize());
-
- AlertDefinitionCriteria alertDefinitionCriteria = new AlertDefinitionCriteria();
- alertDefinitionCriteria.setRestriction(Criteria.Restriction.COUNT_ONLY);
- PageList<AlertDefinition> defList = alertDefinitionManager.findAlertDefinitionsByCriteria(caller,alertDefinitionCriteria);
- status.setAlertDefinitions(defList.getTotalSize());
-
- status.setSchedules(-1); // TODO
-
- status.setMetricsMin(scheduleManager.getScheduledMeasurementsPerMinute());
+ status.setValues(statusMap);
MediaType mediaType = httpHeaders.getAcceptableMediaTypes().get(0);
Response.ResponseBuilder builder;
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Status.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Status.java
index c0a4e10..dfb64ab 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Status.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Status.java
@@ -18,6 +18,9 @@
*/
package org.rhq.enterprise.server.rest.domain;
+import java.util.HashMap;
+import java.util.Map;
+
import javax.xml.bind.annotation.XmlRootElement;
/**
@@ -27,70 +30,15 @@ import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Status {
- int platforms;
- int servers;
- int services;
- int alerts;
- int alertDefinitions;
- int metricsMin;
- int schedules;
-
+ Map<String,String> values = new HashMap<String, String>();
public Status() {
}
- public int getPlatforms() {
- return platforms;
- }
-
- public void setPlatforms(int platforms) {
- this.platforms = platforms;
- }
-
- public int getServers() {
- return servers;
- }
-
- public void setServers(int servers) {
- this.servers = servers;
- }
-
- public int getServices() {
- return services;
- }
-
- public void setServices(int services) {
- this.services = services;
- }
-
- public int getAlerts() {
- return alerts;
- }
-
- public void setAlerts(int alerts) {
- this.alerts = alerts;
- }
-
- public int getAlertDefinitions() {
- return alertDefinitions;
- }
-
- public void setAlertDefinitions(int alertDefinitions) {
- this.alertDefinitions = alertDefinitions;
- }
-
- public int getMetricsMin() {
- return metricsMin;
- }
-
- public void setMetricsMin(int metricsMin) {
- this.metricsMin = metricsMin;
- }
-
- public int getSchedules() {
- return schedules;
+ public Map<String, String> getValues() {
+ return values;
}
- public void setSchedules(int schedules) {
- this.schedules = schedules;
+ public void setValues(Map<String, String> values) {
+ this.values = values;
}
}
diff --git a/modules/enterprise/server/jar/src/main/resources/rest_templates/status.ftl b/modules/enterprise/server/jar/src/main/resources/rest_templates/status.ftl
index f4adb30..6569373 100644
--- a/modules/enterprise/server/jar/src/main/resources/rest_templates/status.ftl
+++ b/modules/enterprise/server/jar/src/main/resources/rest_templates/status.ftl
@@ -2,7 +2,7 @@
<#--
/*
* RHQ Management Platform
- * Copyright (C) 2005-2008 Red Hat, Inc.
+ * Copyright (C) 2005-2011 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
@@ -27,23 +27,10 @@
<td>Name</td><td>Value</td>
</tr>
</thead>
+ <#list var.values?keys as key>
<tr>
- <td>Alert definitions</td><td>${var.alertDefinitions}</td>
- </tr>
- <tr>
- <td>Alerts</td><td>${var.alerts}</td>
- </tr>
- <tr>
- <td>Platforms</td><td>${var.platforms}</td>
- </tr>
- <tr>
- <td>Servers</td><td>${var.servers}</td>
- </tr>
- <tr>
- <td>Services</td><td>${var.services}</td>
- </tr>
- <tr>
- <td>Metrics/min</td><td>${var.metricsMin}</td>
+ <td>${key}</td><td>${var.values[key]}</td>
</tr>
+ </#list>
</table>
</html>
commit 4ab4f01ace074d2d0ed1457175173523988cd5ea
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Sat Nov 26 17:34:07 2011 +0100
Improve history handling and parameter passing.
Add link to self for the history item.
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
index 536274a..be39ede 100644
--- 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
@@ -1,8 +1,27 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
package org.rhq.enterprise.server.rest;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.ejb.EJB;
@@ -14,6 +33,7 @@ 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.EntityTag;
import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
@@ -24,6 +44,8 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.rhq.core.domain.configuration.Configuration;
+import org.rhq.core.domain.configuration.Property;
+import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
import org.rhq.core.domain.criteria.OperationHistoryCriteria;
@@ -40,6 +62,7 @@ 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.OperationHistoryRest;
import org.rhq.enterprise.server.rest.domain.OperationRest;
/**
@@ -204,7 +227,13 @@ public class OperationsHandlerBean extends AbstractRestBean implements Operation
// todo check params
// submit
- ResourceOperationSchedule sched = opsManager.scheduleResourceOperation(caller,operation.getResourceId(),operation.getName(),0,0,0,-1,new Configuration(),"TEst");
+
+ Configuration parameters = new Configuration();
+ for (Map.Entry<String,Object> entry : operation.getParams().entrySet()) {
+ parameters.put(new PropertySimple(entry.getKey(),entry.getValue())); // TODO honor more types
+ }
+ ResourceOperationSchedule sched = opsManager.scheduleResourceOperation(caller,operation.getResourceId(),operation.getName(),0,0,0,-1,
+ parameters,"TEst");
JobId jobId = new JobId(sched.getJobName(),sched.getJobGroup());
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/operation/history/{id}");
@@ -242,27 +271,45 @@ public class OperationsHandlerBean extends AbstractRestBean implements Operation
@Override
@GET
@Path("history/{id}")
- public Response outcome(@PathParam("id") String jobName) {
+ public Response outcome(@PathParam("id") String jobName, @Context UriInfo uriInfo) {
-/*
- ResourceOperationSchedule schedule = opsManager.getResourceOperationSchedule(caller,operationHistoryId);
- if (schedule==null)
- throw new StuffNotFoundException("OperationSchedule with id " + operationHistoryId);
-*/
ResourceOperationHistoryCriteria criteria = new ResourceOperationHistoryCriteria();
criteria.addFilterJobId(new JobId(jobName));
- OperationHistory history ;//= opsManager.getOperationHistoryByJobId(caller,jobName);
+ ResourceOperationHistory history ;//= opsManager.getOperationHistoryByJobId(caller,jobName);
List<ResourceOperationHistory> list = opsManager.findResourceOperationHistoriesByCriteria(caller,criteria);
- if (list==null || list.isEmpty())
+ if (list==null || list.isEmpty()) {
+ log.info("No history with id " + new HistoryJobId(jobName) + " found");
throw new StuffNotFoundException("OperationHistory with id " + new HistoryJobId(jobName));
+ }
history = list.get(0);
- String ret = history.getStatus().getDisplayName();
- // TODO get history.results with the help of the property definition
+ String status;
+ if (history.getStatus()==null)
+ status = " - no infomation yet -";
+ else
+ status = history.getStatus().getDisplayName();
+
+ OperationHistoryRest hist = new OperationHistoryRest();
+ hist.setStatus(status);
+ if (history.getErrorMessage()!=null)
+ hist.setErrorMessage(history.getErrorMessage());
+ if (history.getResults()!=null) {
+ Configuration results = history.getResults();
+ for (Property p : results.getProperties()) {
+ hist.getResult().put(p.getName(),p.toString());
+ }
+ }
+
+ UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/operation/history/{id}");
+ URI url = uriBuilder.build(new JobId(jobName));
+ Link self = new Link("self",url.toString());
+ hist.getLinks().add(self);
+
- return Response.ok(ret).build();
+ return Response.ok(hist).build();
}
}
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
index 1d2a9ed..79c20bb 100644
--- 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
@@ -1,3 +1,21 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
package org.rhq.enterprise.server.rest;
import java.util.List;
@@ -72,5 +90,5 @@ public interface OperationsHandlerLocal {
@GET
@Path("history/{id}")
- public Response outcome(@PathParam("id") String jobName);
+ public Response outcome(@PathParam("id") String jobName, @Context UriInfo uriInfo);
}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationHistoryRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationHistoryRest.java
new file mode 100644
index 0000000..8249db6
--- /dev/null
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/OperationHistoryRest.java
@@ -0,0 +1,72 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+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.XmlRootElement;
+
+/**
+ * An operations history item
+ * @author Heiko W. Rupp
+ */
+@XmlRootElement
+public class OperationHistoryRest {
+
+ String status;
+ String errorMessage;
+
+ Map<String,Object> result = new HashMap<String, Object>();
+ List<Link> links = new ArrayList<Link>();
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public Map<String, Object> getResult() {
+ return result;
+ }
+
+ public void setResult(Map<String, Object> result) {
+ this.result = result;
+ }
+
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public void setErrorMessage(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ public List<Link> getLinks() {
+ return links;
+ }
+
+ public void setLinks(List<Link> links) {
+ this.links = links;
+ }
+}
commit 7eed6555906199687c5f9f5d6c8cf50c15970d0d
Merge: 096cb99 b9ef6c8
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Sat Nov 26 11:31:39 2011 +0100
Merge branch 'master' into feature/rest
commit 096cb99394259a950b92c81781405ad87fb206f6
Merge: 123ac05 90081e7
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Fri Nov 25 17:56:43 2011 +0100
Merge branch 'master' into feature/rest
Conflicts:
modules/enterprise/server/container/pom.xml
modules/enterprise/server/ear/pom.xml
diff --cc modules/enterprise/server/ear/pom.xml
index b792ab8,258a946e..941c88a
--- a/modules/enterprise/server/ear/pom.xml
+++ b/modules/enterprise/server/ear/pom.xml
@@@ -95,7 -89,11 +89,11 @@@
<groupId>org.rhq</groupId>
<artifactId>rhq-core-domain</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>javassist</groupId>
+ <artifactId>javassist</artifactId>
+ </exclusion>
- </exclusions>
+ </exclusions>
</dependency>
<dependency>
diff --cc modules/jopr/modules-plugins-pom.xml
index dc1fa28,3b602ea..1bb52cf
--- a/modules/jopr/modules-plugins-pom.xml
+++ b/modules/jopr/modules-plugins-pom.xml
@@@ -1,83 -1,74 +1,74 @@@
--<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-- <modelVersion>4.0.0</modelVersion>
--
-- <parent>
-- <groupId>org.jboss.on</groupId>
-- <artifactId>jopr-modules-parent</artifactId>
-- <version>2.4.0-SNAPSHOT</version>
-- </parent>
--
-- <!-- Plugin poms should not set their parent pom to this artifact. Instead, set to "jopr-modules-parent". This is due to
-- the fact that this pom runs integration tests *on* the plugins. This pom must process in the reactor after the
-- plugins themselves, as such, it can't be their parent. -->
-- <groupId>org.jboss.on</groupId>
-- <artifactId>jopr-plugins-parent</artifactId>
-- <version>2.3.0.EmbJopr.1.3.0-1</version>
-- <packaging>pom</packaging>
-- <name>Jopr Plugins</name>
-- <description>parent POM for all Jopr plugins</description>
--
- <scm>
- <connection>scm:svn:http://svn.jboss.org/repos/jopr/tags/Jopr_2_3_0_EmbJopr_1_3_0-1/m...</connection>
- <developerConnection>scm:svn:https://svn.jboss.org/repos/jopr/tags/Jopr_2_3_0_EmbJopr_1_3_0-1/...</developerConnection>
- </scm>
-
- <properties>
- <scm.module.path>modules/plugins/</scm.module.path>
- </properties>
-
-- <dependencies>
-- <dependency>
-- <groupId>${rhq.groupId}</groupId>
-- <artifactId>rhq-jmx-plugin</artifactId>
-- <version>${rhq.version}</version>
-- <scope>test</scope>
-- </dependency>
-- </dependencies>
--
-- <!-- plugins - order is important -->
-- <modules>
-- <module>tomcat</module>
-- <module>jboss-as</module>
-- <module>hibernate</module>
-- <module>rhq-server</module>
-- <module>jboss-cache</module>
-- <module>jboss-as-5</module>
-- <module>jboss-cache-v3</module>
-- </modules>
--
-- <build>
-- <plugins>
-- <plugin>
-- <artifactId>maven-antrun-plugin</artifactId>
-- <executions>
-- <execution>
-- <phase>integration-test</phase>
-- <configuration>
-- <tasks>
-- <property name="test.classpath" refid="maven.test.classpath" />
-- <echo>Validating plugins...</echo>
-- <java classname="org.rhq.core.pc.plugin.PluginValidator" failonerror="true" fork="true">
-- <classpath>
-- <pathelement path="${test.classpath}" />
-- <pathelement location="hibernate/target/jopr-hibernate-plugin-${project.version}.jar" />
-- <pathelement location="tomcat/target/jopr-tomcat-plugin-${project.version}.jar" />
-- <pathelement location="jboss-as/target/jopr-jboss-as-plugin-${project.version}.jar" />
-- <!-- TODO: Figure out why this is causing NoClassDefFoundErrors and fix it. -->
-- <!--<pathelement location="jboss-as-5/target/jopr-jboss-as-5-plugin-${project.version}.jar" />-->
-- <pathelement location="rhq-server/target/jopr-rhq-server-plugin-${project.version}.jar" />
-- </classpath>
-- <sysproperty key="org.apache.commons.logging.Log" value="org.apache.commons.logging.impl.SimpleLog" />
-- </java>
-- </tasks>
-- </configuration>
-- <goals>
-- <goal>run</goal>
-- </goals>
-- </execution>
-- </executions>
-- </plugin>
-- </plugins>
-- </build>
--
--</project>
++<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
++ <modelVersion>4.0.0</modelVersion>
++
++ <parent>
++ <groupId>org.jboss.on</groupId>
++ <artifactId>jopr-modules-parent</artifactId>
++ <version>2.4.0-SNAPSHOT</version>
++ </parent>
++
++ <!-- Plugin poms should not set their parent pom to this artifact. Instead, set to "jopr-modules-parent". This is due to
++ the fact that this pom runs integration tests *on* the plugins. This pom must process in the reactor after the
++ plugins themselves, as such, it can't be their parent. -->
++ <groupId>org.jboss.on</groupId>
++ <artifactId>jopr-plugins-parent</artifactId>
++ <version>2.3.0.EmbJopr.1.3.0-1</version>
++ <packaging>pom</packaging>
++ <name>Jopr Plugins</name>
++ <description>parent POM for all Jopr plugins</description>
++
++ <dependencies>
++ <dependency>
++ <groupId>${rhq.groupId}</groupId>
++ <artifactId>rhq-jmx-plugin</artifactId>
++ <version>${rhq.version}</version>
++ <scope>test</scope>
++ </dependency>
++ </dependencies>
++
++ <!-- plugins - order is important -->
++ <modules>
++ <module>tomcat</module>
++ <module>jboss-as</module>
++ <module>hibernate</module>
++ <module>rhq-server</module>
++ <module>jboss-cache</module>
++ <module>jboss-as-5</module>
++ <module>jboss-cache-v3</module>
++ </modules>
++
++ <build>
++ <plugins>
++ <plugin>
++ <artifactId>maven-antrun-plugin</artifactId>
++ <executions>
++ <execution>
++ <phase>integration-test</phase>
++ <configuration>
++ <tasks>
++ <property name="test.classpath" refid="maven.test.classpath" />
++ <echo>Validating plugins...</echo>
++ <java classname="org.rhq.core.pc.plugin.PluginValidator" failonerror="true" fork="true">
++ <classpath>
++ <pathelement path="${test.classpath}" />
++ <pathelement location="hibernate/target/jopr-hibernate-plugin-${project.version}.jar" />
++ <pathelement location="tomcat/target/jopr-tomcat-plugin-${project.version}.jar" />
++ <pathelement location="jboss-as/target/jopr-jboss-as-plugin-${project.version}.jar" />
++ <!-- TODO: Figure out why this is causing NoClassDefFoundErrors and fix it. -->
++ <!--<pathelement location="jboss-as-5/target/jopr-jboss-as-5-plugin-${project.version}.jar" />-->
++ <pathelement location="rhq-server/target/jopr-rhq-server-plugin-${project.version}.jar" />
++ </classpath>
++ <sysproperty key="org.apache.commons.logging.Log" value="org.apache.commons.logging.impl.SimpleLog" />
++ </java>
++ </tasks>
++ </configuration>
++ <goals>
++ <goal>run</goal>
++ </goals>
++ </execution>
++ </executions>
++ </plugin>
++ </plugins>
++ </build>
++
++</project>
diff --cc modules/jopr/modules-pom.xml
index 7571c50,67e763b..de9ca10
--- a/modules/jopr/modules-pom.xml
+++ b/modules/jopr/modules-pom.xml
@@@ -1,131 -1,124 +1,124 @@@
--<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
--
-- <modelVersion>4.0.0</modelVersion>
--
-- <parent>
-- <groupId>org.jboss.on</groupId>
-- <artifactId>jopr-parent</artifactId>
-- <version>2.4.0-SNAPSHOT</version>
-- </parent>
--
-- <groupId>org.jboss.on</groupId>
-- <artifactId>jopr-modules-parent</artifactId>
-- <packaging>pom</packaging>
--
-- <name>Jopr Modules</name>
-- <description>parent POM for all Jopr modules</description>
--
- <scm>
- <connection>scm:svn:http://svn.jboss.org/repos/jopr/trunk/modules/</connection>
- <developerConnection>scm:svn:https://svn.jboss.org/repos/jopr/trunk/modules/</developerConnection>
- </scm>
-
-- <properties>
- <scm.module.path>modules/</scm.module.path>
-
-- <!-- dependency versions -->
-- <hibernate-all.version>1.0.0.Alpha9</hibernate-all.version>
-- <persistence-api.version>1.0</persistence-api.version>
-- </properties>
--
-- <!-- These dependencies are (mostly) common to the plugin artifacts built under the
-- Jopr umbrella. They are not specific to the build a one particular artifact but
-- placed here to simplify the child poms. -->
-- <dependencies>
--
-- <dependency>
-- <groupId>${rhq.groupId}</groupId>
-- <artifactId>rhq-core-util</artifactId>
-- <version>${rhq.version}</version>
-- <scope>provided</scope> <!-- by PC -->
-- </dependency>
--
-- <dependency>
-- <groupId>${rhq.groupId}</groupId>
-- <artifactId>rhq-core-domain</artifactId>
-- <version>${rhq.version}</version>
-- <scope>provided</scope> <!-- by PC -->
-- </dependency>
--
-- <dependency>
-- <groupId>${rhq.groupId}</groupId>
-- <artifactId>rhq-core-plugin-api</artifactId>
-- <version>${rhq.version}</version>
-- <scope>provided</scope> <!-- by PC -->
-- </dependency>
--
-- <dependency>
-- <groupId>${rhq.groupId}</groupId>
-- <artifactId>rhq-core-native-system</artifactId>
-- <version>${rhq.version}</version>
-- <scope>provided</scope> <!-- by PC -->
-- </dependency>
--
-- <dependency>
-- <groupId>${rhq.groupId}</groupId>
-- <artifactId>rhq-core-plugin-container</artifactId>
-- <version>${rhq.version}</version>
-- <scope>test</scope>
-- </dependency>
--
-- <!-- Third-party libs -->
-- <dependency>
-- <groupId>javax.xml.bind</groupId>
-- <artifactId>jaxb-api</artifactId>
-- <scope>provided</scope>
-- </dependency>
--
-- <dependency>
-- <groupId>com.sun.xml.bind</groupId>
-- <artifactId>jaxb-impl</artifactId>
-- <!-- NOTE: The version is defined in the root POM's dependencyManagement section. -->
-- <scope>provided</scope>
-- </dependency>
--
-- <!-- TODO: This is a fix for the Javac bug requiring annotations to be available when compiling dependent classes. It is fixed in JDK 6. -->
-- <dependency>
-- <groupId>javax.persistence</groupId>
-- <artifactId>persistence-api</artifactId>
-- <version>${persistence-api.version}</version>
-- <scope>provided</scope> <!-- by ON container -->
-- </dependency>
--
-- <!-- TODO: This is a fix for the Javac bug requiring annotations to be available when compiling dependent classes; it is fixed in JDK 6. -->
-- <dependency>
-- <groupId>jboss.jboss-embeddable-ejb3</groupId>
-- <artifactId>hibernate-all</artifactId>
-- <version>${hibernate-all.version}</version>
-- <scope>provided</scope> <!-- by ON container -->
-- </dependency>
--
-- </dependencies>
--
-- <modules>
-- <module>common</module>
-- <module>plugins</module>
-- </modules>
--
-- <profiles>
--
-- <!-- enterprise-only stuff -->
-- <profile>
-- <id>enterprise</id>
-- <activation>
-- <activeByDefault>true</activeByDefault>
-- </activation>
-- <modules>
-- <module>serverplugins</module>
-- </modules>
-- </profile>
--
-- <!-- same as enterprise profile, except includes dist -->
-- <profile>
-- <id>dist</id>
-- <modules>
-- <module>serverplugins</module>
-- <module>dist</module>
-- </modules>
-- </profile>
--
-- </profiles>
--</project>
++<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
++
++ <modelVersion>4.0.0</modelVersion>
++
++ <parent>
++ <groupId>org.jboss.on</groupId>
++ <artifactId>jopr-parent</artifactId>
++ <version>2.4.0-SNAPSHOT</version>
++ </parent>
++
++ <groupId>org.jboss.on</groupId>
++ <artifactId>jopr-modules-parent</artifactId>
++ <packaging>pom</packaging>
++
++ <name>Jopr Modules</name>
++ <description>parent POM for all Jopr modules</description>
++
++ <properties>
++ <!-- dependency versions -->
++ <hibernate-all.version>1.0.0.Alpha9</hibernate-all.version>
++ <persistence-api.version>1.0</persistence-api.version>
++ </properties>
++
++ <!-- These dependencies are (mostly) common to the plugin artifacts built under the
++ Jopr umbrella. They are not specific to the build a one particular artifact but
++ placed here to simplify the child poms. -->
++ <dependencies>
++
++ <dependency>
++ <groupId>${rhq.groupId}</groupId>
++ <artifactId>rhq-core-util</artifactId>
++ <version>${rhq.version}</version>
++ <scope>provided</scope> <!-- by PC -->
++ </dependency>
++
++ <dependency>
++ <groupId>${rhq.groupId}</groupId>
++ <artifactId>rhq-core-domain</artifactId>
++ <version>${rhq.version}</version>
++ <scope>provided</scope> <!-- by PC -->
++ </dependency>
++
++ <dependency>
++ <groupId>${rhq.groupId}</groupId>
++ <artifactId>rhq-core-plugin-api</artifactId>
++ <version>${rhq.version}</version>
++ <scope>provided</scope> <!-- by PC -->
++ </dependency>
++
++ <dependency>
++ <groupId>${rhq.groupId}</groupId>
++ <artifactId>rhq-core-native-system</artifactId>
++ <version>${rhq.version}</version>
++ <scope>provided</scope> <!-- by PC -->
++ </dependency>
++
++ <dependency>
++ <groupId>${rhq.groupId}</groupId>
++ <artifactId>rhq-core-plugin-container</artifactId>
++ <version>${rhq.version}</version>
++ <scope>test</scope>
++ </dependency>
++
++ <!-- Third-party libs -->
++ <dependency>
++ <groupId>javax.xml.bind</groupId>
++ <artifactId>jaxb-api</artifactId>
++ <scope>provided</scope>
++ </dependency>
++
++ <dependency>
++ <groupId>com.sun.xml.bind</groupId>
++ <artifactId>jaxb-impl</artifactId>
++ <!-- NOTE: The version is defined in the root POM's dependencyManagement section. -->
++ <scope>provided</scope>
++ </dependency>
++
++ <!-- TODO: This is a fix for the Javac bug requiring annotations to be available when compiling dependent classes. It is fixed in JDK 6. -->
++ <dependency>
++ <groupId>javax.persistence</groupId>
++ <artifactId>persistence-api</artifactId>
++ <version>${persistence-api.version}</version>
++ <scope>provided</scope> <!-- by ON container -->
++ </dependency>
++
++ <!-- TODO: This is a fix for the Javac bug requiring annotations to be available when compiling dependent classes; it is fixed in JDK 6. -->
++ <dependency>
++ <groupId>jboss.jboss-embeddable-ejb3</groupId>
++ <artifactId>hibernate-all</artifactId>
++ <version>${hibernate-all.version}</version>
++ <scope>provided</scope> <!-- by ON container -->
++ </dependency>
++
++ </dependencies>
++
++ <modules>
++ <module>common</module>
++ <module>plugins</module>
++ </modules>
++
++ <profiles>
++
++ <!-- enterprise-only stuff -->
++ <profile>
++ <id>enterprise</id>
++ <activation>
++ <activeByDefault>true</activeByDefault>
++ </activation>
++ <modules>
++ <module>serverplugins</module>
++ </modules>
++ </profile>
++
++ <!-- same as enterprise profile, except includes dist -->
++ <profile>
++ <id>dist</id>
++ <modules>
++ <module>serverplugins</module>
++ <module>dist</module>
++ </modules>
++ </profile>
++
++ </profiles>
++</project>
commit 123ac05d5c93d5f66961e435ccfb67d907c86287
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Fri Nov 25 10:23:05 2011 +0100
Turn down logging. Prevent a LazyLoadException for schedules.
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 e670afb..ce4dd43 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
@@ -146,7 +146,7 @@ public class AbstractRestBean {
protected <T>T getFromCache(Fqn fqn,Class<T> clazz) {
Object o=null;
if (treeCache.exists(fqn)) {
- log.info("Hit for " + fqn.toString());
+ log.debug("Hit for " + fqn.toString());
try{
Node n = treeCache.get(fqn);
Set<Integer> readers= (Set<Integer>) n.get("readers");
@@ -154,11 +154,10 @@ public class AbstractRestBean {
o = n.get("item");
}
else {
- log.info("No object for caller " +caller.toString() + " found");
+ log.debug("No object for caller " +caller.toString() + " found");
}
} catch (CacheException e) {
- e.printStackTrace(); // TODO: Customise this generated block
- log.info("Miss for " + fqn.toString());
+ log.debug("Miss for " + fqn.toString());
}
}
return (T)o;
@@ -187,9 +186,9 @@ public class AbstractRestBean {
treeCache.put(fqn,"readers",readers);
treeCache.put(fqn,"item",o);
success = true;
- log.info("Put " + fqn);
+ log.debug("Put " + fqn);
} catch (CacheException e) {
- e.printStackTrace(); // TODO: Customise this generated block
+ log.warn(e.getMessage());
}
return success;
}
@@ -235,7 +234,7 @@ public class AbstractRestBean {
childParentMap.put(res.getId(), pid);
treeCache.put(resourceMeta,"childParentMap",childParentMap);
} catch (CacheException e) {
- e.printStackTrace(); // TODO: Customise this generated block
+ log.warn(e.getMessage());
}
}
@@ -269,7 +268,7 @@ public class AbstractRestBean {
ret.add(getFromCache(resId, Resource.class));
}
} catch (CacheException e) {
- e.printStackTrace(); // TODO: Customise this generated block
+ log.warn(e.getMessage());
}
}
@@ -287,7 +286,7 @@ public class AbstractRestBean {
if (visibleResources.contains(resourceid))
res = getFromCache(resourceid,Resource.class);
} catch (CacheException e) {
- e.printStackTrace(); // TODO: Customise this generated block
+ log.warn(e.getMessage());
}
}
@@ -316,7 +315,7 @@ public class AbstractRestBean {
if (treeCache.exists(fqn)) {
try {
treeCache.remove(fqn);
- log.info("Cancel " + fqn);
+ log.debug("Cancel " + fqn);
return true;
} catch (CacheException e) {
return false;
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 e500a33..91ed23a 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
@@ -200,7 +200,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
if (scheduleType.equals("metric"))
scheduleType=DataType.MEASUREMENT.toString().toLowerCase();
- Resource res = fetchResource(resourceId);
+ Resource res = resMgr.getResource(caller,resourceId); // Don't fetch(), as this would yield a LazyLoadException
Set<MeasurementSchedule> schedules = res.getSchedules();
List<MetricSchedule> ret = new ArrayList<MetricSchedule>(schedules.size());
@@ -290,6 +290,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
Link link = new Link();
link.setRel("alert");
link.setHref("/alert/" + al.getId());
+ links.add(link);
}
return links;
}
commit cfbb12d5a736b1f01415ef33fbe7a0b0f8078380
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Mon Nov 21 10:46:18 2011 +0100
Tried to do some stuff wrt caching, simple operations can now be scheduled.
Fix operation history retrieval
Tiny cleanup.
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
index f458a8e..4381fbb 100644
--- 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
@@ -123,8 +123,8 @@
<attribute name="wakeUpIntervalSeconds">5</attribute>
<!-- Cache wide default -->
<region name="/_default_">
- <attribute name="maxNodes">5000</attribute>
- <attribute name="timeToLiveSeconds">1000</attribute>
+ <attribute name="maxNodes">50000</attribute>
+ <attribute name="timeToLiveSeconds">3600</attribute>
</region>
<region name="/schedules">
<attribute name="maxNodes">5000</attribute>
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 1be4db0..e670afb 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,12 +25,14 @@ package org.rhq.enterprise.server.rest;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Set;
-import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
@@ -51,6 +53,7 @@ import org.jboss.cache.Node;
import org.jboss.cache.TreeCacheMBean;
import org.rhq.core.domain.auth.Subject;
+import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.enterprise.server.rest.domain.Link;
import org.rhq.enterprise.server.rest.domain.ResourceWithType;
@@ -61,7 +64,7 @@ import org.rhq.enterprise.server.rest.domain.ResourceWithType;
* @author Heiko W. Rupp
*/
@Stateless
-@Resource(name="cache",type= TreeCacheMBean.class,mappedName = "RhqCache")
+(a)javax.annotation.Resource(name="cache",type= TreeCacheMBean.class,mappedName = "RhqCache")
public class AbstractRestBean {
Log log = LogFactory.getLog(getClass().getName());
@@ -70,7 +73,7 @@ public class AbstractRestBean {
Subject caller;
/** The cache to use */
- @Resource(name="cache")
+ @javax.annotation.Resource(name="cache")
TreeCacheMBean treeCache;
/**
@@ -204,6 +207,94 @@ public class AbstractRestBean {
return putToCache(fqn,o);
}
+ protected void putResourceToCache(Resource res) {
+ putToCache(res.getId(),Resource.class,res);
+
+ Fqn callerFqn = new Fqn(new String[]{"user", String.valueOf(caller.getId()),"resources"});
+ Set<Integer> visibleResources;
+ try {
+ if (treeCache.exists(callerFqn)) {
+ Node n = treeCache.get(callerFqn);
+ visibleResources = (Set<Integer>) n.get("visibleResources");
+ }
+ else {
+ visibleResources = new HashSet<Integer>();
+ }
+ visibleResources.add(res.getId());
+ treeCache.put(callerFqn,"visibleResources",visibleResources);
+
+ Fqn resourceMeta = new Fqn(new String[] { "resourceMeta"});
+ Map<Integer,Integer> childParentMap;
+ if (treeCache.exists(resourceMeta)) {
+ childParentMap = (Map<Integer, Integer>) treeCache.get(resourceMeta,"childParentMap");
+ }
+ else {
+ childParentMap = new HashMap<Integer,Integer>();
+ }
+ int pid = res.getParentResource() == null ? 0 : res.getParentResource().getId();
+ childParentMap.put(res.getId(), pid);
+ treeCache.put(resourceMeta,"childParentMap",childParentMap);
+ } catch (CacheException e) {
+ e.printStackTrace(); // TODO: Customise this generated block
+ }
+ }
+
+ protected List<Resource> getResourcesFromCacheByParentId(int pid) {
+ List<Integer> candidateIds = new ArrayList<Integer>();
+ List<Resource> ret = new ArrayList<Resource>();
+
+ // First determine candidate children
+ Map<Integer,Integer> childParentMap;
+ Fqn resourceMeta = new Fqn(new String[] { "resourceMeta"});
+ if (treeCache.exists(resourceMeta)) {
+ try {
+ childParentMap = (Map<Integer, Integer>) treeCache.get(resourceMeta,"childParentMap");
+ for (Map.Entry<Integer,Integer> entry : childParentMap.entrySet()) {
+ if (entry.getValue() == pid)
+ candidateIds.add(entry.getKey());
+ }
+ // then see if the current user can see them
+ Fqn callerFqn = new Fqn(new String[]{"user", String.valueOf(caller.getId()),"resources"});
+ Set<Integer> visibleResources = (Set<Integer>) treeCache.get(callerFqn,"visibleResources");
+ Iterator<Integer> iter = candidateIds.iterator();
+ while (iter.hasNext()) {
+ Integer resId = iter.next();
+ if (!visibleResources.contains(resId)) {
+ iter.remove();
+ }
+ }
+
+ // Last but not least, get the resources and return them
+ for (Integer resId : candidateIds) {
+ ret.add(getFromCache(resId, Resource.class));
+ }
+ } catch (CacheException e) {
+ e.printStackTrace(); // TODO: Customise this generated block
+ }
+
+ }
+ return ret;
+ }
+
+ protected Resource getResourceFromCache(int resourceid) {
+
+ Resource res = null;
+ // check if the current user can see the resource
+ Fqn callerFqn = new Fqn(new String[]{"user", String.valueOf(caller.getId()),"resources"});
+ if (treeCache.exists(callerFqn)) {
+ try {
+ Set<Integer> visibleResources = (Set<Integer>) treeCache.get(callerFqn,"visibleResources");
+ if (visibleResources.contains(resourceid))
+ res = getFromCache(resourceid,Resource.class);
+ } catch (CacheException e) {
+ e.printStackTrace(); // TODO: Customise this generated block
+ }
+ }
+
+ return res;
+ }
+
+
/**
* Construct a Fqn object from the passed data
* @param id Id of the target object
@@ -244,6 +335,8 @@ public class AbstractRestBean {
if (parent!=null) {
rwt.setParentId(parent.getId());
}
+ else
+ rwt.setParentId(0);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/operation/definitions");
uriBuilder.queryParam("resourceId",res.getId());
@@ -251,6 +344,38 @@ public class AbstractRestBean {
Link link = new Link("operationDefinitions",uri.toString());
rwt.addLink(link);
+/*
+ for (Resource child : getResourcesFromCacheByParentId(res.getId())) {
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/resource/{id}");
+ uri = uriBuilder.build(child.getId());
+ link = new Link("child",uri.toString());
+ rwt.addLink(link);
+ }
+*/
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/resource/{id}");
+ uri = uriBuilder.build(res.getId());
+ link = new Link("self",uri.toString());
+ rwt.addLink(link);
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/resource/{id}/schedules");
+ uri = uriBuilder.build(res.getId());
+ link = new Link("schedules",uri.toString());
+ rwt.addLink(link);
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/resource/{id}/children");
+ uri = uriBuilder.build(res.getId());
+ link = new Link("children",uri.toString());
+ rwt.addLink(link);
+ if (parent!=null) {
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/resource/{id}/");
+ uri = uriBuilder.build(parent.getId());
+ link = new Link("parent",uri.toString());
+ rwt.addLink(link);
+ }
+
return rwt;
}
}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
index 627c22b..d742139 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
@@ -1,7 +1,5 @@
package org.rhq.enterprise.server.rest;
-import java.util.List;
-
import javax.ejb.Local;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
@@ -10,7 +8,6 @@ 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;
@@ -18,9 +15,9 @@ import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-import org.rhq.core.domain.resource.group.ResourceGroup;
+import org.jboss.resteasy.annotations.cache.Cache;
+
import org.rhq.enterprise.server.rest.domain.GroupRest;
-import org.rhq.enterprise.server.rest.domain.ResourceWithType;
/**
* Handler for Group related things
@@ -36,6 +33,7 @@ public interface GroupHandlerLocal {
public Response getGroups(@Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo);
+ @Cache(isPrivate = true,maxAge = 60)
@GET
@Path("{id}")
public Response getGroup(@PathParam("id") int id,@Context Request request, @Context HttpHeaders headers,
@@ -61,6 +59,7 @@ public interface GroupHandlerLocal {
@GET
@Path("{id}/resources")
+ @Cache(isPrivate = true,maxAge = 60)
public Response getResources(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo);
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
index 2a4f7df..536274a 100644
--- 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
@@ -9,12 +9,13 @@ import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
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.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
@@ -22,9 +23,17 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
+import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.configuration.definition.PropertyDefinition;
+import org.rhq.core.domain.criteria.OperationHistoryCriteria;
+import org.rhq.core.domain.criteria.ResourceOperationHistoryCriteria;
+import org.rhq.core.domain.operation.HistoryJobId;
+import org.rhq.core.domain.operation.JobId;
import org.rhq.core.domain.operation.OperationDefinition;
+import org.rhq.core.domain.operation.OperationHistory;
+import org.rhq.core.domain.operation.ResourceOperationHistory;
+import org.rhq.core.domain.operation.bean.ResourceOperationSchedule;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.enterprise.server.operation.OperationManagerLocal;
@@ -86,19 +95,14 @@ public class OperationsHandlerBean extends AbstractRestBean implements Operation
}
- 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) {
+ public Response getOperationDefinitions(Integer resourceId, UriInfo uriInfo, Request request) {
if (resourceId == null)
throw new ParameterMissingException("resourceId");
@@ -109,16 +113,15 @@ public class OperationsHandlerBean extends AbstractRestBean implements Operation
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) {
+ putToCache(def.getId(),OperationDefinition.class,def);
OperationDefinitionRest odr = new OperationDefinitionRest();
odr.setId(def.getId());
odr.setName(def.getName());
@@ -130,21 +133,13 @@ public class OperationsHandlerBean extends AbstractRestBean implements Operation
resultList.add(odr);
}
- return resultList;
-
-/*
- builder = Response.ok(resultList);
+ GenericEntity<List<OperationDefinitionRest>> entity = new GenericEntity<List<OperationDefinitionRest>>(resultList){};
+ builder = Response.ok(entity);
}
- 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();
-*/
}
@@ -203,19 +198,30 @@ public class OperationsHandlerBean extends AbstractRestBean implements Operation
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);
-
+ operation.setState("ready");
}
- UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
- uriBuilder.path("/operation/{id}");
- URI uri = uriBuilder.build(operation.getId());
- Link editLink = new Link("edit",uri.toString());
- operation.addLink(editLink);
+ if (operation.getState().equals("ready")) {
+ // todo check params
+
+ // submit
+ ResourceOperationSchedule sched = opsManager.scheduleResourceOperation(caller,operation.getResourceId(),operation.getName(),0,0,0,-1,new Configuration(),"TEst");
+ JobId jobId = new JobId(sched.getJobName(),sched.getJobGroup());
+ UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/operation/history/{id}");
+ URI uri = uriBuilder.build(jobId);
+ Link histLink = new Link("history",uri.toString());
+ operation.addLink(histLink);
+ }
+ else {
+ 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();
}
@@ -233,4 +239,30 @@ public class OperationsHandlerBean extends AbstractRestBean implements Operation
return null; // TODO: Customise this generated block
}
+ @Override
+ @GET
+ @Path("history/{id}")
+ public Response outcome(@PathParam("id") String jobName) {
+
+/*
+ ResourceOperationSchedule schedule = opsManager.getResourceOperationSchedule(caller,operationHistoryId);
+ if (schedule==null)
+ throw new StuffNotFoundException("OperationSchedule with id " + operationHistoryId);
+*/
+
+ ResourceOperationHistoryCriteria criteria = new ResourceOperationHistoryCriteria();
+ criteria.addFilterJobId(new JobId(jobName));
+
+ OperationHistory history ;//= opsManager.getOperationHistoryByJobId(caller,jobName);
+ List<ResourceOperationHistory> list = opsManager.findResourceOperationHistoriesByCriteria(caller,criteria);
+ if (list==null || list.isEmpty())
+ throw new StuffNotFoundException("OperationHistory with id " + new HistoryJobId(jobName));
+
+ history = list.get(0);
+ String ret = history.getStatus().getDisplayName();
+ // TODO get history.results with the help of the property definition
+
+ return Response.ok(ret).build();
+
+ }
}
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
index 5ab7366..1d2a9ed 100644
--- 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
@@ -19,6 +19,8 @@ import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+import org.jboss.resteasy.annotations.cache.Cache;
+
import org.rhq.enterprise.server.rest.domain.OperationDefinitionRest;
import org.rhq.enterprise.server.rest.domain.OperationRest;
@@ -28,12 +30,13 @@ import org.rhq.enterprise.server.rest.domain.OperationRest;
*/
@Local
@Path("/operation")
+(a)Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public interface OperationsHandlerLocal {
@GET
@Path("definition/{id}")
- @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
+ @Cache(maxAge = 1200)
public Response getOperationDefinition(@PathParam("id") int definitionId,
@QueryParam("resourceId") Integer resourceId,
@Context UriInfo uriInfo,
@@ -42,33 +45,32 @@ public interface OperationsHandlerLocal {
@GET
@Path("definitions")
- @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
- public List<OperationDefinitionRest> getOperationDefinitions(@QueryParam("resourceId") Integer resourceId,
+ @Cache(maxAge = 1200)
+ public Response 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);
+ @GET
+ @Path("history/{id}")
+ public Response outcome(@PathParam("id") String jobName);
}
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 f90143a..e500a33 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
@@ -31,7 +31,6 @@ 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.GenericEntity;
@@ -42,8 +41,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
-import com.arjuna.ats.internal.jdbc.drivers.modifiers.list;
-
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.criteria.AlertCriteria;
import org.rhq.core.domain.measurement.Availability;
@@ -85,29 +82,14 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
public Response getResource(int id, @Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo) {
- // Create a cache control
- CacheControl cc = new CacheControl();
- cc.setMaxAge(300); // Resources are valid for 5 mins
- cc.setPrivate(false); // Proxies may cache this
-
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);
- }
-
- Response.ResponseBuilder builder=null;
+ res = fetchResource(id);
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);
+ Response.ResponseBuilder builder = request.evaluatePreconditions(new Date(mtime),eTag);
if (builder!=null) {
- builder.cacheControl(cc);
return builder.build();
}
@@ -218,7 +200,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
if (scheduleType.equals("metric"))
scheduleType=DataType.MEASUREMENT.toString().toLowerCase();
- Resource res = resMgr.getResource(caller, resourceId);
+ Resource res = fetchResource(resourceId);
Set<MeasurementSchedule> schedules = res.getSchedules();
List<MetricSchedule> ret = new ArrayList<MetricSchedule>(schedules.size());
@@ -273,18 +255,10 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
@Context UriInfo uriInfo) {
PageControl pc = new PageControl();
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);
- }
+ parent = fetchResource(id);
List<Resource> ret = resMgr.findResourceByParentAndInventoryStatus(caller,parent,InventoryStatus.COMMITTED,pc);
List<ResourceWithType> rwtList = new ArrayList<ResourceWithType>(ret.size());
for (Resource r: ret) {
- putToCache(r.getId(),Resource.class,r);
ResourceWithType rwt = fillRWT(r, uriInfo);
rwtList.add(rwt);
}
@@ -319,4 +293,18 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
}
return links;
}
+
+ private Resource fetchResource(int resourceId) {
+ Resource res;
+ res = getFromCache(resourceId,Resource.class);
+ if (res==null) {
+ res = resMgr.getResource(caller, resourceId);
+ if (res!=null)
+ putToCache(resourceId, Resource.class,res);
+ else
+ throw new StuffNotFoundException("Resource with id " + resourceId);
+ }
+ return res;
+ }
+
}
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 412b347..08a1905 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
@@ -34,6 +34,7 @@ import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
+import org.jboss.resteasy.annotations.cache.Cache;
import org.jboss.resteasy.links.AddLinks;
import org.jboss.resteasy.links.LinkResource;
import org.jboss.resteasy.links.LinkResources;
@@ -59,12 +60,13 @@ public interface ResourceHandlerLocal {
})
@GET
@Path("/{id}")
-
+ @Cache(isPrivate = true,maxAge = 120)
Response getResource(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo);
@GET
@Path("/platforms")
+ @Cache(isPrivate = true,maxAge = 300)
Response getPlatforms(@Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo);
@@ -82,6 +84,7 @@ public interface ResourceHandlerLocal {
@GET
@Path("/{id}/schedules")
@LinkResource(rel="schedules",value = MetricSchedule.class)
+ @Cache(isPrivate = true,maxAge = 60)
Response getSchedules(@PathParam("id") int resourceId,
@QueryParam("type") @DefaultValue("all") String scheduleType,
@Context Request request,
commit 1527222e6a0c560401e29300ffc26e1ac807a1e9
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Tue Nov 15 10:22:42 2011 +0100
Use the correct data type as input for group creation. Add some more attributes to a group.
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
index 5ecbb86..76180d9 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
@@ -28,8 +28,11 @@ import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.criteria.ResourceGroupCriteria;
import org.rhq.core.domain.resource.Resource;
+import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.domain.resource.group.ResourceGroup;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
+import org.rhq.enterprise.server.resource.ResourceTypeManagerLocal;
+import org.rhq.enterprise.server.resource.ResourceTypeNotFoundException;
import org.rhq.enterprise.server.resource.group.ResourceGroupDeleteException;
import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal;
import org.rhq.enterprise.server.rest.domain.GroupRest;
@@ -52,6 +55,9 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
@EJB
ResourceManagerLocal resourceManager;
+ @EJB
+ ResourceTypeManagerLocal resourceTypeManager;
+
public Response getGroups(@Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
ResourceGroupCriteria criteria = new ResourceGroupCriteria();
@@ -104,19 +110,33 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
@Override
@POST
@Path("/")
- public Response createGroup(ResourceGroup group, @Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
+ public Response createGroup(GroupRest group, @Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
+
+ ResourceGroup newGroup = new ResourceGroup(group.getName());
+ if (group.getResourceTypeId()!=null) {
+
+ ResourceType resourceType = null;
+ try {
+ resourceType = resourceTypeManager.getResourceTypeById(caller,group.getResourceTypeId());
+ newGroup.setResourceType(resourceType);
+ } catch (ResourceTypeNotFoundException e) {
+ e.printStackTrace(); // TODO: Customise this generated block
+ throw new StuffNotFoundException("ResourceType with id " + group.getResourceTypeId());
+ }
+ }
Response.ResponseBuilder builder;
try {
- ResourceGroup newGroup = resourceGroupManager.createResourceGroup(caller,group);
+ newGroup = resourceGroupManager.createResourceGroup(caller,newGroup);
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/group/{id}");
URI uri = uriBuilder.build(newGroup.getId());
builder=Response.created(uri);
+ putToCache(newGroup.getId(),ResourceGroup.class,newGroup);
} catch (Exception e) {
e.printStackTrace(); // TODO: Customise this generated block
- builder=Response.serverError();
+ builder=Response.status(Response.Status.NOT_ACCEPTABLE);
}
return builder.build();
}
@@ -135,6 +155,7 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
try {
resourceGroup = resourceGroupManager.updateResourceGroup(caller,resourceGroup);
builder=Response.ok(fillGroup(resourceGroup,uriInfo));
+ putToCache(resourceGroup.getId(),ResourceGroup.class,resourceGroup);
}
catch (Exception e) {
builder = Response.status(Response.Status.NOT_ACCEPTABLE); // TODO correct?
@@ -150,6 +171,7 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
try {
resourceGroupManager.deleteResourceGroup(caller,id);
+ removeFromCache(id,ResourceGroup.class);
return Response.ok().build();
} catch (ResourceGroupDeleteException e) {
e.printStackTrace(); // TODO: Customise this generated block
@@ -196,8 +218,13 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
if (res==null)
throw new StuffNotFoundException("Resource with id " + resourceId);
- // TODO do we want/ need to check that the group category stays the same?
+ // A resource type is set for the group, so only allow to add resources with the same type.
+ if (resourceGroup.getResourceType()!=null) {
+ if (!res.getResourceType().equals(resourceGroup.getResourceType()))
+ return Response.status(Response.Status.NOT_ACCEPTABLE).build();
+ }
+ // TODO if comp group and no resourceTypeId set, shall we allow to have it change to a mixed group?
resourceGroup.addExplicitResource(res);
return Response.ok().build(); // TODO right code?
@@ -246,6 +273,7 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
GroupRest gr = new GroupRest(group.getName());
gr.setId(group.getId());
gr.setCategory(group.getGroupCategory());
+ gr.setRecursive(group.isRecursive());
UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
uriBuilder.path("/group/{id}");
URI uri = uriBuilder.build(group.getId());
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
index 88a3610..627c22b 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
@@ -44,7 +44,7 @@ public interface GroupHandlerLocal {
@POST
@Path("/")
- public Response createGroup(ResourceGroup group, @Context Request request, @Context HttpHeaders headers,
+ public Response createGroup(GroupRest group, @Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo);
@PUT
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java
index c55223d..4c9079b 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java
@@ -18,6 +18,8 @@ public class GroupRest {
int id;
private String name;
+ private Integer resourceTypeId;
+ private boolean recursive;
private GroupCategory category;
List<Link> links = new ArrayList<Link>();
@@ -45,6 +47,22 @@ public class GroupRest {
this.name = name;
}
+ public Integer getResourceTypeId() {
+ return resourceTypeId;
+ }
+
+ public void setResourceTypeId(Integer resourceTypeId) {
+ this.resourceTypeId = resourceTypeId;
+ }
+
+ public boolean isRecursive() {
+ return recursive;
+ }
+
+ public void setRecursive(boolean recursive) {
+ this.recursive = recursive;
+ }
+
public GroupCategory getCategory() {
return category;
}
diff --git a/modules/enterprise/server/jar/src/main/resources/rest_templates/group.ftl b/modules/enterprise/server/jar/src/main/resources/rest_templates/group.ftl
index 90bbfac..6edaf99 100644
--- a/modules/enterprise/server/jar/src/main/resources/rest_templates/group.ftl
+++ b/modules/enterprise/server/jar/src/main/resources/rest_templates/group.ftl
@@ -36,6 +36,14 @@
<tr>
<td>Category</td><td>${var.category}</td>
</tr>
+ <#if var.resourceTypeId??>
+ <tr>
+ <td>ResourceType id</td><td></td>
+ </tr>
+ </#if>
+ <tr>
+ <td>Recursive</td><td>${var.recursive?string("Yes","No")}</td>
+ </tr>
</table>
<a href="/rest/1/group/${var.id?c}/resources.html">Resources</a><br/>
</html>
commit 7317b864fa22275d1784edb410d37ef65c84c6d2
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Mon Nov 14 22:55:17 2011 +0100
Do some caching of the group.
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
index ea3d2ec..5ecbb86 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
@@ -228,9 +228,14 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
* @throws StuffNotFoundException if the group is not found (or not accessible by the caller)
*/
private ResourceGroup fetchGroup(int groupId) {
- ResourceGroup resourceGroup = resourceGroupManager.getResourceGroup(caller, groupId);
- if (resourceGroup==null)
- throw new StuffNotFoundException("Group with id " + groupId);
+ ResourceGroup resourceGroup;
+ resourceGroup = getFromCache(groupId,ResourceGroup.class);
+ if (resourceGroup==null) {
+ resourceGroup = resourceGroupManager.getResourceGroup(caller, groupId);
+ if (resourceGroup==null)
+ throw new StuffNotFoundException("Group with id " + groupId);
+ putToCache(groupId,ResourceGroup.class,resourceGroup);
+ }
return resourceGroup;
}
commit 2920cb4967c49b083198111032d62bc8e8426b4e
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Mon Nov 14 21:55:30 2011 +0100
Allow updates to the group name.
Remove non-needed method.
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
index a227a18..ea3d2ec 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
@@ -124,9 +124,22 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
@Override
@PUT
@Path("{id}")
- public Response updateGroup(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
+ public Response updateGroup(@PathParam("id") int id, GroupRest in, @Context Request request,
+ @Context HttpHeaders headers,
@Context UriInfo uriInfo) {
- return null; // TODO: Customise this generated block
+
+ ResourceGroup resourceGroup = fetchGroup(id);
+ resourceGroup.setName(in.getName());
+ Response.ResponseBuilder builder;
+
+ try {
+ resourceGroup = resourceGroupManager.updateResourceGroup(caller,resourceGroup);
+ builder=Response.ok(fillGroup(resourceGroup,uriInfo));
+ }
+ catch (Exception e) {
+ builder = Response.status(Response.Status.NOT_ACCEPTABLE); // TODO correct?
+ }
+ return builder.build();
}
@Override
@@ -172,18 +185,6 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
}
-
- @Override
- @GET
- @Path("{id}/resource/{resourceId}")
- public Response getResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
- @Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
-
-
-
- return null; // TODO: Customise this generated block
- }
-
@Override
@PUT
@Path("{id}/resource/{resourceId}")
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
index 6c91fbe..88a3610 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
@@ -19,6 +19,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.rhq.core.domain.resource.group.ResourceGroup;
+import org.rhq.enterprise.server.rest.domain.GroupRest;
import org.rhq.enterprise.server.rest.domain.ResourceWithType;
/**
@@ -48,8 +49,9 @@ public interface GroupHandlerLocal {
@PUT
@Path("{id}")
- public Response updateGroup(@PathParam("id") int id,@Context Request request, @Context HttpHeaders headers,
- @Context UriInfo uriInfo);
+ public Response updateGroup(@PathParam("id") int id, GroupRest in, @Context Request request,
+ @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
@DELETE
@Path("{id}")
@@ -63,12 +65,6 @@ public interface GroupHandlerLocal {
@Context UriInfo uriInfo);
- @GET
- @Path("{id}/resource/{resourceId}")
- public Response getResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
- @Context Request request, @Context HttpHeaders headers,
- @Context UriInfo uriInfo);
-
@PUT
@Path("{id}/resource/{resourceId}")
public Response addResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
commit 6f207bc8f0ec6126d94342f4f76ae6817b426320
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Mon Nov 14 21:36:05 2011 +0100
Improvements in group handling.
Make resource id an element and not an attribute.
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/index.html b/modules/enterprise/gui/rest-war/src/main/webapp/index.html
index 7c28583..76aa220 100644
--- a/modules/enterprise/gui/rest-war/src/main/webapp/index.html
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/index.html
@@ -10,6 +10,7 @@ See also <a href="http://rhq-project.org/display/RHQ/Design-REST">the RHQ wiki</
mobile client (Android) of the API.
<ul>
<li><a href="1/resource/platforms.html">Platforms</a></li>
+ <li><a href="1/group/">Groups</a></li>
<li><a href="1/alert/">Alerts</a></li>
<li><a href="1/status.html">System status</a> </li>
</ul>
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
index 6f2c6b0..a227a18 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
@@ -15,7 +15,9 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
+import javax.ws.rs.core.GenericEntity;
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;
@@ -24,13 +26,14 @@ import javax.ws.rs.core.UriInfo;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import org.rhq.core.domain.authz.Role;
+import org.rhq.core.domain.criteria.ResourceGroupCriteria;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.group.ResourceGroup;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
import org.rhq.enterprise.server.resource.group.ResourceGroupDeleteException;
import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal;
import org.rhq.enterprise.server.rest.domain.GroupRest;
+import org.rhq.enterprise.server.rest.domain.Link;
import org.rhq.enterprise.server.rest.domain.ResourceWithType;
/**
@@ -49,15 +52,28 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
@EJB
ResourceManagerLocal resourceManager;
- @Override
- @GET
- @Path("/")
public Response getGroups(@Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
- Set<Role> roles = caller.getRoles();
+ ResourceGroupCriteria criteria = new ResourceGroupCriteria();
+ List<ResourceGroup> groups = resourceGroupManager.findResourceGroupsByCriteria(caller,criteria);
+
+ List<GroupRest> list = new ArrayList<GroupRest>(groups.size());
+ for (ResourceGroup group : groups) {
+ list.add(fillGroup(group, uriInfo));
+ }
+
+ MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
+ Response.ResponseBuilder builder;
+
+ if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
+ builder = Response.ok(renderTemplate("listGroup", list), mediaType);
+ }
+ else {
+ GenericEntity<List<GroupRest>> ret = new GenericEntity<List<GroupRest>>(list) {};
+ builder = Response.ok(ret);
+ }
-// resourceGroupManager.findAvailableResourceGroupsForRole(caller,roleId,new int[]{}, PageControl.SIZE_UNLIMITED);
- return null; // TODO
+ return builder.build();
}
@@ -69,8 +85,18 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
ResourceGroup group = fetchGroup(id);
- GroupRest groupRest = fillGroup(group);
- Response.ResponseBuilder builder = Response.ok(groupRest);
+ GroupRest groupRest = fillGroup(group, uriInfo);
+
+ MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
+
+ Response.ResponseBuilder builder;
+
+ if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
+ builder = Response.ok(renderTemplate("group", groupRest), mediaType);
+ }
+ else {
+ builder = Response.ok(groupRest,mediaType);
+ }
return builder.build();
}
@@ -121,7 +147,7 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
@Override
@GET
@Path("{id}/resources") // TODO introduce paging through the list
- public List<ResourceWithType> getResources(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
+ public Response getResources(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo) {
ResourceGroup resourceGroup = fetchGroup(id);
@@ -131,10 +157,19 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
for (Resource res: resources) {
rwtList.add(fillRWT(res,uriInfo));
}
- Response.ResponseBuilder builder = Response.ok(rwtList);
+ MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
+ Response.ResponseBuilder builder;
+
+ if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
+ builder = Response.ok(renderTemplate("listResourceWithType", rwtList), mediaType);
+ }
+ else {
+ GenericEntity<List<ResourceWithType>> list = new GenericEntity<List<ResourceWithType>>(rwtList){};
+ builder = Response.ok(list);
+ }
+
+ return builder.build();
-// return builder.build();
- return rwtList;
}
@@ -200,10 +235,17 @@ public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLo
- private GroupRest fillGroup(ResourceGroup group) {
+ private GroupRest fillGroup(ResourceGroup group, UriInfo uriInfo) {
GroupRest gr = new GroupRest(group.getName());
+ gr.setId(group.getId());
gr.setCategory(group.getGroupCategory());
+ UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/group/{id}");
+ URI uri = uriBuilder.build(group.getId());
+
+ Link link = new Link("edit",uri.toASCIIString());
+ gr.getLinks().add(link);
return gr;
}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
index 79052f5..6c91fbe 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
@@ -59,8 +59,8 @@ public interface GroupHandlerLocal {
@GET
@Path("{id}/resources")
- public List<ResourceWithType> getResources(@PathParam("id") int id,@Context Request request, @Context HttpHeaders headers,
- @Context UriInfo uriInfo);
+ public Response getResources(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
@GET
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 8b02ad8..f90143a 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
@@ -34,6 +34,7 @@ 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.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
@@ -41,6 +42,8 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
+import com.arjuna.ats.internal.jdbc.drivers.modifiers.list;
+
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.criteria.AlertCriteria;
import org.rhq.core.domain.measurement.Availability;
@@ -145,7 +148,8 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
builder = Response.ok(renderTemplate("listResourceWithType", rwtList), mediaType);
}
else {
- builder = Response.ok(rwtList);
+ GenericEntity<List<ResourceWithType>> list = new GenericEntity<List<ResourceWithType>>(rwtList){};
+ builder = Response.ok(list);
}
return builder.build();
@@ -257,7 +261,8 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
builder = Response.ok(renderTemplate("listMetricSchedule", ret), mediaType);
}
else {
- builder = Response.ok(ret,mediaType);
+ GenericEntity<List<MetricSchedule>> list = new GenericEntity<List<MetricSchedule>>(ret){};
+ builder = Response.ok(list,mediaType);
}
return builder.build();
@@ -292,7 +297,8 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
builder = Response.ok(renderTemplate("listResourceWithType", rwtList), mediaType);
}
else {
- builder = Response.ok(rwtList);
+ GenericEntity<List<ResourceWithType>> list = new GenericEntity<List<ResourceWithType>>(rwtList){};
+ builder = Response.ok(list);
}
return builder.build();
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java
index 3192ea2..c55223d 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java
@@ -16,6 +16,7 @@ import org.rhq.core.domain.resource.group.GroupCategory;
public class GroupRest {
+ int id;
private String name;
private GroupCategory category;
@@ -28,6 +29,14 @@ public class GroupRest {
this.name = name;
}
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
public String getName() {
return name;
}
@@ -44,6 +53,7 @@ public class GroupRest {
this.category = category;
}
+ @XmlElementRef
public List<Link> getLinks() {
return links;
}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
index b7ee10a..132fc34 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
@@ -76,7 +76,6 @@ public class ResourceWithType {
this.resourceName = resourceName;
}
- @XmlAttribute
@XmlID
public String getResourceId() {
return String.valueOf(resourceId);
diff --git a/modules/enterprise/server/jar/src/main/resources/rest_templates/group.ftl b/modules/enterprise/server/jar/src/main/resources/rest_templates/group.ftl
new file mode 100644
index 0000000..90bbfac
--- /dev/null
+++ b/modules/enterprise/server/jar/src/main/resources/rest_templates/group.ftl
@@ -0,0 +1,41 @@
+<#ftl >
+<#--
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+-->
+<#-- @ftlvariable name="var" type="org.rhq.enterprise.server.rest.domain.GroupRest" -->
+<html>
+ <table>
+ <thead>
+ <tr>
+ <td>Name</td><td>Value</td>
+ </tr>
+ </thead>
+ <tr>
+ <td>Name</td><td>${var.name}</td>
+ </tr>
+ <tr>
+ <td>Id</td><td>${var.id}</td>
+ </tr>
+ <tr>
+ <td>Category</td><td>${var.category}</td>
+ </tr>
+ </table>
+ <a href="/rest/1/group/${var.id?c}/resources.html">Resources</a><br/>
+</html>
diff --git a/modules/enterprise/server/jar/src/main/resources/rest_templates/listGroup.ftl b/modules/enterprise/server/jar/src/main/resources/rest_templates/listGroup.ftl
new file mode 100644
index 0000000..6f38801
--- /dev/null
+++ b/modules/enterprise/server/jar/src/main/resources/rest_templates/listGroup.ftl
@@ -0,0 +1,32 @@
+<#ftl >
+<#--
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+-->
+<#-- @ftlvariable name="var" type="java.util.List<org.rhq.enterprise.server.rest.domain.GroupRest>" -->
+<html>
+<ul>
+ <#-- the next looks odd, but the incoming var is a list -->
+ <#list var as var>
+ <li>
+ <#include "group.ftl"/>
+ </li>
+ </#list>
+</ul>
+<html>
\ No newline at end of file
diff --git a/modules/enterprise/server/jar/src/main/resources/rest_templates/resourceWithType.ftl b/modules/enterprise/server/jar/src/main/resources/rest_templates/resourceWithType.ftl
index 9c3e435..f1ab4d2 100644
--- a/modules/enterprise/server/jar/src/main/resources/rest_templates/resourceWithType.ftl
+++ b/modules/enterprise/server/jar/src/main/resources/rest_templates/resourceWithType.ftl
@@ -2,7 +2,7 @@
<#--
/*
* RHQ Management Platform
- * Copyright (C) 2005-2008 Red Hat, Inc.
+ * Copyright (C) 2005-2011 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
commit 02a84e46915dfb47500233db99e534d4ef7973f8
Merge: a43fccf fdf91e1
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Mon Nov 14 20:34:45 2011 +0100
Merge branch 'master' into feature/rest
commit a43fccf8118da8af2017933cbcbcd6733105b6fd
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Mon Nov 14 08:19:50 2011 +0100
Unify get schedules code, allow to limit the returned values to all,metric,trait
Fix return type for schedule update and only include the link to data for metric schedules.
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 f21a90c..8be8070 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
@@ -138,7 +138,7 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
builder = Response.ok(htmlString,mediaType);
}
else
- builder= Response.ok(res);
+ builder= Response.ok(res,mediaType);
builder.cacheControl(cc);
return builder.build();
@@ -178,8 +178,6 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
builder.cacheControl(cc);
return builder.build();
}
-
-
}
if (schedule==null) {
@@ -210,12 +208,17 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
if (builder==null) {
// preconditions not met, we need to send the resource
- // create link to metrics
- UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
- uriBuilder.path("metric/data/" + scheduleId);
- URI uri = uriBuilder.build();
- Link link = new Link("metrics",uri.toString());
- metricSchedule.addLink(link);
+ UriBuilder uriBuilder;
+ URI uri;
+ Link link;
+ if (definition.getDataType()==DataType.MEASUREMENT) {
+ // create link to metrics
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("metric/data/" + scheduleId);
+ uri = uriBuilder.build();
+ link = new Link("metric",uri.toString());
+ metricSchedule.addLink(link);
+ }
// create link to the resource
uriBuilder = uriInfo.getBaseUriBuilder();
@@ -237,7 +240,7 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
builder = Response.ok(renderTemplate("metricSchedule", metricSchedule), mediaType);
}
else {
- builder = Response.ok(metricSchedule);
+ builder = Response.ok(metricSchedule,mediaType);
}
}
@@ -271,7 +274,7 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
}
@Override
- public MetricSchedule updateSchedule(int scheduleId, MetricSchedule in) {
+ public Response updateSchedule(int scheduleId, MetricSchedule in,HttpHeaders httpHeaders) {
if (in==null)
throw new StuffNotFoundException("Input is null"); // TODO other type of exception
if (in.getScheduleId()==null)
@@ -294,6 +297,6 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
MetricSchedule ret = new MetricSchedule(scheduleId,def.getName(),def.getDisplayName(),
schedule.isEnabled(),schedule.getInterval(),def.getUnits().toString(),def.getDataType().toString());
- return ret;
+ return Response.ok(ret,httpHeaders.getAcceptableMediaTypes().get(0)).build();
}
}
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 368ef36..44844ae 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
@@ -75,6 +75,6 @@ public interface MetricHandlerLocal {
@Path("/schedule/{id}")
@Consumes({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
- MetricSchedule updateSchedule(@PathParam("id") int scheduleId, MetricSchedule in);
+ Response updateSchedule(@PathParam("id") int scheduleId, MetricSchedule in,@Context HttpHeaders headers);
}
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 4ae74f4..8b02ad8 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
@@ -44,6 +44,7 @@ import javax.ws.rs.core.UriInfo;
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.criteria.AlertCriteria;
import org.rhq.core.domain.measurement.Availability;
+import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementSchedule;
import org.rhq.core.domain.resource.InventoryStatus;
@@ -203,7 +204,15 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
return availabilityRest;
}
- public List<MetricSchedule> getSchedules(int resourceId, UriInfo uriInfo) {
+ public Response getSchedules(int resourceId,
+ String scheduleType,
+ @Context Request request,
+ @Context HttpHeaders headers,
+ @Context UriInfo uriInfo) {
+
+ // allow metric as input
+ if (scheduleType.equals("metric"))
+ scheduleType=DataType.MEASUREMENT.toString().toLowerCase();
Resource res = resMgr.getResource(caller, resourceId);
@@ -212,30 +221,46 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
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(),
- definition.getDataType().toString());
- UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
- uriBuilder.path("/metric/data/{id}");
- 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);
+
+ // user can opt to e.g. only get "measurement" or "trait" metrics
+
+ if ("all".equals(scheduleType) ||
+ scheduleType.toLowerCase().equals(definition.getDataType().toString().toLowerCase()) ) {
+ MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
+ schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
+ definition.getDataType().toString());
+ UriBuilder uriBuilder;
+ URI uri;
+ if (definition.getDataType()== DataType.MEASUREMENT) {
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/metric/data/{id}");
+ 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);
+ }
}
- return ret;
- }
+ // What media type does the user request?
+ MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
+ Response.ResponseBuilder builder;
- public String getSchedulesHtml(int resourceId, UriInfo uriInfo) {
- List<MetricSchedule> list = getSchedules(resourceId, uriInfo);
- return renderTemplate("listMetricSchedule", list);
+ if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
+ builder = Response.ok(renderTemplate("listMetricSchedule", ret), mediaType);
+ }
+ else {
+ builder = Response.ok(ret,mediaType);
+ }
+
+ return builder.build();
}
@Override
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 9d9a293..412b347 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
@@ -21,10 +21,12 @@ package org.rhq.enterprise.server.rest;
import java.util.List;
import javax.ejb.Local;
+import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
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;
@@ -36,7 +38,6 @@ import org.jboss.resteasy.links.AddLinks;
import org.jboss.resteasy.links.LinkResource;
import org.jboss.resteasy.links.LinkResources;
-import org.rhq.core.domain.resource.Resource;
import org.rhq.enterprise.server.rest.domain.AvailabilityRest;
import org.rhq.enterprise.server.rest.domain.Link;
import org.rhq.enterprise.server.rest.domain.MetricSchedule;
@@ -80,18 +81,13 @@ public interface ResourceHandlerLocal {
@GET
@Path("/{id}/schedules")
- @Produces({"application/json","application/xml"})
@LinkResource(rel="schedules",value = MetricSchedule.class)
- List<MetricSchedule> getSchedules(@PathParam("id") int resourceId,
+ Response getSchedules(@PathParam("id") int resourceId,
+ @QueryParam("type") @DefaultValue("all") String scheduleType,
+ @Context Request request,
+ @Context HttpHeaders headers,
@Context UriInfo uriInfo);
- @GET
- @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")
commit 805c8af249272c4e93f06bb3710d74949ba5e6b3
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Sun Nov 13 22:53:54 2011 +0100
Update RESTeasy to 2.2.3
Start with Group related things
Use the jackson provider for nicer json
Cleanup getting the status
Fix compile errors
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 5d39d99..d607ea2 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,rhq/OperationsHandlerBean/local</param-value>
+ <param-value>rhq/ResourceHandlerBean/local,rhq/MetricHandlerBean/local,rhq/UserHandlerBean/local,rhq/StatusHandlerBean/local,rhq/AlertHandlerBean/local,rhq/OperationsHandlerBean/local,rhq/GroupHandlerBean/local</param-value>
<description>List of jndi names of EJBs local interfaces that define REST stuff</description>
</context-param>
<context-param>
@@ -22,7 +22,7 @@
</context-param>
<context-param>
<param-name>resteasy.media.type.mappings</param-name>
- <param-value>html : text/html, json : application/json, xml : application/xml</param-value>
+ <param-value>html : text/html, json : application/json, xml : application/xml, yaml: application/yaml</param-value>
</context-param>
<!--
diff --git a/modules/enterprise/server/container/pom.xml b/modules/enterprise/server/container/pom.xml
index e6fa762..4890488 100644
--- a/modules/enterprise/server/container/pom.xml
+++ b/modules/enterprise/server/container/pom.xml
@@ -117,18 +117,6 @@
<version>1.7.3</version>
</dependency>
- <dependency>
- <groupId>org.jboss.resteasy</groupId>
- <artifactId>resteasy-jaxrs-all</artifactId>
- <scope>provided</scope>
- <version>${resteasy.version}</version>
- <type>pom</type>
- </dependency>
- <dependency>
- <groupId>org.jboss.resteasy</groupId>
- <artifactId>resteasy-jettison-provider</artifactId>
- </dependency>
-
</dependencies>
<build>
diff --git a/modules/enterprise/server/ear/pom.xml b/modules/enterprise/server/ear/pom.xml
index a913b44..b792ab8 100644
--- a/modules/enterprise/server/ear/pom.xml
+++ b/modules/enterprise/server/ear/pom.xml
@@ -150,7 +150,7 @@
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
- <artifactId>resteasy-jettison-provider</artifactId>
+ <artifactId>resteasy-jackson-provider</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
@@ -164,6 +164,32 @@
<version>${freemarker.version}</version>
<type>jar</type>
</dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-yaml-provider</artifactId>
+ <version>${resteasy.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.yaml</groupId>
+ <artifactId>snakeyaml</artifactId>
+ <version>1.8</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jaxb-provider</artifactId>
+ <version>${resteasy.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.sun.xml.bind</groupId>
+ <artifactId>jaxb-impl</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.sun.xml.stream</groupId>
+ <artifactId>sjsxp</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
</dependencies>
diff --git a/modules/enterprise/server/jar/pom.xml b/modules/enterprise/server/jar/pom.xml
index 7206726..f14c91d 100644
--- a/modules/enterprise/server/jar/pom.xml
+++ b/modules/enterprise/server/jar/pom.xml
@@ -477,7 +477,7 @@
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
- <artifactId>resteasy-jettison-provider</artifactId>
+ <artifactId>resteasy-jackson-provider</artifactId>
<version>${resteasy.version}</version>
<scope>provided</scope> <!-- by container -->
</dependency>
@@ -488,6 +488,34 @@
<scope>provided</scope> <!-- by container -->
</dependency>
<dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-yaml-provider</artifactId>
+ <version>${resteasy.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jaxb-provider</artifactId>
+ <version>${resteasy.version}</version>
+ <scope>provided</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>com.sun.xml.bind</groupId>
+ <artifactId>jaxb-impl</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.sun.xml.stream</groupId>
+ <artifactId>sjsxp</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <dependency>
+ <groupId>org.yaml</groupId>
+ <artifactId>snakeyaml</artifactId>
+ <version>1.8</version>
+ </dependency>
+ <dependency>
<groupId>org.jboss.el</groupId>
<artifactId>jboss-el</artifactId>
<version>2.0.1.GA</version>
@@ -806,7 +834,7 @@ Build-OS-Version=${os.version}
<property name="javadoc.outputDirectory" value="${javadoc.outputDirectory}" />
<property name="project.dir" value="./src/main/java/org/rhq/enterprise/server" />
<property name="maven.compile.classpath" refid="maven.compile.classpath" />
-
+
<mkdir dir="${javadoc.outputDirectory}/remote-api" />
<javadoc destdir="${javadoc.outputDirectory}/remote-api" author="false" version="true" windowtitle="RHQ ${project.version} Remote API" noindex="false">
<classpath>
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 124e12f..1be4db0 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
@@ -24,6 +24,7 @@ package org.rhq.enterprise.server.rest;
import java.io.IOException;
import java.io.StringWriter;
+import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -31,6 +32,8 @@ import java.util.Set;
import javax.annotation.Resource;
import javax.ejb.Stateless;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
@@ -48,6 +51,9 @@ import org.jboss.cache.Node;
import org.jboss.cache.TreeCacheMBean;
import org.rhq.core.domain.auth.Subject;
+import org.rhq.core.domain.resource.ResourceType;
+import org.rhq.enterprise.server.rest.domain.Link;
+import org.rhq.enterprise.server.rest.domain.ResourceWithType;
/**
* Abstract base class for EJB classes that implement REST methods.
@@ -227,4 +233,24 @@ public class AbstractRestBean {
}
return true;
}
+
+ public ResourceWithType fillRWT(org.rhq.core.domain.resource.Resource res, UriInfo uriInfo) {
+ ResourceType resourceType = res.getResourceType();
+ ResourceWithType rwt = new ResourceWithType(res.getName(),res.getId());
+ rwt.setTypeName(resourceType.getName());
+ rwt.setTypeId(resourceType.getId());
+ rwt.setPluginName(resourceType.getPlugin());
+ org.rhq.core.domain.resource.Resource parent = res.getParentResource();
+ 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;
+ }
}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
new file mode 100644
index 0000000..6f2c6b0
--- /dev/null
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerBean.java
@@ -0,0 +1,210 @@
+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.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.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+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.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.rhq.core.domain.authz.Role;
+import org.rhq.core.domain.resource.Resource;
+import org.rhq.core.domain.resource.group.ResourceGroup;
+import org.rhq.enterprise.server.resource.ResourceManagerLocal;
+import org.rhq.enterprise.server.resource.group.ResourceGroupDeleteException;
+import org.rhq.enterprise.server.resource.group.ResourceGroupManagerLocal;
+import org.rhq.enterprise.server.rest.domain.GroupRest;
+import org.rhq.enterprise.server.rest.domain.ResourceWithType;
+
+/**
+ * Deal with group related things.
+ * @author Heiko W. Rupp
+ */
+@Stateless
+(a)Interceptors(SetCallerInterceptor.class)
+public class GroupHandlerBean extends AbstractRestBean implements GroupHandlerLocal {
+
+ private final Log log = LogFactory.getLog(GroupHandlerBean.class);
+
+ @EJB
+ ResourceGroupManagerLocal resourceGroupManager;
+
+ @EJB
+ ResourceManagerLocal resourceManager;
+
+ @Override
+ @GET
+ @Path("/")
+ public Response getGroups(@Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
+
+ Set<Role> roles = caller.getRoles();
+
+// resourceGroupManager.findAvailableResourceGroupsForRole(caller,roleId,new int[]{}, PageControl.SIZE_UNLIMITED);
+ return null; // TODO
+
+ }
+
+ @Override
+ @GET
+ @Path("{id}")
+ public Response getGroup(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo) {
+
+ ResourceGroup group = fetchGroup(id);
+
+ GroupRest groupRest = fillGroup(group);
+ Response.ResponseBuilder builder = Response.ok(groupRest);
+
+ return builder.build();
+ }
+
+ @Override
+ @POST
+ @Path("/")
+ public Response createGroup(ResourceGroup group, @Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
+
+ Response.ResponseBuilder builder;
+ try {
+ ResourceGroup newGroup = resourceGroupManager.createResourceGroup(caller,group);
+ UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/group/{id}");
+ URI uri = uriBuilder.build(newGroup.getId());
+
+ builder=Response.created(uri);
+ } catch (Exception e) {
+ e.printStackTrace(); // TODO: Customise this generated block
+ builder=Response.serverError();
+ }
+ return builder.build();
+ }
+
+ @Override
+ @PUT
+ @Path("{id}")
+ public Response updateGroup(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo) {
+ return null; // TODO: Customise this generated block
+ }
+
+ @Override
+ @DELETE
+ @Path("{id}")
+ public Response deleteGroup(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo) {
+
+ try {
+ resourceGroupManager.deleteResourceGroup(caller,id);
+ return Response.ok().build();
+ } catch (ResourceGroupDeleteException e) {
+ e.printStackTrace(); // TODO: Customise this generated block
+ return Response.serverError().build(); // TODO what exactly ?
+ }
+ }
+
+ @Override
+ @GET
+ @Path("{id}/resources") // TODO introduce paging through the list
+ public List<ResourceWithType> getResources(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo) {
+
+ ResourceGroup resourceGroup = fetchGroup(id);
+
+ Set<Resource> resources = resourceGroup.getExplicitResources();
+ List<ResourceWithType> rwtList = new ArrayList<ResourceWithType>(resources.size());
+ for (Resource res: resources) {
+ rwtList.add(fillRWT(res,uriInfo));
+ }
+ Response.ResponseBuilder builder = Response.ok(rwtList);
+
+// return builder.build();
+ return rwtList;
+ }
+
+
+ @Override
+ @GET
+ @Path("{id}/resource/{resourceId}")
+ public Response getResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
+ @Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
+
+
+
+ return null; // TODO: Customise this generated block
+ }
+
+ @Override
+ @PUT
+ @Path("{id}/resource/{resourceId}")
+ public Response addResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
+ @Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
+
+ ResourceGroup resourceGroup = fetchGroup(id);
+ Resource res = resourceManager.getResource(caller,resourceId);
+ if (res==null)
+ throw new StuffNotFoundException("Resource with id " + resourceId);
+
+ // TODO do we want/ need to check that the group category stays the same?
+
+ resourceGroup.addExplicitResource(res);
+
+ return Response.ok().build(); // TODO right code?
+
+ }
+
+ @Override
+ @DELETE
+ @Path("{id}/resource/{resourceId}")
+ public Response removeResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
+ @Context Request request, @Context HttpHeaders headers, @Context UriInfo uriInfo) {
+
+ ResourceGroup resourceGroup = fetchGroup(id);
+ Resource res = resourceManager.getResource(caller,resourceId);
+ if (res==null)
+ throw new StuffNotFoundException("Resource with id " + resourceId);
+
+ resourceGroup.removeExplicitResource(res);
+
+ return Response.ok().build(); // TODO right code?
+
+ }
+
+ /**
+ * Fetch the group with the passed id
+ * @param groupId id of the resource group
+ * @return the group object if found
+ * @throws StuffNotFoundException if the group is not found (or not accessible by the caller)
+ */
+ private ResourceGroup fetchGroup(int groupId) {
+ ResourceGroup resourceGroup = resourceGroupManager.getResourceGroup(caller, groupId);
+ if (resourceGroup==null)
+ throw new StuffNotFoundException("Group with id " + groupId);
+ return resourceGroup;
+ }
+
+
+
+ private GroupRest fillGroup(ResourceGroup group) {
+
+ GroupRest gr = new GroupRest(group.getName());
+ gr.setCategory(group.getGroupCategory());
+
+ return gr;
+ }
+}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
new file mode 100644
index 0000000..79052f5
--- /dev/null
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/GroupHandlerLocal.java
@@ -0,0 +1,83 @@
+package org.rhq.enterprise.server.rest;
+
+import java.util.List;
+
+import javax.ejb.Local;
+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.rhq.core.domain.resource.group.ResourceGroup;
+import org.rhq.enterprise.server.rest.domain.ResourceWithType;
+
+/**
+ * Handler for Group related things
+ * @author Heiko W. Rupp
+ */
+@Local
+@Path("/group/")
+(a)Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML, "application/yaml"})
+public interface GroupHandlerLocal {
+
+ @GET
+ @Path("/")
+ public Response getGroups(@Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+
+ @GET
+ @Path("{id}")
+ public Response getGroup(@PathParam("id") int id,@Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+
+
+ @POST
+ @Path("/")
+ public Response createGroup(ResourceGroup group, @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+
+ @PUT
+ @Path("{id}")
+ public Response updateGroup(@PathParam("id") int id,@Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+
+ @DELETE
+ @Path("{id}")
+ public Response deleteGroup(@PathParam("id") int id,@Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+
+
+ @GET
+ @Path("{id}/resources")
+ public List<ResourceWithType> getResources(@PathParam("id") int id,@Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+
+
+ @GET
+ @Path("{id}/resource/{resourceId}")
+ public Response getResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
+ @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+
+ @PUT
+ @Path("{id}/resource/{resourceId}")
+ public Response addResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
+ @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+
+ @DELETE
+ @Path("{id}/resource/{resourceId}")
+ public Response removeResource(@PathParam("id") int id, @PathParam("resourceId") int resourceId,
+ @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
+}
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
index 46a7f86..5ab7366 100644
--- 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
@@ -19,8 +19,6 @@ 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;
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 e656c24..4ae74f4 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
@@ -49,7 +49,6 @@ import org.rhq.core.domain.measurement.MeasurementSchedule;
import org.rhq.core.domain.resource.InventoryStatus;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceCategory;
-import org.rhq.core.domain.resource.ResourceType;
import org.rhq.enterprise.server.alert.AlertManagerLocal;
import org.rhq.enterprise.server.rest.domain.AvailabilityRest;
import org.rhq.enterprise.server.rest.domain.Link;
@@ -204,26 +203,6 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
return availabilityRest;
}
- private ResourceWithType fillRWT(Resource res, UriInfo uriInfo) {
- ResourceType resourceType = res.getResourceType();
- ResourceWithType rwt = new ResourceWithType(res.getName(),res.getId());
- rwt.setTypeName(resourceType.getName());
- rwt.setTypeId(resourceType.getId());
- rwt.setPluginName(resourceType.getPlugin());
- Resource parent = res.getParentResource();
- 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;
- }
-
public List<MetricSchedule> getSchedules(int resourceId, UriInfo uriInfo) {
Resource res = resMgr.getResource(caller, resourceId);
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 d1cb0f2..9d9a293 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
@@ -36,6 +36,7 @@ import org.jboss.resteasy.links.AddLinks;
import org.jboss.resteasy.links.LinkResource;
import org.jboss.resteasy.links.LinkResources;
+import org.rhq.core.domain.resource.Resource;
import org.rhq.enterprise.server.rest.domain.AvailabilityRest;
import org.rhq.enterprise.server.rest.domain.Link;
import org.rhq.enterprise.server.rest.domain.MetricSchedule;
@@ -46,7 +47,7 @@ import org.rhq.enterprise.server.rest.domain.ResourceWithType;
* Interface class that describes the REST interface
* @author Heiko W. Rupp
*/
-@Produces({"application/json","application/xml","text/html"})
+(a)Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML})
@Path("/resource")
@Local
public interface ResourceHandlerLocal {
@@ -57,13 +58,12 @@ public interface ResourceHandlerLocal {
})
@GET
@Path("/{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({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML})
Response getPlatforms(@Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo);
@@ -95,7 +95,6 @@ public interface ResourceHandlerLocal {
@GET
@Path("/{id}/children")
- @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML})
@LinkResource(rel="children", value = ResourceWithType.class)
Response getChildren(@PathParam("id") int id, @Context Request request, @Context HttpHeaders headers,
@Context UriInfo uriInfo);
@@ -104,7 +103,6 @@ public interface ResourceHandlerLocal {
@AddLinks
@GET
@Path(("/{id}/alerts"))
-// @LinkResource(rel="alerts", value = AlertRest.class)
List<Link> getAlertsForResource(@PathParam("id") int resourceId); // TODO paging + status
}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java
index cd68a72..0f2a0ed 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerBean.java
@@ -21,13 +21,12 @@ package org.rhq.enterprise.server.rest;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.alert.AlertDefinition;
-import org.rhq.core.domain.content.composite.PackageListItemComposite;
import org.rhq.core.domain.criteria.AlertCriteria;
import org.rhq.core.domain.criteria.AlertDefinitionCriteria;
import org.rhq.core.domain.criteria.Criteria;
@@ -60,7 +59,7 @@ public class StatusHandlerBean extends AbstractRestBean implements StatusHandler
AlertDefinitionManagerLocal alertDefinitionManager;
@Override
- public Status getStatus() {
+ public Response getStatus(HttpHeaders httpHeaders) {
Status status = new Status();
@@ -94,12 +93,15 @@ public class StatusHandlerBean extends AbstractRestBean implements StatusHandler
status.setMetricsMin(scheduleManager.getScheduledMeasurementsPerMinute());
- return status;
- }
+ MediaType mediaType = httpHeaders.getAcceptableMediaTypes().get(0);
+ Response.ResponseBuilder builder;
+ if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
+ String htmlString = renderTemplate("status",status);
+ builder = Response.ok(htmlString,mediaType);
+ } else {
+ builder = Response.ok(status, httpHeaders.getAcceptableMediaTypes().get(0));
+ }
- @Override
- public String getStatusHtml() {
- Status status = getStatus();
- return renderTemplate("status",status);
+ return builder.build();
}
}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerLocal.java
index 5e1c3f4..838fbad 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerLocal.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/StatusHandlerLocal.java
@@ -22,8 +22,9 @@ import javax.ejb.Local;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
-
-import org.rhq.enterprise.server.rest.domain.Status;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
/**
* Return some status information about the system
@@ -36,11 +37,5 @@ public interface StatusHandlerLocal {
@GET
@Path("/")
- @Produces({"application/json","application/xml"})
- Status getStatus();
-
- @GET
- @Path("/")
- @Produces("text/html")
- String getStatusHtml();
+ Response getStatus(@Context HttpHeaders httpHeaders);
}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerBean.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerBean.java
index d8b0c39..b644cd6 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerBean.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerBean.java
@@ -32,10 +32,13 @@ import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
+import javax.ws.rs.core.UriInfo;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
+import org.rhq.core.domain.resource.Resource;
import org.rhq.enterprise.server.auth.SubjectManagerLocal;
+import org.rhq.enterprise.server.resource.ResourceManagerLocal;
import org.rhq.enterprise.server.rest.domain.ResourceWithType;
/**
@@ -66,15 +69,20 @@ public class UserHandlerBean extends AbstractRestBean implements UserHandlerLoca
@EJB
ResourceHandlerLocal resourceHandler;
+ @EJB
+ ResourceManagerLocal resourceManager;
+
@Override
@GET
@Path("favorites/r")
- public List<ResourceWithType> getFavorites() {
+ public List<ResourceWithType> getFavorites(UriInfo uriInfo) {
Set<Integer> favIds = getResourceIdsForFavorites();
List<ResourceWithType> ret = new ArrayList<ResourceWithType>();
for (Integer id : favIds) {
- ResourceWithType rwt = resourceHandler.getResource(id);
+ Resource res = resourceManager.getResource(caller,id);
+
+ ResourceWithType rwt = fillRWT(res,uriInfo);
ret.add(rwt);
}
return ret;
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerLocal.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerLocal.java
index f80eb9e..9073f32 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerLocal.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/UserHandlerLocal.java
@@ -28,6 +28,8 @@ import javax.ws.rs.PUT;
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.UriInfo;
import org.rhq.enterprise.server.rest.domain.ResourceWithType;
@@ -42,7 +44,7 @@ public interface UserHandlerLocal {
@GET
@Path("favorites/resource")
- public List<ResourceWithType> getFavorites();
+ public List<ResourceWithType> getFavorites(@Context UriInfo uriInfo);
@PUT
@Path("favorites/resource/{id}")
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertDefinitionRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertDefinitionRest.java
index 073cab8..f8d1f88 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertDefinitionRest.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertDefinitionRest.java
@@ -24,8 +24,6 @@ import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlRootElement;
-import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped;
-import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap;
import org.jboss.resteasy.links.RESTServiceDiscovery;
import org.jboss.resteasy.spi.touri.URITemplate;
@@ -35,7 +33,6 @@ import org.jboss.resteasy.spi.touri.URITemplate;
*/
@URITemplate("/alert/definition/{id}")
@XmlRootElement
-@Mapped(namespaceMap = @XmlNsMap(jsonName = "atom", namespace = "http://www.w3.org/2005/Atom"))
public class AlertDefinitionRest {
int id;
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertRest.java
index c540434..90fe925 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertRest.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AlertRest.java
@@ -26,8 +26,6 @@ import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlRootElement;
-import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped;
-import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap;
import org.jboss.resteasy.links.AddLinks;
import org.jboss.resteasy.links.LinkResource;
import org.jboss.resteasy.links.RESTServiceDiscovery;
@@ -39,9 +37,7 @@ import org.jboss.resteasy.spi.touri.URITemplate;
*/
@URITemplate("/alert/{id}")
@XmlRootElement
-//@XmlType(propOrder = {"id","name","alertTime","description","alertDefinitionId","definitionEnabled","resourceId","resourceName","ackBy","ackTime","rest"})
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
-@Mapped(namespaceMap = @XmlNsMap(jsonName = "atom", namespace = "http://www.w3.org/2005/Atom"))
public class AlertRest {
private int id;
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AvailabilityRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AvailabilityRest.java
index a0763dd..9f64f4b 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AvailabilityRest.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/AvailabilityRest.java
@@ -22,8 +22,6 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlRootElement;
-import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped;
-import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap;
import org.jboss.resteasy.links.RESTServiceDiscovery;
import org.rhq.core.domain.measurement.AvailabilityType;
@@ -33,7 +31,6 @@ import org.rhq.core.domain.measurement.AvailabilityType;
* @author Heiko W. Rupp
*/
@XmlRootElement(name = "availability")
-@Mapped(namespaceMap = @XmlNsMap(jsonName = "atom", namespace = "http://www.w3.org/2005/Atom"))
public class AvailabilityRest {
long since;
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java
new file mode 100644
index 0000000..3192ea2
--- /dev/null
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/GroupRest.java
@@ -0,0 +1,54 @@
+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;
+
+import org.rhq.core.domain.resource.group.GroupCategory;
+
+/**
+ * Representation of a resource group
+ * @author Heiko W. Rupp
+ */
+@XmlRootElement
+public class GroupRest {
+
+
+ private String name;
+ private GroupCategory category;
+
+ List<Link> links = new ArrayList<Link>();
+
+ public GroupRest() {
+ }
+
+ public GroupRest(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public GroupCategory getCategory() {
+ return category;
+ }
+
+ public void setCategory(GroupCategory category) {
+ this.category = category;
+ }
+
+ public List<Link> getLinks() {
+ return links;
+ }
+
+ public void setLinks(List<Link> links) {
+ this.links = links;
+ }
+}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
index 4333530..30a006b 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
@@ -27,8 +27,6 @@ import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
-import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped;
-import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap;
import org.jboss.resteasy.spi.touri.URITemplate;
/**
@@ -36,8 +34,6 @@ import org.jboss.resteasy.spi.touri.URITemplate;
* @author Heiko W. Rupp
*/
@XmlRootElement
-//@XmlType(propOrder = {"scheduleId","scheduleName","displayName","enabled","collectionInterval","unit","type"})
-@Mapped(namespaceMap = @XmlNsMap(jsonName = "atom", namespace = "http://www.w3.org/2005/Atom"))
@URITemplate("/metric/schedule/{id}")
public class MetricSchedule {
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
index 1e5971c..b7ee10a 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
@@ -33,19 +33,17 @@ import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlRootElement;
-import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped;
-import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap;
import org.jboss.resteasy.spi.touri.URITemplate;
/**
* A (partial) resource with some type information
* @author Heiko W. Rupp
*/
-@Mapped(namespaceMap = @XmlNsMap(jsonName = "atom", namespace = "http://www.w3.org/2005/Atom"))
@XmlRootElement
+/*
@XmlAccessorType(XmlAccessType.NONE)
-//@XmlType(propOrder = {"resourceId","resourceName","parentId","parentName","typeId","typeName","pluginId","pluginName","rest"})
@URITemplate("{id}")
+*/
public class ResourceWithType {
String resourceName;
diff --git a/pom.xml b/pom.xml
index 9eae894..c01cf60 100644
--- a/pom.xml
+++ b/pom.xml
@@ -107,7 +107,7 @@
<augeas.zip.location>${settings.localRepository}/net/augeas/augeas-native/${augeas.version}</augeas.zip.location>
<augeas.zip.mask>*-${augeas.classifier}.zip</augeas.zip.mask>
<ant.contrib.version>1.0b3</ant.contrib.version>
- <resteasy.version>2.2.2.GA</resteasy.version>
+ <resteasy.version>2.2.3.GA</resteasy.version>
<freemarker.version>2.3.18</freemarker.version>
<rhq.db.admin.username>rhqadmin</rhq.db.admin.username>
commit e23db61ad37d962ce5ba746a2864854d1ce4fc78
Author: Heiko W. Rupp <hwr(a)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
+(a)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
commit 001a8e84ccb27d43979c10ce2103a6106440dfea
Author: Heiko W. Rupp <pilhuhn(a)fedoraproject.org>
Date: Wed Nov 2 17:58:59 2011 +0100
Remove bogus import.
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
index ff160e2..d951767 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
@@ -23,8 +23,6 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
-import sun.security.provider.certpath.OCSPResponse;
-
import org.rhq.enterprise.server.resource.ResourceNotFoundException;
/**
commit c6be98cee4043752d110f191a039a11bfe869465
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Nov 2 08:51:54 2011 +0100
Introduce a ParamMissing exception for missing query params
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
index 57385cd..ff160e2 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
@@ -23,6 +23,10 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
+import sun.security.provider.certpath.OCSPResponse;
+
+import org.rhq.enterprise.server.resource.ResourceNotFoundException;
+
/**
* Map a NotFoundException to a HTTP response with respective error message
* @author Heiko W. Rupp
@@ -40,6 +44,14 @@ public class CustomExceptionMapper implements ExceptionMapper<EJBException> {
return Response.status(Response.Status.NOT_FOUND)
.entity(cause.getMessage())
.build();
+ else if (cause instanceof ResourceNotFoundException)
+ return Response.status(Response.Status.NOT_FOUND)
+ .entity(cause.getMessage())
+ .build();
+ else if (cause instanceof ParameterMissingException)
+ return Response.status(Response.Status.NOT_ACCEPTABLE)
+ .entity(cause.getMessage())
+ .build();
else
return Response.status(Response.Status.SERVICE_UNAVAILABLE)
.entity(cause.getMessage())
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ParameterMissingException.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ParameterMissingException.java
new file mode 100644
index 0000000..16fd62f
--- /dev/null
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ParameterMissingException.java
@@ -0,0 +1,12 @@
+package org.rhq.enterprise.server.rest;
+
+/**
+ * Exception thrown if (Query) Parameters are missing
+ * @author Heiko W. Rupp
+ */
+public class ParameterMissingException extends RuntimeException {
+
+ public ParameterMissingException(String what) {
+ super("(Query)param " + what + " is missing");
+ }
+}
commit 47a3371266a2607681cd7ee05f9028ef03bb86ed
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Nov 2 08:48:34 2011 +0100
Add hyperlinks from resource/schedules to their metrics
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 5251805..368efc0 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
@@ -22,6 +22,7 @@
*/
package org.rhq.enterprise.server.rest;
+import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -29,8 +30,8 @@ import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
-
-import org.jboss.resteasy.spi.Link;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.criteria.AlertCriteria;
@@ -42,8 +43,8 @@ import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceCategory;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.enterprise.server.alert.AlertManagerLocal;
-import org.rhq.enterprise.server.plugin.pc.content.RepoSource;
import org.rhq.enterprise.server.rest.domain.AvailabilityRest;
+import org.rhq.enterprise.server.rest.domain.Link;
import org.rhq.enterprise.server.rest.domain.MetricSchedule;
import org.rhq.enterprise.server.rest.domain.ResourceWithChildren;
import org.rhq.enterprise.server.rest.domain.ResourceWithType;
@@ -163,7 +164,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
return rwt;
}
- public List<MetricSchedule> getSchedules(int resourceId) {
+ public List<MetricSchedule> getSchedules(int resourceId, UriInfo uriInfo) {
Resource res = resMgr.getResource(caller, resourceId);
@@ -174,14 +175,19 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
definition.getDataType().toString());
+ UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/metric/data/{id}");
+ URI uri = uriBuilder.build(schedule.getId());
+ Link metricLink = new Link("metric",uri.toString());
+ ms.addLink(metricLink);
ret.add(ms);
}
return ret;
}
- public String getSchedulesHtml(int resourceId) {
- List<MetricSchedule> list = getSchedules(resourceId);
+ public String getSchedulesHtml(int resourceId, UriInfo uriInfo) {
+ List<MetricSchedule> list = getSchedules(resourceId, uriInfo);
return renderTemplate("listMetricSchedule", list);
}
@@ -212,7 +218,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
List<Link> links = new ArrayList<Link>(alerts.size());
for (Alert al: alerts) {
Link link = new Link();
- link.setRelationship("alert");
+ link.setRel("alert");
link.setHref("/alert/" + al.getId());
}
return links;
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 a36d4fb..c2e8698 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
@@ -25,13 +25,15 @@ import javax.ws.rs.GET;
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.UriInfo;
import org.jboss.resteasy.links.AddLinks;
import org.jboss.resteasy.links.LinkResource;
import org.jboss.resteasy.links.LinkResources;
-import org.jboss.resteasy.spi.Link;
import org.rhq.enterprise.server.rest.domain.AvailabilityRest;
+import org.rhq.enterprise.server.rest.domain.Link;
import org.rhq.enterprise.server.rest.domain.MetricSchedule;
import org.rhq.enterprise.server.rest.domain.ResourceWithChildren;
import org.rhq.enterprise.server.rest.domain.ResourceWithType;
@@ -84,13 +86,15 @@ public interface ResourceHandlerLocal {
@Path("/{id}/schedules")
@Produces({"application/json","application/xml"})
@LinkResource(rel="schedules",value = MetricSchedule.class)
- List<MetricSchedule> getSchedules(@PathParam("id") int resourceId);
+ List<MetricSchedule> getSchedules(@PathParam("id") int resourceId,
+ @Context UriInfo uriInfo);
@GET
@Path("/{id}/schedules")
@Produces("text/html")
@LinkResource(rel="schedules",value = MetricSchedule.class)
- String getSchedulesHtml(@PathParam("id") int resourceId);
+ String getSchedulesHtml(@PathParam("id") int resourceId,
+ @Context UriInfo uriInfo);
@GET
@Path("/{id}/children")
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
index 894277a..1e5971c 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/ResourceWithType.java
@@ -22,6 +22,9 @@
*/
package org.rhq.enterprise.server.rest.domain;
+import java.util.ArrayList;
+import java.util.List;
+
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
@@ -32,9 +35,6 @@ import javax.xml.bind.annotation.XmlRootElement;
import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped;
import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap;
-import org.jboss.resteasy.links.AddLinks;
-import org.jboss.resteasy.links.LinkResource;
-import org.jboss.resteasy.links.RESTServiceDiscovery;
import org.jboss.resteasy.spi.touri.URITemplate;
/**
@@ -54,8 +54,8 @@ public class ResourceWithType {
Integer typeId;
String pluginName;
Integer parentId;
+ List<Link> links = new ArrayList<Link>();
- private RESTServiceDiscovery rest;
public ResourceWithType() {
}
@@ -124,12 +124,15 @@ public class ResourceWithType {
}
@XmlElementRef
- public RESTServiceDiscovery getRest() {
- return rest;
+ public List<Link> getLinks() {
+ return links;
}
- public void setRest(RESTServiceDiscovery rest) {
- this.rest = rest;
+ public void setLinks(List<Link> links) {
+ this.links = links;
}
+ public void addLink(Link link) {
+ links.add(link);
+ }
}
commit 691dc9c0cce937f4c1b979145bfa6458382c29c0
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Fri Oct 28 11:01:52 2011 +0200
Add support for hyperlinking between schedule and metrics and to the edit schedule method.
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 0f0c9a9..9908b7d 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
@@ -18,6 +18,7 @@
*/
package org.rhq.enterprise.server.rest;
+import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -32,12 +33,15 @@ 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.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementSchedule;
import org.rhq.core.domain.measurement.composite.MeasurementDataNumericHighLowComposite;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
+import org.rhq.enterprise.server.rest.domain.Link;
import org.rhq.enterprise.server.rest.domain.MetricAggregate;
import org.rhq.enterprise.server.measurement.MeasurementAggregate;
import org.rhq.enterprise.server.measurement.MeasurementDataManagerLocal;
@@ -132,12 +136,14 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
/**
* Return a metric schedule with the respective status codes for cache validation
*
+ *
* @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
* @return Schedule with respective headers
*/
- public Response getSchedule(int scheduleId, Request request, HttpHeaders headers) {
+ public Response getSchedule(int scheduleId, Request request, HttpHeaders headers, UriInfo uriInfo) {
MeasurementSchedule schedule = scheduleManager.getScheduleById(caller, scheduleId);
if (schedule==null)
@@ -155,11 +161,29 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
// Check for conditional get
+ // 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);
if (builder==null) {
// preconditions not met, we need to send the resource
+
+ // create link to metrics
+ UriBuilder uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("metric/data/" + scheduleId);
+ URI uri = uriBuilder.build();
+
+ Link link = new Link("metrics",uri.toString());
+ metricSchedule.addLink(link);
+
+ // Link for updates
+ uriBuilder = uriInfo.getAbsolutePathBuilder();
+ uri = uriBuilder.build();
+ Link updateLink = new Link("edit",uri.toString());
+ metricSchedule.addLink(updateLink);
+
+
if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
builder = Response.ok(renderTemplate("metricSchedule", metricSchedule), mediaType);
}
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 053005b..76c3f9d 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
@@ -34,6 +34,7 @@ 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.rhq.enterprise.server.rest.domain.MetricAggregate;
import org.rhq.enterprise.server.rest.domain.MetricSchedule;
@@ -67,7 +68,8 @@ public interface MetricHandlerLocal {
@GET
@Path("/schedule/{id}")
@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML})
- Response getSchedule(@PathParam("id") int scheduleId, @Context Request request, @Context HttpHeaders headers);
+ Response getSchedule(@PathParam("id") int scheduleId, @Context Request request, @Context HttpHeaders headers,
+ @Context UriInfo uriInfo);
@PUT
@Path("/schedule/{id}")
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Link.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Link.java
new file mode 100644
index 0000000..1936425
--- /dev/null
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/Link.java
@@ -0,0 +1,50 @@
+package org.rhq.enterprise.server.rest.domain;
+
+import javax.xml.bind.annotation.XmlAttribute;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * A link between two resources
+ * @author Heiko W. Rupp
+ */
+@XmlRootElement
+public class Link {
+
+ private String rel;
+ private String href;
+
+ public Link() {
+ }
+
+ public Link(String rel, String href) {
+ this.rel = rel;
+ this.href = href;
+ }
+
+
+ @XmlAttribute
+ public String getRel() {
+ return rel;
+ }
+
+ @XmlAttribute
+ public String getHref() {
+ return href;
+ }
+
+ public void setRel(String rel) {
+ this.rel = rel;
+ }
+
+ public void setHref(String href) {
+ this.href = href;
+ }
+
+ @Override
+ public String toString() {
+ return "Link{" +
+ "rel='" + rel + '\'' +
+ ", href='" + href + '\'' +
+ '}';
+ }
+}
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
index 377ca87..4333530 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
@@ -18,6 +18,11 @@
*/
package org.rhq.enterprise.server.rest.domain;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.ws.rs.core.UriBuilder;
+import javax.xml.bind.annotation.XmlElementRef;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
@@ -44,6 +49,7 @@ public class MetricSchedule {
String unit;
String type;
transient long mtime;
+ List<Link> links = new ArrayList<Link>();
@SuppressWarnings("unused")
public MetricSchedule() {
@@ -129,7 +135,14 @@ public class MetricSchedule {
this.mtime = mtime;
}
+ public void addLink(Link link) {
+ links.add(link);
+ }
+ @XmlElementRef
+ public List<Link> getLinks() {
+ return links;
+ }
@Override
public boolean equals(Object o) {
@@ -156,4 +169,5 @@ public class MetricSchedule {
result = 31 * result + (int) (mtime ^ (mtime >>> 32));
return result;
}
+
}
commit 9e080614d6b101bd3a86905583d4e4de002a89c7
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Thu Oct 27 22:53:51 2011 +0200
API cleanup for getting metrics and schedules. Also fix updating of schedules.
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 5beee77..0f0c9a9 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
@@ -26,6 +26,7 @@ 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;
@@ -61,8 +62,10 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
private static final long EIGHT_HOURS = 8 * 3600L * 1000L;
@Override
- public MetricAggregate getMetricData(int scheduleId, long startTime, long endTime,
- int dataPoints,boolean hideEmpty) {
+ public Response getMetricData(int scheduleId, long startTime, long endTime,
+ int dataPoints,boolean hideEmpty,
+ @Context Request request,
+ @Context HttpHeaders headers) {
if (dataPoints<0)
throw new IllegalArgumentException("dataPoints must be >0 ");
@@ -105,19 +108,27 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
res.setMaxTimeStamp(maxTime);
res.setMinTimeStamp(minTime);
+ CacheControl cc = new CacheControl();
+ int maxAge = (int) (schedule.getInterval() / 1000L)/2; // millis ; half of schedule interval
+ cc.setMaxAge(maxAge); // these are seconds
+ cc.setPrivate(false);
+ cc.setNoCache(false);
- return res;
- }
- public String getMetricDataHtml(int scheduleId,
- long startTime,
- long endTime,
- int dataPoints,
- boolean hideEmpty) {
- MetricAggregate agg = getMetricData(scheduleId,startTime,endTime,dataPoints,hideEmpty);
- return renderTemplate("metricData", agg);
+ Response.ResponseBuilder builder;
+ MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
+ if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
+ String htmlString = renderTemplate("metricData", res);
+ builder = Response.ok(htmlString,mediaType);
+ }
+ else
+ builder= Response.ok(res);
+ builder.cacheControl(cc);
+
+ return builder.build();
}
+
/**
* Return a metric schedule with the respective status codes for cache validation
*
@@ -149,7 +160,7 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
if (builder==null) {
// preconditions not met, we need to send the resource
- if (mediaType == MediaType.TEXT_HTML_TYPE) {
+ if (mediaType.equals(MediaType.TEXT_HTML_TYPE)) {
builder = Response.ok(renderTemplate("metricSchedule", metricSchedule), mediaType);
}
else {
@@ -191,7 +202,6 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
public MetricSchedule updateSchedule(int scheduleId, MetricSchedule in) {
if (in==null)
throw new StuffNotFoundException("Input is null"); // TODO other type of exception
-
if (in.getScheduleId()==null)
throw new StuffNotFoundException("Invalid input data");
@@ -205,8 +215,10 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
scheduleManager.updateSchedule(caller, schedule);
schedule = scheduleManager.getScheduleById(caller,scheduleId);
+ MeasurementDefinition def = schedule.getDefinition();
- MetricSchedule ret = new MetricSchedule(scheduleId,in.getScheduleName(),in.getDisplayName(),schedule.isEnabled(),schedule.getInterval(),in.getUnit(),in.getType());
+ MetricSchedule ret = new MetricSchedule(scheduleId,def.getName(),def.getDisplayName(),
+ schedule.isEnabled(),schedule.getInterval(),def.getUnits().toString(),def.getDataType().toString());
return ret;
}
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 f7802d3..053005b 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
@@ -18,6 +18,7 @@
*/
package org.rhq.enterprise.server.rest;
+import java.io.InputStream;
import java.util.List;
import javax.ejb.Local;
@@ -30,6 +31,7 @@ import javax.ws.rs.PUT;
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;
@@ -48,21 +50,15 @@ public interface MetricHandlerLocal {
@GET
@Path("data/{scheduleId}")
- @Produces({"application/json","application/xml"})
- MetricAggregate getMetricData(@PathParam("scheduleId") int scheduleId,
+ @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML})
+ Response getMetricData(@PathParam("scheduleId") int scheduleId,
@QueryParam("startTime") long startTime,
@QueryParam("endTime") long endTime,
@QueryParam("dataPoints") int dataPoints,
- @QueryParam("hideEmpty") boolean hideEmpty);
+ @QueryParam("hideEmpty") boolean hideEmpty,
+ @Context Request request,
+ @Context HttpHeaders headers);
- @GET
- @Path("data/{scheduleId}")
- @Produces("text/html")
- String getMetricDataHtml(@PathParam("scheduleId") int scheduleId,
- @QueryParam("startTime") long startTime,
- @QueryParam("endTime") long endTime,
- @QueryParam("dataPoints") int dataPoints,
- @QueryParam("hideEmpty") boolean hideEmpty);
@GET
@Path("data/resource/{resourceId}")
@@ -70,12 +66,13 @@ public interface MetricHandlerLocal {
@GET
@Path("/schedule/{id}")
- @Produces({"application/json","application/xml","text/html"})
+ @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML})
Response getSchedule(@PathParam("id") int scheduleId, @Context Request request, @Context HttpHeaders headers);
@PUT
@Path("/schedule/{id}")
- @Consumes("application/json")
- MetricSchedule updateSchedule(@PathParam("id") int scheduleId, MetricSchedule in);
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ MetricSchedule updateSchedule(@PathParam("id") int scheduleId, MetricSchedule in);
}
commit 8e40cd43b2ca814b43fcd17140d144ae6523cafb
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Oct 26 15:01:54 2011 +0200
Add cache control and merge all media types into one method
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 3ff403d..5beee77 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
@@ -19,11 +19,18 @@
package org.rhq.enterprise.server.rest;
import java.util.ArrayList;
+import java.util.Date;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
+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 org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
@@ -111,23 +118,54 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
return renderTemplate("metricData", agg);
}
- public MetricSchedule getSchedule(int scheduleId) {
+ /**
+ * Return a metric schedule with the respective status codes for cache validation
+ *
+ * @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
+ * @return Schedule with respective headers
+ */
+ public Response getSchedule(int scheduleId, Request request, HttpHeaders headers) {
- MeasurementSchedule schedule = scheduleManager.getScheduleById(caller,scheduleId);
+ MeasurementSchedule schedule = scheduleManager.getScheduleById(caller, scheduleId);
if (schedule==null)
throw new StuffNotFoundException("Schedule with id " + scheduleId);
+
MeasurementDefinition definition = schedule.getDefinition();
- MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
- schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
+ MetricSchedule metricSchedule = new MetricSchedule(schedule.getId(), definition.getName(),
+ definition.getDisplayName(),
+ schedule.isEnabled(), schedule.getInterval(), definition.getUnits().toString(),
definition.getDataType().toString());
+ if (schedule.getMtime()!=null)
+ metricSchedule.setMtime(schedule.getMtime());
- return ms;
- }
+ // What media type does the user request?
+ MediaType mediaType = headers.getAcceptableMediaTypes().get(0);
- @Override
- public String getScheduleHtml(int scheduleId) {
- MetricSchedule ms = getSchedule(scheduleId);
- return renderTemplate("metricSchedule", ms);
+ // Check for conditional get
+ EntityTag eTag = new EntityTag(Integer.toOctalString(metricSchedule.hashCode()));
+ Response.ResponseBuilder builder = request.evaluatePreconditions(new Date(metricSchedule.getMtime()),eTag);
+
+ if (builder==null) {
+ // preconditions not met, we need to send the resource
+ if (mediaType == MediaType.TEXT_HTML_TYPE) {
+ builder = Response.ok(renderTemplate("metricSchedule", metricSchedule), mediaType);
+ }
+ else {
+ builder = Response.ok(metricSchedule);
+ }
+ }
+
+ // 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);
+
+ return builder.build();
}
@Override
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 572fe5b..f7802d3 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
@@ -28,6 +28,10 @@ import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.PUT;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
import org.rhq.enterprise.server.rest.domain.MetricAggregate;
import org.rhq.enterprise.server.rest.domain.MetricSchedule;
@@ -66,13 +70,8 @@ public interface MetricHandlerLocal {
@GET
@Path("/schedule/{id}")
- @Produces({"application/json","application/xml"})
- MetricSchedule getSchedule(@PathParam("id") int scheduleId);
-
- @GET
- @Path("/schedule/{id}")
- @Produces("text/html")
- String getScheduleHtml(@PathParam("id") int scheduleId);
+ @Produces({"application/json","application/xml","text/html"})
+ Response getSchedule(@PathParam("id") int scheduleId, @Context Request request, @Context HttpHeaders headers);
@PUT
@Path("/schedule/{id}")
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
index efa6361..377ca87 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricSchedule.java
@@ -20,7 +20,7 @@ package org.rhq.enterprise.server.rest.domain;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.XmlTransient;
import org.jboss.resteasy.annotations.providers.jaxb.json.Mapped;
import org.jboss.resteasy.annotations.providers.jaxb.json.XmlNsMap;
@@ -43,6 +43,7 @@ public class MetricSchedule {
String displayName;
String unit;
String type;
+ transient long mtime;
@SuppressWarnings("unused")
public MetricSchedule() {
@@ -118,4 +119,41 @@ public class MetricSchedule {
// public String getEnabled() {
// return String.valueOf(enabled);
// }
+
+ @XmlTransient
+ public long getMtime() {
+ return mtime;
+ }
+
+ public void setMtime(long mtime) {
+ this.mtime = mtime;
+ }
+
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ MetricSchedule that = (MetricSchedule) o;
+
+ if (collectionInterval != that.collectionInterval) return false;
+ if (mtime != that.mtime) return false;
+ if (scheduleId != that.scheduleId) return false;
+ if (enabled != null ? !enabled.equals(that.enabled) : that.enabled != null) return false;
+ if (scheduleName != null ? !scheduleName.equals(that.scheduleName) : that.scheduleName != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = scheduleId;
+ result = 31 * result + (scheduleName != null ? scheduleName.hashCode() : 0);
+ result = 31 * result + (enabled != null ? enabled.hashCode() : 0);
+ result = 31 * result + (int) (collectionInterval ^ (collectionInterval >>> 32));
+ result = 31 * result + (int) (mtime ^ (mtime >>> 32));
+ return result;
+ }
}
commit abd6af308975aab38766afa51c733923d3df23cf
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Oct 26 14:59:08 2011 +0200
Update to RESTeasy 2.2 and move artifacs into rhq.ear, so future updates do noot need a container build.
diff --git a/modules/enterprise/gui/rest-war/src/main/webapp/js/whisker.js b/modules/enterprise/gui/rest-war/src/main/webapp/js/whisker.js
index b07ec2e..6f23669 100644
--- a/modules/enterprise/gui/rest-war/src/main/webapp/js/whisker.js
+++ b/modules/enterprise/gui/rest-war/src/main/webapp/js/whisker.js
@@ -9,17 +9,16 @@ function whisker(scheduleId,divId) {
.attr("height", h);
// .append("svg:g");
// .attr("transform", "translate(" + p[3] + "," + (h - p[2]) + ")");
-console.log(scheduleId);
d3.json(
'/rest/1/metric/data/'+scheduleId+'.json?hideEmpty=true',
function (jsondata) {
- var points = jsondata.metricAggregate.dataPoints;
- var minVal = jsondata.metricAggregate.min;
- var maxVal = jsondata.metricAggregate.max;
- var avgVal = jsondata.metricAggregate.avg;
- var minTs = jsondata.metricAggregate.minTimeStamp;
- var maxTs = jsondata.metricAggregate.maxTimeStamp;
+ var points = jsondata.dataPoints;
+ var minVal = jsondata.min;
+ var maxVal = jsondata.max;
+ var avgVal = jsondata.avg;
+ var minTs = jsondata.minTimeStamp;
+ var maxTs = jsondata.maxTimeStamp;
var minTsD = new Date(minTs);
var maxTsD = new Date(maxTs);
@@ -30,7 +29,6 @@ console.log(scheduleId);
// Y axis goes from lowest to highest value
var y = d3.scale.linear().domain([minVal, maxVal]).rangeRound([0,h]);
-// console.log(jsondata.metricAggregate);
// Line for the average
var avgLine = svg.append("svg:line")
@@ -50,7 +48,9 @@ console.log(scheduleId);
return x(new Date(d.timeStamp))
};
- var line = bars.enter().append("svg:line")
+ var group = bars.enter().append("svg:g");
+
+ var line = group.append("svg:line")
.attr("x1", currX)
.attr("x2", currX)
.attr("y1", function(d) {
@@ -61,7 +61,7 @@ console.log(scheduleId);
})
.attr("stroke", "lightblue");
- var circleLow = bars.enter().append("svg:circle")
+ var circleLow = group.append("svg:circle")
.attr("cx", currX)
.attr("cy", function(d) {
return h - y(d.low)
@@ -70,7 +70,7 @@ console.log(scheduleId);
.attr("stroke", "green")
.attr("fill", "lightgreen");
- var circleHigh = bars.enter().append("svg:circle")
+ var circleHigh = group.append("svg:circle")
.attr("cx", currX)
.attr("cy", function(d) {
return h - y(d.high)
@@ -79,7 +79,7 @@ console.log(scheduleId);
.attr("stroke", "green")
.attr("fill", "lightblue");
- var circleVal = bars.enter().append("svg:circle")
+ var circleVal = group.append("svg:circle")
.attr("cx", currX)
.attr("cy", function(d) {
return h - y(d.value)
diff --git a/modules/enterprise/server/container/pom.xml b/modules/enterprise/server/container/pom.xml
index 1e55e08..e6fa762 100644
--- a/modules/enterprise/server/container/pom.xml
+++ b/modules/enterprise/server/container/pom.xml
@@ -194,7 +194,6 @@
<property name="jsf-impl.version" value="${jsf-impl.version}" />
<property name="hibernate3.version" value="${hibernate3.version}" />
<property name="quartz.version" value="${quartz.version}" />
- <property name="resteasy.version" value="${resteasy.version}" />
<!-- dev DB settings (used to init rhq-server.properties if dev profile is active or developer prop is set) -->
<property name="rhq.dev.ds.connection-url" value="${rhq.dev.ds.connection-url}" />
diff --git a/modules/enterprise/server/container/src/main/scripts/rhq-container.build.xml b/modules/enterprise/server/container/src/main/scripts/rhq-container.build.xml
index 1f8b8b2..e2f3663 100644
--- a/modules/enterprise/server/container/src/main/scripts/rhq-container.build.xml
+++ b/modules/enterprise/server/container/src/main/scripts/rhq-container.build.xml
@@ -408,27 +408,6 @@
</fileset>
</move>
- <echo>Adding REST support ...</echo>
- <copy file="${settings.localRepository}/org/jboss/resteasy/resteasy-jaxrs/${resteasy.version}/resteasy-jaxrs-${resteasy.version}.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
- <copy file="${settings.localRepository}/org/jboss/resteasy/resteasy-jettison-provider/${resteasy.version}/resteasy-jettison-provider-${resteasy.version}.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
- <copy file="${settings.localRepository}/org/jboss/resteasy/resteasy-jaxb-provider/${resteasy.version}/resteasy-jaxb-provider-${resteasy.version}.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
- <copy file="${settings.localRepository}/org/jboss/resteasy/resteasy-links/${resteasy.version}/resteasy-links-${resteasy.version}.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
- <copy file="${settings.localRepository}/org/jboss/resteasy/jaxrs-api/${resteasy.version}/jaxrs-api-${resteasy.version}.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
-
- <!-- TODO define versions of the following in the root pom -->
- <copy file="${settings.localRepository}/org/scannotation/scannotation/1.0.3/scannotation-1.0.3.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
- <copy file="${settings.localRepository}/net/jcip/jcip-annotations/1.0/jcip-annotations-1.0.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
- <copy file="${settings.localRepository}/org/codehaus/jettison/jettison/1.2/jettison-1.2.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
- <copy file="${settings.localRepository}/org/freemarker/freemarker/2.3.18/freemarker-2.3.18.jar"
- todir="${jboss.home}/server/default/lib" verbose="true" failonerror="true"/>
</target>
diff --git a/modules/enterprise/server/ear/pom.xml b/modules/enterprise/server/ear/pom.xml
index 32ce3d3..a913b44 100644
--- a/modules/enterprise/server/ear/pom.xml
+++ b/modules/enterprise/server/ear/pom.xml
@@ -95,7 +95,7 @@
<groupId>org.rhq</groupId>
<artifactId>rhq-core-domain</artifactId>
</exclusion>
- </exclusions>
+ </exclusions>
</dependency>
<dependency>
@@ -128,6 +128,43 @@
</dependency>
-->
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jaxrs</artifactId>
+ <version>${resteasy.version}</version>
+ <scope>jar</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>javassist</groupId>
+ <artifactId>javassist</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-jettison-provider</artifactId>
+ <version>${resteasy.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.resteasy</groupId>
+ <artifactId>resteasy-links</artifactId>
+ <version>${resteasy.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.freemarker</groupId>
+ <artifactId>freemarker</artifactId>
+ <version>${freemarker.version}</version>
+ <type>jar</type>
+ </dependency>
+
</dependencies>
<build>
@@ -351,13 +388,13 @@
<artifactId>packagetype-cli</artifactId>
<version>${project.version}</version>
</artifactItem>
-
+
<artifactItem>
<groupId>org.rhq</groupId>
<artifactId>rhq-serverplugin-drift</artifactId>
<version>${project.version}</version>
</artifactItem>
-
+
</artifactItems>
<outputDirectory>${earDirectory}/rhq-serverplugins</outputDirectory>
</configuration>
diff --git a/pom.xml b/pom.xml
index 040e7e6..9eae894 100644
--- a/pom.xml
+++ b/pom.xml
@@ -107,7 +107,8 @@
<augeas.zip.location>${settings.localRepository}/net/augeas/augeas-native/${augeas.version}</augeas.zip.location>
<augeas.zip.mask>*-${augeas.classifier}.zip</augeas.zip.mask>
<ant.contrib.version>1.0b3</ant.contrib.version>
- <resteasy.version>2.2.1.GA</resteasy.version>
+ <resteasy.version>2.2.2.GA</resteasy.version>
+ <freemarker.version>2.3.18</freemarker.version>
<rhq.db.admin.username>rhqadmin</rhq.db.admin.username>
<rhq.db.admin.password>rhqadmin</rhq.db.admin.password>
12 years, 5 months
[rhq] Branch 'feature/rest' - 3 commits - modules/enterprise modules/integration-tests
by Heiko W. Rupp
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java | 40 ++-
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerBean.java | 84 +++++++-
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/MetricHandlerLocal.java | 12 +
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java | 4
modules/integration-tests/rest-api/README.md | 13 +
modules/integration-tests/rest-api/pom.xml | 56 +++++
modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/GetStatusTest.java | 78 +++++++
modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/ResourcesTest.java | 104 ++++++++++
8 files changed, 372 insertions(+), 19 deletions(-)
New commits:
commit f74b119c57c4fe7721127b2faa9a5b0b046a8811
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Dec 7 12:04:02 2011 +0100
Initial set of integration tests for the REST api.
diff --git a/modules/integration-tests/rest-api/README.md b/modules/integration-tests/rest-api/README.md
new file mode 100644
index 0000000..b5551be
--- /dev/null
+++ b/modules/integration-tests/rest-api/README.md
@@ -0,0 +1,13 @@
+# REST-API Integration tests
+
+These require a running server with at least one platform as resource id 10001 in inventory.
+Also required are two accounts
+
+* rhqadmin with password rhqadmin in the superuser role
+* user with password name23 as a restricted user
+
+The server to connect to can be given on the command line via `-Drest.server=<server>` as in
+
+ `mvn -Pdev -Drest.server=pintsize test`
+
+If no server is given, localhost is assumed.
\ No newline at end of file
diff --git a/modules/integration-tests/rest-api/pom.xml b/modules/integration-tests/rest-api/pom.xml
new file mode 100644
index 0000000..4a2c4a6
--- /dev/null
+++ b/modules/integration-tests/rest-api/pom.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.rhq</groupId>
+ <artifactId>rhq-integration-tests</artifactId>
+ <version>4.3.0-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+
+ <groupId>org.rhq</groupId>
+ <artifactId>rest-api-tests</artifactId>
+
+ <name>RHQ REST API tests</name>
+
+ <properties>
+ <surefire-plugin.version>2.10</surefire-plugin.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire-plugin.version}</version>
+ <dependencies>
+ <dependency>
+ <!-- explicitly choose the junit4.7+ provider -->
+ <groupId>org.apache.maven.surefire</groupId>
+ <artifactId>surefire-junit47</artifactId>
+ <version>${surefire-plugin.version}</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.restfuse</groupId>
+ <artifactId>com.eclipsesource.restfuse</artifactId>
+ <version>1.0.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.8.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/GetStatusTest.java b/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/GetStatusTest.java
new file mode 100644
index 0000000..b59bd11
--- /dev/null
+++ b/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/GetStatusTest.java
@@ -0,0 +1,78 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation, and/or the GNU Lesser
+ * General Public License, version 2.1, also as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License and the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * and the GNU Lesser General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.rhq.modules.integrationTests.restApi;
+
+
+import com.eclipsesource.restfuse.AuthenticationType;
+import com.eclipsesource.restfuse.Destination;
+import com.eclipsesource.restfuse.HttpJUnitRunner;
+import com.eclipsesource.restfuse.Method;
+import com.eclipsesource.restfuse.Response;
+import com.eclipsesource.restfuse.annotation.Authentication;
+import com.eclipsesource.restfuse.annotation.Context;
+import com.eclipsesource.restfuse.annotation.HttpTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test the status part of the API
+ */
+(a)RunWith(HttpJUnitRunner.class)
+public class GetStatusTest {
+
+
+
+ @Rule
+ public Destination destination = new Destination("http://" + System.getProperty("rest.server","localhost") + ":7080/rest/1");
+
+ @Context
+ private Response response;
+
+
+ @Test
+ @HttpTest( method = Method.GET, path = "/status" )
+ public void testAuthRequired() {
+ com.eclipsesource.restfuse.Assert.assertUnauthorized(response);
+ }
+
+
+ @Test
+ @HttpTest( method = Method.GET, path = "/status" ,authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin")
+ )
+ public void testAuthRhqadmin() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ }
+
+ @Test
+ @HttpTest( method = Method.GET, path = "/status" ,authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "user", password = "name23")
+ )
+ public void testAuthRestricted() {
+ com.eclipsesource.restfuse.Assert.assertForbidden(response);
+ }
+
+
+}
diff --git a/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/ResourcesTest.java b/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/ResourcesTest.java
new file mode 100644
index 0000000..88cfaaf
--- /dev/null
+++ b/modules/integration-tests/rest-api/src/test/java/org/rhq/modules/integrationTests/restApi/ResourcesTest.java
@@ -0,0 +1,104 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2011 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation, and/or the GNU Lesser
+ * General Public License, version 2.1, also as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License and the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * and the GNU Lesser General Public License along with this program;
+ * if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.rhq.modules.integrationTests.restApi;
+
+import com.eclipsesource.restfuse.AuthenticationType;
+import com.eclipsesource.restfuse.Destination;
+import com.eclipsesource.restfuse.HttpJUnitRunner;
+import com.eclipsesource.restfuse.Method;
+import com.eclipsesource.restfuse.Response;
+import com.eclipsesource.restfuse.annotation.Authentication;
+import com.eclipsesource.restfuse.annotation.Context;
+import com.eclipsesource.restfuse.annotation.Header;
+import com.eclipsesource.restfuse.annotation.HttpTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertTrue;
+
+/**
+ * Test the resources part
+ * @author Heiko W. Rupp
+ */
+(a)RunWith(HttpJUnitRunner.class)
+public class ResourcesTest {
+
+ @Rule
+ public Destination destination = new Destination("http://" + System.getProperty("rest.server","localhost") + ":7080/rest/1");
+
+ @Context
+ private Response response;
+
+
+ @HttpTest( method = Method.GET, path = "/resource/platforms" )
+ public void testAuthRequired() {
+ com.eclipsesource.restfuse.Assert.assertUnauthorized(response);
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/platforms",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin")
+ )
+ public void testPlatformsPresent() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/10001",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin"),
+ headers = {@Header(name = "Accept",value ="application/json")}
+ )
+ public void testGetPlatformJson() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/10001",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin"),
+ headers = {@Header(name = "Accept",value ="application/xml")}
+ )
+ public void testGetPlatformXml() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/10001/schedules",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin"),
+ headers = {@Header(name = "Accept",value ="application/json")}
+ )
+ public void testGetPlatformSchedules() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+ @HttpTest( method = Method.GET, path = "/resource/10001/children",authentications =
+ @Authentication(type = AuthenticationType.BASIC, user = "rhqadmin", password = "rhqadmin"),
+ headers = {@Header(name = "Accept",value ="application/json")}
+ )
+ public void testGetPlatformChildren() {
+ com.eclipsesource.restfuse.Assert.assertOk(response);
+ assertTrue(response.hasBody());
+ }
+
+}
commit 56eab8fd42ea5c3cfece191a71fef81fd8bf9c54
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Dec 7 12:03:28 2011 +0100
Improve Exception mapping
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
index d951767..58ccf30 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/CustomExceptionMapper.java
@@ -18,11 +18,11 @@
*/
package org.rhq.enterprise.server.rest;
-import javax.ejb.EJBException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
+import org.rhq.enterprise.server.authz.PermissionException;
import org.rhq.enterprise.server.resource.ResourceNotFoundException;
/**
@@ -30,31 +30,39 @@ import org.rhq.enterprise.server.resource.ResourceNotFoundException;
* @author Heiko W. Rupp
*/
@Provider
-public class CustomExceptionMapper implements ExceptionMapper<EJBException> {
+public class CustomExceptionMapper implements ExceptionMapper<Exception> {
@Override
- public Response toResponse(EJBException e) {
+ public Response toResponse(Exception e) {
Throwable cause = e.getCause();
+ Response.ResponseBuilder builder;
if (cause !=null) {
+ Response.Status status;
if (cause instanceof StuffNotFoundException)
- return Response.status(Response.Status.NOT_FOUND)
- .entity(cause.getMessage())
- .build();
+ status =Response.Status.NOT_FOUND;
else if (cause instanceof ResourceNotFoundException)
- return Response.status(Response.Status.NOT_FOUND)
- .entity(cause.getMessage())
- .build();
+ status = Response.Status.NOT_FOUND;
else if (cause instanceof ParameterMissingException)
- return Response.status(Response.Status.NOT_ACCEPTABLE)
- .entity(cause.getMessage())
- .build();
+ status = Response.Status.NOT_ACCEPTABLE;
+ else if (cause instanceof PermissionException)
+ status = Response.Status.FORBIDDEN;
else
- return Response.status(Response.Status.SERVICE_UNAVAILABLE)
- .entity(cause.getMessage())
- .build();
+ status = Response.Status.SERVICE_UNAVAILABLE;
+
+ builder = Response.status(status);
+ builder.entity(cause.getMessage());
+ }
+ else {
+ if (e instanceof PermissionException) {
+ builder = Response.status(Response.Status.FORBIDDEN);
+ } else {
+ builder = Response.status(Response.Status.INTERNAL_SERVER_ERROR);
+ }
+ if (e.getMessage()!=null)
+ builder.entity(e.getMessage());
}
- throw e;
+ return builder.build();
}
}
commit 8c1ab57450bc314e7a469ff968d4c24a806905ef
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Wed Dec 7 09:30:45 2011 +0100
Allow to obtain metrics for multiple schedules at once.
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 8be8070..5934dbe 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
@@ -26,9 +26,14 @@ import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.GenericEntity;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
@@ -38,6 +43,7 @@ import javax.ws.rs.core.UriInfo;
import org.jboss.cache.Fqn;
+import org.rhq.core.domain.common.EntityContext;
import org.rhq.core.domain.measurement.DataType;
import org.rhq.core.domain.measurement.MeasurementDefinition;
import org.rhq.core.domain.measurement.MeasurementSchedule;
@@ -73,7 +79,7 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
@Context Request request,
@Context HttpHeaders headers) {
- if (dataPoints<0)
+ if (dataPoints<=0)
throw new IllegalArgumentException("dataPoints must be >0 ");
if (startTime==0) {
@@ -103,11 +109,14 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
List<List<MeasurementDataNumericHighLowComposite>> listList = dataManager.findDataForResource(caller,
schedule.getResource().getId(), new int[]{definitionId}, startTime, endTime, dataPoints);
+/*
long minTime=Long.MAX_VALUE;
long maxTime=0;
+*/
if (!listList.isEmpty()) {
List<MeasurementDataNumericHighLowComposite> list = listList.get(0);
+/*
for (MeasurementDataNumericHighLowComposite c : list) {
long timestamp = c.getTimestamp();
if (!Double.isNaN(c.getValue()) || !hideEmpty) {
@@ -123,6 +132,9 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
}
res.setMaxTimeStamp(maxTime);
res.setMinTimeStamp(minTime);
+*/
+ res = fill(list,scheduleId,hideEmpty);
+ }
CacheControl cc = new CacheControl();
int maxAge = (int) (schedule.getInterval() / 1000L)/2; // millis ; half of schedule interval
@@ -144,6 +156,76 @@ public class MetricHandlerBean extends AbstractRestBean implements MetricHandle
return builder.build();
}
+ private MetricAggregate fill(List<MeasurementDataNumericHighLowComposite> list,int scheduleId , boolean hideEmpty) {
+ long minTime=Long.MAX_VALUE;
+ long maxTime=0;
+ MetricAggregate res = new MetricAggregate();
+ res.setScheduleId(scheduleId);
+
+ for (MeasurementDataNumericHighLowComposite c : list) {
+ long timestamp = c.getTimestamp();
+ if (!Double.isNaN(c.getValue()) || !hideEmpty) {
+ MetricAggregate.DataPoint dp = new MetricAggregate.DataPoint(timestamp,c.getValue(),c.getHighValue(),c.getLowValue());
+ res.addDataPoint(dp);
+ }
+ if (timestamp <minTime)
+ minTime= timestamp;
+ if (timestamp >maxTime)
+ maxTime= timestamp;
+ }
+ res.setNumDataPoints(list.size());
+ res.setMaxTimeStamp(maxTime);
+ res.setMinTimeStamp(minTime);
+
+ return res;
+ }
+
+ @Override
+ @GET
+ @Path("data")
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_HTML})
+ public Response getMetricDataMulti(@QueryParam("sid") String schedules, @QueryParam("startTime") long startTime,
+ @QueryParam("endTime") long endTime, @QueryParam("dataPoints") int dataPoints,
+ @QueryParam("hideEmpty") boolean hideEmpty, @Context Request request,
+ @Context HttpHeaders headers) {
+
+ if (startTime==0) {
+ endTime = System.currentTimeMillis();
+ startTime = endTime - EIGHT_HOURS;
+ }
+
+ String[] tmp = schedules.split(",");
+ Integer[] scheduleIds = new Integer[tmp.length];
+ try {
+ for (int i = 0; i < tmp.length ; i++)
+ scheduleIds[i] = Integer.parseInt(tmp[i]);
+ }
+ catch (NumberFormatException nfe) {
+ throw new IllegalArgumentException("Bad input: " + nfe.getMessage());
+ }
+
+ List<MetricAggregate> resList = new ArrayList<MetricAggregate>(scheduleIds.length);
+ for (Integer scheduleId : scheduleIds) {
+ MeasurementSchedule sched = scheduleManager.getScheduleById(caller,scheduleId);
+ if (sched==null)
+ throw new StuffNotFoundException("Schedule with id " + scheduleId);
+ int definitionId = sched.getDefinition().getId();
+ List<List<MeasurementDataNumericHighLowComposite>> listList =
+ dataManager.findDataForContext(caller, EntityContext.forResource(sched.getResource().getId()),definitionId,startTime,endTime,dataPoints);
+ if (!listList.isEmpty()) {
+ List<MeasurementDataNumericHighLowComposite> list = listList.get(0);
+ MetricAggregate ma = fill(list,scheduleId,hideEmpty);
+ resList.add(ma);
+ }
+ else
+ throw new StuffNotFoundException("Metrics for schedule " + scheduleId);
+ }
+
+ GenericEntity<List<MetricAggregate>> metAgg = new GenericEntity<List<MetricAggregate>>(resList) {};
+
+ return Response.ok(metAgg).build();
+
+ }
/**
* Return a metric schedule with the respective status codes for cache validation
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 44844ae..7e908ad 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
@@ -18,7 +18,6 @@
*/
package org.rhq.enterprise.server.rest;
-import java.io.InputStream;
import java.util.List;
import javax.ejb.Local;
@@ -60,6 +59,17 @@ public interface MetricHandlerLocal {
@Context Request request,
@Context HttpHeaders headers);
+ @GET
+ @Path("data")
+ @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML,MediaType.TEXT_HTML})
+ Response getMetricDataMulti(@QueryParam("sid") String scheduleIds,
+ @QueryParam("startTime") long startTime,
+ @QueryParam("endTime") long endTime,
+ @QueryParam("dataPoints") int dataPoints,
+ @QueryParam("hideEmpty") boolean hideEmpty,
+ @Context Request request,
+ @Context HttpHeaders headers);
+
@GET
@Path("data/resource/{resourceId}")
diff --git a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java
index d6af76e..16b6d40 100644
--- a/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java
+++ b/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/domain/MetricAggregate.java
@@ -41,15 +41,17 @@ public class MetricAggregate {
long maxTimeStamp;
public MetricAggregate() {
+ dataPoints = new ArrayList<DataPoint>();
}
public MetricAggregate(Integer scheduleId, double min, double avg, double max) {
+ this();
this.scheduleId = scheduleId;
this.min = min;
this.avg = avg;
this.max = max;
- dataPoints = new ArrayList<DataPoint>();
+
}
@XmlElement
12 years, 5 months
[rhq] modules/enterprise
by mazz
modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleDeploymentListView.java | 10 ++++++++++
1 file changed, 10 insertions(+)
New commits:
commit bf5446c876c5d799a6fb58cf3c6a2e050a0c6390
Author: John Mazzitelli <mazz(a)redhat.com>
Date: Tue Dec 6 17:03:30 2011 -0500
[BZ 755019] sort the deployment table
diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleDeploymentListView.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleDeploymentListView.java
index 4f78a16..a1f2c13 100644
--- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleDeploymentListView.java
+++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleDeploymentListView.java
@@ -26,6 +26,7 @@ import java.util.HashMap;
import com.smartgwt.client.data.Criteria;
import com.smartgwt.client.types.ListGridFieldType;
+import com.smartgwt.client.types.SortDirection;
import com.smartgwt.client.widgets.events.DoubleClickEvent;
import com.smartgwt.client.widgets.events.DoubleClickHandler;
import com.smartgwt.client.widgets.grid.CellFormatter;
@@ -44,6 +45,7 @@ import org.rhq.enterprise.gui.coregui.client.components.table.EscapedHtmlCellFor
import org.rhq.enterprise.gui.coregui.client.components.table.Table;
import org.rhq.enterprise.gui.coregui.client.components.table.TimestampCellFormatter;
import org.rhq.enterprise.gui.coregui.client.util.StringUtility;
+import org.rhq.enterprise.gui.coregui.client.util.selenium.LocatableListGrid;
/**
* @author Greg Hinkle
@@ -60,6 +62,14 @@ public class BundleDeploymentListView extends Table<BundleDeploymentDataSource>
}
@Override
+ protected LocatableListGrid createListGrid(String locatorId) {
+ LocatableListGrid grid = super.createListGrid(locatorId);
+ grid.setSortField(BundleDeploymentDataSource.FIELD_DEPLOY_TIME);
+ grid.setSortDirection(SortDirection.DESCENDING);
+ return grid;
+ }
+
+ @Override
protected void configureTable() {
ListGridField idField = new ListGridField(BundleDeploymentDataSource.FIELD_ID, MSG.common_title_id());
ListGridField nameField = new ListGridField(BundleDeploymentDataSource.FIELD_NAME, MSG
12 years, 5 months
[rhq] modules/enterprise
by mazz
modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleResourceDeploymentHistoryListView.java | 3 +++
1 file changed, 3 insertions(+)
New commits:
commit 8a2f8f8caf0c10cfc279dc49a8526da44bb7702b
Author: John Mazzitelli <mazz(a)redhat.com>
Date: Tue Dec 6 16:23:05 2011 -0500
[BZ 755019] sort the deployment audit trail
diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleResourceDeploymentHistoryListView.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleResourceDeploymentHistoryListView.java
index 749b042..63a10d7 100644
--- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleResourceDeploymentHistoryListView.java
+++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/bundle/deployment/BundleResourceDeploymentHistoryListView.java
@@ -32,6 +32,7 @@ import com.smartgwt.client.types.DateDisplayFormat;
import com.smartgwt.client.types.ExpansionMode;
import com.smartgwt.client.types.ListGridFieldType;
import com.smartgwt.client.types.SelectionStyle;
+import com.smartgwt.client.types.SortDirection;
import com.smartgwt.client.types.VerticalAlignment;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.events.DoubleClickEvent;
@@ -92,6 +93,8 @@ public class BundleResourceDeploymentHistoryListView extends LocatableVLayout {
grid.setCanExpandRecords(true);
grid.setExpansionMode(ExpansionMode.DETAIL_FIELD);
grid.setDetailField("message");
+ grid.setSortField("timestamp");
+ grid.setSortDirection(SortDirection.ASCENDING);
ListGridField action = new ListGridField("action", MSG.view_bundle_deploy_action());
action.setAutoFitWidth(true);
12 years, 5 months
[rhq] modules/core
by Jay Shaughnessy
modules/core/dbutils/src/main/java/org/rhq/core/db/DatabaseType.java | 20 ++
modules/core/dbutils/src/main/java/org/rhq/core/db/OracleDatabaseType.java | 4
modules/core/dbutils/src/main/java/org/rhq/core/db/upgrade/DriftPathDirectoryDatabaseUpgradeTask.java | 71 ++++++++++
modules/core/dbutils/src/main/java/org/rhq/core/db/upgrade/DriftTemplateMapDatabaseUpgradeTask.java | 65 +++++++++
modules/core/dbutils/src/main/scripts/dbupgrade/db-upgrade.xml | 17 +-
5 files changed, 167 insertions(+), 10 deletions(-)
New commits:
commit 64c7032453ec453246ffb3a9a2bcb664903be88e
Author: Jay Shaughnessy <jshaughn(a)redhat.com>
Date: Tue Dec 6 16:11:42 2011 -0500
[Bug 753866 - DB Upgrade needs more work for existing drift records]
This is to support RHQ 4.1 and 4.2 drift upgrades to 4.3. It adds
two Java upgrade tasks and puts them into the existing 2.115 step. (JON
note, JON3.0 already places the db at 2.116 and the previous
version of 2.115 was already sufficient for JON3.0 purposes. We *do not*
want these new changes applied to JON3.0 upgrades and so I did not
create a new schema task for this, on purpose.)
diff --git a/modules/core/dbutils/src/main/java/org/rhq/core/db/DatabaseType.java b/modules/core/dbutils/src/main/java/org/rhq/core/db/DatabaseType.java
index 27fee40..eb2f6a7 100644
--- a/modules/core/dbutils/src/main/java/org/rhq/core/db/DatabaseType.java
+++ b/modules/core/dbutils/src/main/java/org/rhq/core/db/DatabaseType.java
@@ -560,6 +560,20 @@ public abstract class DatabaseType {
public abstract int getNextSequenceValue(Connection conn, String table, String key) throws SQLException;
/**
+ * A utility that returns the string for sequence use in an Insert statement
+ *
+ * @param conn The connection to use to get the sequence value.
+ * @param sequenceName The sequence name for the table.
+ *
+ * @return the sequence generating string
+ *
+ * @throws SQLException
+ */
+ public String getSequenceInsertValue(Connection conn, String sequenceName) {
+ return "nextval('" + sequenceName + "')";
+ }
+
+ /**
* Creates a sequence with the given name. Its initial value is specified along with its increment (both are
* specified as Strings).
*
@@ -570,13 +584,15 @@ public abstract class DatabaseType {
* @param seqIdCacheSize
* @throws SQLException
*/
- public void createSequence(Connection conn, String name, String initial, String increment, String seqIdCacheSize) throws SQLException {
+ public void createSequence(Connection conn, String name, String initial, String increment, String seqIdCacheSize)
+ throws SQLException {
CreateSequenceExprBuilder builder = CreateSequenceExprBuilder.getBuilder(this);
HashMap<String, Object> terms = new HashMap<String, Object>();
terms.put(CreateSequenceExprBuilder.KEY_SEQ_NAME, name);
terms.put(CreateSequenceExprBuilder.KEY_SEQ_START, initial);
terms.put(CreateSequenceExprBuilder.KEY_SEQ_INCREMENT, increment);
- terms.put(CreateSequenceExprBuilder.KEY_SEQ_CACHE_SIZE, CreateSequenceExprBuilder.getSafeSequenceCacheSize(builder, seqIdCacheSize));
+ terms.put(CreateSequenceExprBuilder.KEY_SEQ_CACHE_SIZE, CreateSequenceExprBuilder.getSafeSequenceCacheSize(
+ builder, seqIdCacheSize));
executeSql(conn, builder.build(terms));
}
diff --git a/modules/core/dbutils/src/main/java/org/rhq/core/db/OracleDatabaseType.java b/modules/core/dbutils/src/main/java/org/rhq/core/db/OracleDatabaseType.java
index abf73a3..b4ea5e9 100644
--- a/modules/core/dbutils/src/main/java/org/rhq/core/db/OracleDatabaseType.java
+++ b/modules/core/dbutils/src/main/java/org/rhq/core/db/OracleDatabaseType.java
@@ -95,6 +95,10 @@ public abstract class OracleDatabaseType extends DatabaseType {
}
}
+ public String getSequenceInsertValue(Connection conn, String sequenceName) {
+ return sequenceName + ".NEXTVAL";
+ }
+
/**
* @see DatabaseType#getNextSequenceValue(Connection, String, String)
*/
diff --git a/modules/core/dbutils/src/main/java/org/rhq/core/db/upgrade/DriftPathDirectoryDatabaseUpgradeTask.java b/modules/core/dbutils/src/main/java/org/rhq/core/db/upgrade/DriftPathDirectoryDatabaseUpgradeTask.java
new file mode 100644
index 0000000..6b87331
--- /dev/null
+++ b/modules/core/dbutils/src/main/java/org/rhq/core/db/upgrade/DriftPathDirectoryDatabaseUpgradeTask.java
@@ -0,0 +1,71 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2008 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.rhq.core.db.upgrade;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import mazz.i18n.Logger;
+
+import org.rhq.core.db.DatabaseType;
+import org.rhq.core.db.DbUtilsI18NFactory;
+import org.rhq.core.db.DbUtilsI18NResourceKeys;
+
+/**
+ * Database upgrade task 2.115 adds rhq_drift.path_directory column to store the directory portion of the path
+ * column. This code sets the value appropriately.
+ *
+ * @author Jay Shaughnessy
+ */
+public class DriftPathDirectoryDatabaseUpgradeTask implements DatabaseUpgradeTask {
+
+ private static final Logger LOG = DbUtilsI18NFactory.getLogger(DriftPathDirectoryDatabaseUpgradeTask.class);
+
+ public void execute(DatabaseType databaseType, Connection connection) throws SQLException {
+
+ Statement s = null;
+
+ try {
+ String sql = "select d.ID, d.PATH, d.PATH_DIRECTORY from RHQ_DRIFT d";
+ LOG.debug(DbUtilsI18NResourceKeys.EXECUTING_SQL, sql);
+
+ s = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
+ ResultSet rs = s.executeQuery(sql);
+
+ while (rs.next()) {
+ // note, this is guaranteed to be non-null and valid
+ String path = rs.getString(2);
+ String dir = getDir(path);
+ rs.updateString(3, dir);
+ rs.updateRow();
+ }
+ } finally {
+ databaseType.closeStatement(s);
+ }
+ }
+
+ private String getDir(String path) {
+ int i = path.lastIndexOf("/");
+ String result = (i != -1) ? path.substring(0, i) : "./";
+
+ return result;
+ }
+}
diff --git a/modules/core/dbutils/src/main/java/org/rhq/core/db/upgrade/DriftTemplateMapDatabaseUpgradeTask.java b/modules/core/dbutils/src/main/java/org/rhq/core/db/upgrade/DriftTemplateMapDatabaseUpgradeTask.java
new file mode 100644
index 0000000..d060a2a
--- /dev/null
+++ b/modules/core/dbutils/src/main/java/org/rhq/core/db/upgrade/DriftTemplateMapDatabaseUpgradeTask.java
@@ -0,0 +1,65 @@
+/*
+ * RHQ Management Platform
+ * Copyright (C) 2005-2008 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+package org.rhq.core.db.upgrade;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import mazz.i18n.Logger;
+
+import org.rhq.core.db.DatabaseType;
+import org.rhq.core.db.DbUtilsI18NFactory;
+import org.rhq.core.db.DbUtilsI18NResourceKeys;
+
+/**
+ * Database upgrade task 2.115 adds the rhq_drift_def_template table. The table is initially populated here
+ * by using data in the to-be-removed rhq_drift_template_map table.
+ *
+ * @author Jay Shaughnessy
+ */
+public class DriftTemplateMapDatabaseUpgradeTask implements DatabaseUpgradeTask {
+
+ private static final Logger LOG = DbUtilsI18NFactory.getLogger(DriftPathDirectoryDatabaseUpgradeTask.class);
+
+ public void execute(DatabaseType databaseType, Connection connection) throws SQLException {
+
+ String sql = "" //
+ + " insert into RHQ_DRIFT_DEF_TEMPLATE (" //
+ + " ID," //
+ + " RESOURCE_TYPE_ID," //
+ + " CONFIG_ID," //
+ + " NAME," //
+ + " DESCRIPTION," //
+ + " IS_USER_DEFINED," //
+ + " CTIME )" //
+ + " select " + databaseType.getSequenceInsertValue(connection, "RHQ_DRIFT_DEF_TEMPLATE_ID_SEQ") + "," //
+ + " tm.RESOURCE_TYPE_ID, " //
+ + " ct.CONFIG_ID, " //
+ + " ct.NAME, " //
+ + " ct.DESCRIPTION, " //
+ + " " + databaseType.getBooleanValue(false) + "," //
+ + " " + System.currentTimeMillis() //
+ + " from RHQ_DRIFT_TEMPLATE_MAP tm " //
+ + " join RHQ_RESOURCE_TYPE rt on tm.RESOURCE_TYPE_ID = rt.ID" //
+ + " join RHQ_CONFIG_TEMPLATE ct on tm.CONFIG_TEMPLATE_ID = ct.ID";
+ LOG.debug(DbUtilsI18NResourceKeys.EXECUTING_SQL, sql);
+
+ databaseType.executeSql(connection, sql);
+ }
+}
diff --git a/modules/core/dbutils/src/main/scripts/dbupgrade/db-upgrade.xml b/modules/core/dbutils/src/main/scripts/dbupgrade/db-upgrade.xml
index fb2ed85..8f10a3b 100644
--- a/modules/core/dbutils/src/main/scripts/dbupgrade/db-upgrade.xml
+++ b/modules/core/dbutils/src/main/scripts/dbupgrade/db-upgrade.xml
@@ -3781,12 +3781,11 @@
<!-- RHQ_DRIFT -->
<!-- Add new path_directory column -->
<schema-addColumn table="RHQ_DRIFT" column="PATH_DIRECTORY" columnType="VARCHAR2" precision="1024" />
- <!-- TODO: This needs to be a java update step! -->
- <schema-directSQL>
- <statement desc="RHQ_DRIFT_DEFINITION: set drift_handling_mode column (normal)">
- UPDATE RHQ_DRIFT SET PATH_DIRECTORY = PATH
- </statement>
- </schema-directSQL>
+
+ <!-- Set the new path_directory column using the current path column values -->
+ <schema-javaTask className="DriftPathDirectoryDatabaseUpgradeTask" />
+
+ <!-- Now it is safe to set the new path_directory column not null -->
<schema-alterColumn table="RHQ_DRIFT" column="PATH_DIRECTORY" nullable="FALSE" />
<!-- add new drift_set_id column and FK -->
@@ -3800,8 +3799,10 @@
</statement>
</schema-directSQL>
- <!-- TODO For RHQ 4.3: Java upgrade step to set path_directory from path in rhq_drift -->
- <!-- TODO For RHQ 4.3: Java upgrade step for rhq_drift_template_map conversion and removal -->
+ <!-- Morph rhq_drift_template_map rows into rhq_drift_def_template rows. The rhq_drift_template_map
+ table is obsolete. -->
+ <schema-javaTask className="DriftTemplateMapDatabaseUpgradeTask" />
+
</schemaSpec>
<schemaSpec version="2.116">
12 years, 5 months
[rhq] Branch 'feature/rest' - modules/enterprise
by Heiko W. Rupp
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerBean.java | 39 +++++-----
modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/rest/ResourceHandlerLocal.java | 1
2 files changed, 22 insertions(+), 18 deletions(-)
New commits:
commit 71db2833a06ad1537d8a27fe5a0399850d6f81bd
Author: Heiko W. Rupp <hwr(a)redhat.com>
Date: Tue Dec 6 16:48:56 2011 +0100
Allow to limit schedules by name
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 59efeba..64cdea3 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
@@ -195,6 +195,7 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
public Response getSchedules(int resourceId,
String scheduleType,
boolean enabledOnly,
+ String name,
@Context Request request,
@Context HttpHeaders headers,
@Context UriInfo uriInfo) {
@@ -216,26 +217,28 @@ public class ResourceHandlerBean extends AbstractRestBean implements ResourceHan
if ("all".equals(scheduleType) ||
scheduleType.toLowerCase().equals(definition.getDataType().toString().toLowerCase()) ) {
if (!enabledOnly || (enabledOnly && schedule.isEnabled() )) {
- MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
- schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
- definition.getDataType().toString());
- UriBuilder uriBuilder;
- URI uri;
- if (definition.getDataType()== DataType.MEASUREMENT) {
+ if (name==null || (name!=null && name.equals(definition.getName()))) {
+ MetricSchedule ms = new MetricSchedule(schedule.getId(), definition.getName(), definition.getDisplayName(),
+ schedule.isEnabled(),schedule.getInterval(), definition.getUnits().toString(),
+ definition.getDataType().toString());
+ UriBuilder uriBuilder;
+ URI uri;
+ if (definition.getDataType()== DataType.MEASUREMENT) {
+ uriBuilder = uriInfo.getBaseUriBuilder();
+ uriBuilder.path("/metric/data/{id}");
+ 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("/metric/data/{id}");
- uri = uriBuilder.build(schedule.getId());
- Link metricLink = new Link("metric",uri.toString());
- ms.addLink(metricLink);
+ uriBuilder.path("resource/" + schedule.getResource().getId());
+ uri = uriBuilder.build();
+ Link link = new Link("resource",uri.toString());
+ ms.addLink(link);
+
+ ret.add(ms);
}
- // 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);
}
}
}
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 ab9ea62..77f268f 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
@@ -88,6 +88,7 @@ public interface ResourceHandlerLocal {
Response getSchedules(@PathParam("id") int resourceId,
@QueryParam("type") @DefaultValue("all") String scheduleType,
@QueryParam("enabledOnly") @DefaultValue("true") boolean enabledOnly,
+ @QueryParam("name") String name,
@Context Request request,
@Context HttpHeaders headers,
@Context UriInfo uriInfo);
12 years, 5 months
[rhq] modules/core
by mazz
modules/core/plugin-container/src/main/java/org/rhq/core/pc/bundle/BundleManager.java | 39 +++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
New commits:
commit 16823b9934ea91e81e0b7102bd464a57875fe033
Author: John Mazzitelli <mazz(a)redhat.com>
Date: Tue Dec 6 14:16:32 2011 -0500
[BZ 752550] remove old downloaded bundle files to help clean out the tmp directory
diff --git a/modules/core/plugin-container/src/main/java/org/rhq/core/pc/bundle/BundleManager.java b/modules/core/plugin-container/src/main/java/org/rhq/core/pc/bundle/BundleManager.java
index d240606..561e4a1 100644
--- a/modules/core/plugin-container/src/main/java/org/rhq/core/pc/bundle/BundleManager.java
+++ b/modules/core/plugin-container/src/main/java/org/rhq/core/pc/bundle/BundleManager.java
@@ -23,6 +23,7 @@
package org.rhq.core.pc.bundle;
import java.io.File;
+import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashMap;
@@ -47,10 +48,10 @@ import org.rhq.core.domain.bundle.BundleDeploymentStatus;
import org.rhq.core.domain.bundle.BundleDestination;
import org.rhq.core.domain.bundle.BundleResourceDeployment;
import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory;
+import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory.Status;
import org.rhq.core.domain.bundle.BundleType;
import org.rhq.core.domain.bundle.BundleVersion;
import org.rhq.core.domain.bundle.ResourceTypeBundleConfiguration;
-import org.rhq.core.domain.bundle.BundleResourceDeploymentHistory.Status;
import org.rhq.core.domain.bundle.ResourceTypeBundleConfiguration.BundleDestinationBaseDirectory;
import org.rhq.core.domain.content.PackageVersion;
import org.rhq.core.domain.resource.Resource;
@@ -72,6 +73,7 @@ import org.rhq.core.pluginapi.bundle.BundleManagerProvider;
import org.rhq.core.pluginapi.bundle.BundlePurgeResult;
import org.rhq.core.util.MessageDigestGenerator;
import org.rhq.core.util.exception.ThrowableUtil;
+import org.rhq.core.util.file.FileUtil;
/**
* Manages the bundle subsystem, which allows bundles of content to be installed.
@@ -172,6 +174,12 @@ public class BundleManager extends AgentService implements BundleAgentService, B
File bundleFilesDir = new File(pluginTmpDir, "bundle-versions/"
+ bundleDeployment.getBundleVersion().getId());
bundleFilesDir.mkdirs();
+
+ // clean up any old downloads we may have retrieved before. This helps clean out
+ // our temp directory so we don't unnecessarily fill up our file system with obsolete files
+ removeOldDownloadedBundleFiles(bundleFilesDir);
+
+ // now download the bundle files we need for the current deployment
Map<PackageVersion, File> downloadedFiles = downloadBundleFiles(resourceDeployment,
bundleFilesDir);
@@ -311,6 +319,35 @@ public class BundleManager extends AgentService implements BundleAgentService, B
}
/**
+ * Given a directory where the current deployment's bundle files will reside, this method will clear out
+ * any other files located in other peer directories. In other words, the given directory and all its
+ * subdirectories and child files are left untouched, but all files and directories found in peer
+ * directories are wiped. This helps clean out our temp directory so we don't fill up the file system
+ * with old downloaded files that we don't need anymore. See BZ 752550.
+ *
+ * @param currentBundleVersionFilesDir
+ */
+ private void removeOldDownloadedBundleFiles(final File currentBundleVersionFilesDir) {
+ File parent = null;
+ try {
+ parent = currentBundleVersionFilesDir.getParentFile();
+ File[] doomedFiles = parent.listFiles(new FileFilter() {
+ public boolean accept(File child) {
+ return !currentBundleVersionFilesDir.equals(child);
+ }
+ });
+ for (File doomedFile : doomedFiles) {
+ FileUtil.purge(doomedFile, true);
+ }
+ } catch (Exception e) {
+ log.warn("Failed to clean up old downloaded bundle files in ["
+ + parent
+ + "]. You can ignore this but if the agent is asked to deploy a lot of bundles, the file system may fill up."
+ + " Cause: " + e);
+ }
+ }
+
+ /**
* Downloads the bundle's files into the bundle plugin's tmp directory and returns that tmp directory.
*
* @param resourceDeployment access to deployment information, including what bundle files need to be downloaded
12 years, 5 months
[rhq] Changes to 'stefan/backingcontent'
by snegrea
New branch 'stefan/backingcontent' available with the following commits:
commit a1f35576f299d9d1aee602c7d6151eb7dee5fe45
Author: Stefan Negrea <snegrea(a)redhat.com>
Date: Tue Dec 6 12:46:44 2011 -0600
[BZ 758753] If a package is successfully installed and the resource is a cotennt resource then update the installed package information in the database (delete old entry and add a new entry).
commit d4021138f68209e7743011179eb97b5897c9b4fe
Author: Stefan Negrea <snegrea(a)redhat.com>
Date: Mon Dec 5 09:32:43 2011 -0600
[BZ 758753] Use a single bean method for all deployment requests. A single point of entry simplifies design and allows adding post installation processing code in a single place.
12 years, 5 months
[rhq] modules/enterprise
by Jay Shaughnessy
modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselMemberView.java | 1 +
modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselView.java | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
New commits:
commit 32e06f0466e3a6a440518c4bd72e21c012bd6afa
Author: Jay Shaughnessy <jshaughn(a)redhat.com>
Date: Tue Dec 6 12:08:35 2011 -0500
[Bug 760209 - Drift Carousel not filtering the drift entries]
I think this broke when initial criteria handling changed in the
underlying Table implementation. The initial criteria was set correctly
but that was no longer applied directly to the grid, instead we now
manually perform the initial fetch. But that led to an overriden method
getting called to return the current criteria, which had yet to be
determined (it was thought to be needed on refresh only). So, the fix was
to immediately set the current criteria to the initial criteria in the
subclass.
diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselMemberView.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselMemberView.java
index 47b5d3d..991ec84 100644
--- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselMemberView.java
+++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselMemberView.java
@@ -71,6 +71,7 @@ public class DriftCarouselMemberView extends DriftHistoryView implements Carouse
super(locatorId, null, context, hasWriteAccess, initialCriteria);
+ this.carouselCriteria = initialCriteria;
this.changeSet = driftChangeSet;
((DriftCarouselDataSource) getDataSource()).setChangeSetId(this.changeSet.getId());
diff --git a/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselView.java b/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselView.java
index 6ccbb17..f941c6f 100644
--- a/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselView.java
+++ b/modules/enterprise/gui/coregui/src/main/java/org/rhq/enterprise/gui/coregui/client/drift/DriftCarouselView.java
@@ -242,7 +242,7 @@ public class DriftCarouselView extends BookmarkableCarousel implements DetailsVi
}
changeSetCriteria.addFilterStartVersion(String.valueOf(startVersion));
- // apply the drift-level carousel filters in order to filter out changesets that have no applicab;e drift
+ // apply the drift-level carousel filters in order to filter out changesets that have no applicable drift
Criteria criteria = getCurrentCriteria();
DriftCategory[] driftCategoriesFilter = RPCDataSource.getArrayFilter(criteria,
DriftDataSource.FILTER_CATEGORIES, DriftCategory.class);
12 years, 5 months