/*
* Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hawkular.inventory.rest.deprecated;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.Response.Status.FORBIDDEN;
import static org.hawkular.inventory.rest.RequestUtil.extractPaging;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.Encoded;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
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.Response;
import javax.ws.rs.core.UriInfo;
import org.hawkular.inventory.api.Metrics;
import org.hawkular.inventory.api.Resources;
import org.hawkular.inventory.api.model.Metric;
import org.hawkular.inventory.api.paging.Page;
import org.hawkular.inventory.paths.CanonicalPath;
import org.hawkular.inventory.paths.Path;
import org.hawkular.inventory.rest.RequestUtil;
import org.hawkular.inventory.rest.ResponseUtil;
import org.hawkular.inventory.rest.json.ApiError;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
/**
* @author Lukas Krejci
* @since 0.1.0
*/
@javax.ws.rs.Path("/deprecated")
@Produces(APPLICATION_JSON)
@Consumes(APPLICATION_JSON)
@Api(value = "/deprecated", description = "Resource Metrics CRUD", tags = {"Deprecated"})
public class RestResourcesMetrics extends RestResources {
@POST
@javax.ws.rs.Path("/{environmentId}/resources/{resourcePath:.+}/metrics")
@ApiOperation("Either creates a new metric owned by the resource or associates a pre-existing metric with the " +
"resource. This depends on what you pass as the the body of the request. A JSON array of strings is " +
"understood as a list of pre-existing metric paths that are associated with the resource. If the body is" +
" a JSON object or an array of JSON objects, the new metric or metrics are created \"underneath\" the " +
"resource.")
@ApiResponses({
@ApiResponse(code = 201, message = "New metric created under the resource"),
@ApiResponse(code = 204, message = "Existing metric successfully associated"),
@ApiResponse(code = 404, message = "Tenant, environment, resource doesn't exist. Also when an array of " +
"strings is supplied and one of the metrics in that array doesn't exist.",
response = ApiError.class),
@ApiResponse(code = 500, message = "Server error", response = ApiError.class)
})
public Response associateOrCreateMetrics(@PathParam("environmentId") String environmentId,
@Encoded @PathParam("resourcePath") String resourcePath,
@ApiParam("This is either a metric blueprint or a list of paths to " +
"metrics to be associated with the resource. They can either be " +
"canonical or relative to the resource.")
Object metricPathsOrBlueprint,
@Context UriInfo uriInfo) {
String tenantId = getTenantId();
CanonicalPath tenant = CanonicalPath.of().tenant(tenantId).get();
CanonicalPath resource = composeCanonicalPath(tenantId, environmentId, null, resourcePath);
return createOrAssociateMetric(tenant, resource, metricPathsOrBlueprint, uriInfo);
}
@POST
@javax.ws.rs.Path("/feeds/{feedId}/resources/{resourcePath:.+}/metrics")
@ApiOperation("Either creates a new metric owned by the resource or associates a pre-existing metric with the " +
"resource. This depends on what you pass as the the body of the request. A JSON array of strings is " +
"understood as a list of pre-existing metric paths that are associated with the resource. If the body is" +
" a JSON object or an array of JSON objects, the new metric or metrics are created \"underneath\" the " +
"resource.")
@ApiResponses({
@ApiResponse(code = 201, message = "New metric created under the resource"),
@ApiResponse(code = 204, message = "Existing metric successfully associated"),
@ApiResponse(code = 404, message = "Tenant, environment, resource doesn't exist. Also when an array of " +
"strings is supplied and one of the metrics in that array doesn't exist.",
response = ApiError.class),
@ApiResponse(code = 500, message = "Server error", response = ApiError.class)
})
public Response associateOrCreateMetricsUnderFeed(@PathParam("feedId") String feedId,
@Encoded @PathParam("resourcePath") String resourcePath,
@ApiParam("This is either a metric blueprint or a list of paths to metrics to " +
"be associated with the resource. They can either be canonical or " +
"relative to the resource.")
Object metricPathsOrBlueprint, @Context UriInfo uriInfo) {
String tenantId = getTenantId();
CanonicalPath tenant = CanonicalPath.of().tenant(tenantId).get();
CanonicalPath resource = composeCanonicalPath(tenantId, null, feedId, resourcePath);
return createOrAssociateMetric(tenant, resource, metricPathsOrBlueprint, uriInfo);
}
private Response createOrAssociateMetric(CanonicalPath tenant, CanonicalPath resource,
Object metricPathsOrBlueprint, UriInfo uriInfo) {
BiFunction<Map<?, ?>, Metrics.ReadWrite, Metric> createMetric = (map, access) -> {
Metric.Blueprint blueprint = getMapper().convertValue(map, Metric.Blueprint.class);
return access.create(blueprint).entity();
};
if (metricPathsOrBlueprint instanceof List) {
List<?> list = (List<?>) metricPathsOrBlueprint;
if (list.isEmpty()) {
return Response.noContent().build();
}
if (list.get(0) instanceof Map) {
if (!security.canCreate(Metric.class).under(resource)) {
return Response.status(FORBIDDEN).build();
}
Metrics.ReadWrite metricDao = inventory.inspect(resource, Resources.Single.class).metrics();
List<Metric> createdMetrics = list.stream().map(m -> createMetric.apply((Map<?, ?>) m, metricDao))
.collect(Collectors.toList());
if (createdMetrics.size() == 1) {
Metric.Blueprint blueprint = getMapper().convertValue(list.get(0), Metric.Blueprint.class);
return ResponseUtil.created(createdMetrics.get(0), uriInfo, blueprint.getId()).build();
} else {
return ResponseUtil.created(uriInfo, createdMetrics.stream().map(Metric::getId).spliterator())
.build();
}
} else {
if (!security.canAssociateFrom(resource)) {
return Response.status(FORBIDDEN).build();
}
Metrics.ReadAssociate metricDao = inventory.inspect(resource, Resources.Single.class).allMetrics();
list.stream()
.map((p) -> Path.fromPartiallyUntypedString((String) p, tenant, resource, Metric.SEGMENT_TYPE))
.forEach(metricDao::associate);
return Response.noContent().build();
}
} else if (metricPathsOrBlueprint instanceof Map) {
if (!security.canCreate(Metric.class).under(resource)) {
return Response.status(FORBIDDEN).build();
}
Metrics.ReadWrite metricDao = inventory.inspect(resource, Resources.Single.class).metrics();
Metric entity = createMetric.apply((Map<?, ?>) metricPathsOrBlueprint, metricDao);
Metric.Blueprint blueprint = getMapper().convertValue(metricPathsOrBlueprint, Metric.Blueprint.class);
return ResponseUtil.created(entity, uriInfo, blueprint.getId()).build();
} else {
throw new IllegalArgumentException("Unhandled type of input");
}
}
@GET
@javax.ws.rs.Path("/{environmentId}/resources/{resourcePath:.+}/metrics")
@ApiOperation("Retrieves all metrics associated with a resource. Accepts paging query parameters.")
@ApiResponses({
@ApiResponse(code = 200, message = "The list of metrics"),
@ApiResponse(code = 404, message = "Tenant, environment or resource doesn't exist",
response = ApiError.class),
@ApiResponse(code = 500, message = "Server error", response = ApiError.class)
})
public Response getAssociatedMetrics(@PathParam("environmentId") String environmentID,
@Encoded @PathParam("resourcePath") String resourcePath, @Context UriInfo uriInfo) {
CanonicalPath resource = composeCanonicalPath(getTenantId(), environmentID, null, resourcePath);
Page<Metric> ms = inventory.inspect(resource, Resources.Single.class).allMetrics().getAll().entities(
extractPaging(uriInfo));
return pagedResponse(Response.ok(), uriInfo, ms).build();
}
@GET
@javax.ws.rs.Path("/feeds/{feedId}/resources/{resourcePath:.+}/metrics")
@ApiOperation("Retrieves all metrics associated with a resource. Accepts paging query parameters.")
@ApiResponses({
@ApiResponse(code = 200, message = "The list of metrics"),
@ApiResponse(code = 404, message = "Tenant, environment, feed or resource doesn't exist",
response = ApiError.class),
@ApiResponse(code = 500, message = "Server error", response = ApiError.class)
})
public Response getAssociatedMetricsF(@PathParam("feedId") String feedId,
@Encoded @PathParam("resourcePath") String resourcePath,
@Context UriInfo uriInfo) {
CanonicalPath resource = composeCanonicalPath(getTenantId(), null, feedId, resourcePath);
Page<Metric> ms = inventory.inspect(resource, Resources.Single.class).allMetrics().getAll().entities(
extractPaging(uriInfo));
return pagedResponse(Response.ok(), uriInfo, ms).build();
}
@GET
@javax.ws.rs.Path("/{environmentId}/resources/{resourcePath:.+}/metrics/{metricPath:.+}")
@ApiOperation("Retrieves a single metric associated with a resource")
@ApiResponses({
@ApiResponse(code = 200, message = "The resource"),
@ApiResponse(code = 404,
message = "Tenant, environment, resource or metric does not exist or the metric is not " +
"associated with the resource", response = ApiError.class),
@ApiResponse(code = 500, message = "Server error", response = ApiError.class)
})
public Response getAssociatedMetric(@PathParam("environmentId") String environmentId,
@Encoded @PathParam("resourcePath") String resourcePath,
@Encoded @PathParam("metricPath") String metricPath, @QueryParam("canonical") @DefaultValue("false")
@ApiParam("True if metric path should be considered canonical, false by default.") boolean isCanonical) {
String tenantId = getTenantId();
CanonicalPath tenant = CanonicalPath.of().tenant(tenantId).get();
CanonicalPath rp = composeCanonicalPath(tenantId, environmentId, null, resourcePath);
if (isCanonical) {
metricPath = "/" + metricPath;
}
Path mp = Path.fromPartiallyUntypedString(metricPath, tenant, rp, Metric.SEGMENT_TYPE);
if (RequestUtil.isTenantEscapeAttempt(rp, mp)) {
Response.status(FORBIDDEN).build();
}
Metric m = inventory.inspect(rp, Resources.Single.class).allMetrics().get(mp).entity();
return Response.ok(m).build();
}
@GET
@javax.ws.rs.Path("/feeds/{feedId}/resources/{resourcePath:.+}/metrics/{metricPath:.+}")
@ApiOperation("Retrieves a single resource")
@ApiResponses({
@ApiResponse(code = 200, message = "The resource"),
@ApiResponse(code = 404,
message = "Tenant, environment, feed, resource or metric doesn't exist or if the metric is not " +
"associated with the resource", response = ApiError.class),
@ApiResponse(code = 500, message = "Server error", response = ApiError.class)
})
public Response getAssociatedMetricF(
@PathParam("feedId") String feedId, @Encoded @PathParam("resourcePath") String resourcePath,
@Encoded @PathParam("metricPath") String metricPath, @QueryParam("canonical") @DefaultValue("false")
@ApiParam("True if metric path should be considered canonical, false by default.") boolean isCanonical) {
String tenantId = getTenantId();
CanonicalPath tenant = CanonicalPath.of().tenant(tenantId).get();
CanonicalPath rp = composeCanonicalPath(tenantId, null, feedId, resourcePath);
if (isCanonical) {
metricPath = "/" + metricPath;
}
Path mp = Path.fromPartiallyUntypedString(metricPath, tenant, rp, Metric.SEGMENT_TYPE);
if (RequestUtil.isTenantEscapeAttempt(rp, mp)) {
Response.status(FORBIDDEN).build();
}
Metric m = inventory.inspect(rp, Resources.Single.class).allMetrics().get(mp).entity();
return Response.ok(m).build();
}
@DELETE
@javax.ws.rs.Path("/{environmentId}/resources/{resourcePath:.+}/metrics/{metricPath:.+}")
@ApiOperation("Disassociates the given resource from the given metric. If the metric is contained within the " +
"resource, it is also deleted.")
@ApiResponses({
@ApiResponse(code = 204, message = "OK"),
@ApiResponse(code = 404,
message = "Tenant, environment, resource or metric does not exist or the metric is not " +
"associated with the resource", response = ApiError.class),
@ApiResponse(code = 500, message = "Server error", response = ApiError.class)
})
public Response disassociateMetric(@PathParam("environmentId") String environmentId,
@Encoded @PathParam("resourcePath") String resourcePath,
@Encoded @PathParam("metricPath") String metricPath, @QueryParam("canonical") @DefaultValue("false")
@ApiParam("True if metric path should be considered canonical, false by default.") boolean isCanonical) {
String tenantId = getTenantId();
CanonicalPath tenant = CanonicalPath.of().tenant(tenantId).get();
CanonicalPath rp = composeCanonicalPath(tenantId, environmentId, null, resourcePath);
return disassociateOrDelete(tenant, rp, metricPath, isCanonical);
}
@DELETE
@javax.ws.rs.Path("/feeds/{feedId}/resources/{resourcePath:.+}/metrics/{metricPath:.+}")
@ApiOperation("Disassociates the given resource from the given metric. If the metric is contained within the " +
"resource, it is also deleted.")
@ApiResponses({
@ApiResponse(code = 204, message = "OK"),
@ApiResponse(code = 404,
message = "Tenant, environment, feed, resource or metric does not exist or the metric is not " +
"associated with the resource", response = ApiError.class),
@ApiResponse(code = 500, message = "Server error", response = ApiError.class)
})
public Response disassociateMetricF(
@PathParam("feedId") String feedId, @Encoded @PathParam("resourcePath") String resourcePath,
@Encoded @PathParam("metricPath") String metricPath, @QueryParam("canonical") @DefaultValue("false")
@ApiParam("True if metric path should be considered canonical, false by default.") boolean isCanonical) {
String tenantId = getTenantId();
CanonicalPath tenant = CanonicalPath.of().tenant(tenantId).get();
CanonicalPath rp = composeCanonicalPath(tenantId, null, feedId, resourcePath);
return disassociateOrDelete(tenant, rp, metricPath, isCanonical);
}
private Response disassociateOrDelete(CanonicalPath tenant, CanonicalPath resource, String metricPath, boolean
isCanonical) {
if (!security.canAssociateFrom(resource)) {
return Response.status(FORBIDDEN).build();
}
if (isCanonical) {
metricPath = "/" + metricPath;
}
Path mp = Path.fromPartiallyUntypedString(metricPath, tenant, resource, Metric.SEGMENT_TYPE);
if (RequestUtil.isTenantEscapeAttempt(resource, mp)) {
Response.status(FORBIDDEN).build();
}
if (mp.isRelative()) {
mp = mp.toRelativePath().applyTo(resource);
}
if (mp.toCanonicalPath().up().equals(resource)) {
inventory.inspect(resource, Resources.Single.class).metrics().delete(mp.getSegment().getElementId());
} else {
inventory.inspect(resource, Resources.Single.class).allMetrics().disassociate(mp);
}
return Response.noContent().build();
}
}