/* * 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; import static org.hawkular.inventory.api.filters.With.path; import static org.hawkular.inventory.rest.Utils.createUnder; import java.io.IOException; import java.io.Reader; import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import javax.inject.Inject; import javax.ws.rs.core.Context; import javax.ws.rs.core.PathSegment; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.hawkular.inventory.api.Configuration; import org.hawkular.inventory.api.Inventory; import org.hawkular.inventory.api.Query; import org.hawkular.inventory.api.ResolvableToSingleEntity; import org.hawkular.inventory.api.TransactionFrame; import org.hawkular.inventory.api.model.Change; import org.hawkular.inventory.api.model.Entity; import org.hawkular.inventory.api.paging.Page; import org.hawkular.inventory.json.DetypedPathDeserializer; import org.hawkular.inventory.paths.CanonicalPath; import org.hawkular.inventory.paths.SegmentType; import org.hawkular.inventory.rest.cdi.AutoTenant; import org.hawkular.inventory.rest.cdi.Our; import org.hawkular.inventory.rest.cdi.TenantAware; import org.hawkular.inventory.rest.security.Security; import org.hawkular.inventory.rest.security.TenantId; import org.jboss.resteasy.annotations.GZIP; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; /** * @author Lukas Krejci * @since 0.0.1 */ @GZIP public class RestBase { /** * @deprecated this will be made private. Use {@link #inventory(UriInfo)} instead. */ @Inject @AutoTenant @Deprecated protected Inventory inventory; @Inject protected Security security; @Inject @TenantId private String tenantId; @Inject Configuration config; @Inject @Our private ObjectMapper deprecatedMapper; @Inject @TenantAware private ObjectMapper defaultMapper; private final int pathLength; /** * @deprecated used by the deprecated REST API */ @Deprecated protected RestBase() { this(0); } protected RestBase(int pathLength) { this.pathLength = pathLength; } protected <T> Response.ResponseBuilder pagedResponse(Response.ResponseBuilder response, UriInfo uriInfo, Page<T> page) { return pagedResponse(response, uriInfo, getMapper(), page); } protected <T> Response.ResponseBuilder pagedResponse(Response.ResponseBuilder response, UriInfo uriInfo, ObjectMapper mapper, Page<T> page) { boolean streaming = config.getFlag(RestConfiguration.Keys.STREAMING_SERIALIZATION, RestConfiguration.Keys .STREAMING_SERIALIZATION.getDefaultValue()); if (streaming) { return ResponseUtil.pagedResponse(response, uriInfo, mapper, page); } else { try { RestApiLogger.LOGGER.debug("Fetching data from backend"); List<?> data = page.toList(); RestApiLogger.LOGGER.debug("Finished fetching data from backend"); return ResponseUtil.pagedResponse(response, uriInfo, page, mapper.writeValueAsString(data)); } catch (JsonProcessingException e) { RestApiLogger.LOGGER.warn(e); // fallback to the default object mapper return ResponseUtil.pagedResponse(response, uriInfo, page, page.toList()); } finally { RestApiLogger.LOGGER.debug("Finished building paged response (no data sent to client yet)"); } } } protected String getTenantId() { return tenantId; } protected CanonicalPath getTenantPath() { return CanonicalPath.of().tenant(getTenantId()).get(); } protected CanonicalPath parsePath(List<PathSegment> uriPath) { StringBuilder bld = new StringBuilder("/"); for (PathSegment seg : uriPath) { if (seg.getPath() != null) { bld.append(seg.getPath()); } if (seg.getMatrixParameters() != null) { for (Map.Entry<String, List<String>> e : seg.getMatrixParameters().entrySet()) { String param = e.getKey(); List<String> values = e.getValue(); if (values != null && !values.isEmpty()) { for (String val : values) { bld.append(";").append(param); if (val != null) { bld.append("=").append(val); } } } else { bld.append(";").append(param); } } } bld.append("/"); } bld.replace(bld.length() - 1, bld.length(), ""); return CanonicalPath.fromPartiallyUntypedString(bld.toString(), CanonicalPath.of().tenant(getTenantId()).get (), Entity.class); } protected Traverser getTraverser(UriInfo ctx) { Query.Builder queryPrefix = Query.builder().path().with( path(CanonicalPath.of().tenant(getTenantId()).get())); return new Traverser(ctx.getBaseUri().getPath().length() + pathLength, queryPrefix, str -> CanonicalPath.fromPartiallyUntypedString(str, getTenantPath(), (SegmentType) null)); } protected String getPath(UriInfo uriInfo) { return getPath(uriInfo, 0); } protected String getPath(UriInfo uriInfo, int excludeFromEnd) { String chopped = uriInfo.getPath(false).substring(pathLength); if (excludeFromEnd > 0) { chopped = chopped.substring(0, chopped.length() - excludeFromEnd); } return chopped; } protected Object create(CanonicalPath parentPath, SegmentType elementType, UriInfo uriInfo, Reader input) throws IOException { Class<?> blueprintType = Inventory.types().bySegment(elementType).getBlueprintType(); JsonNode data = getMapper().readTree(input); setupMapper(parentPath); if (data.isArray()) { TransactionFrame frame = inventory(uriInfo).newTransactionFrame(); Inventory inv = frame.boundInventory(); try { List<Object> result = new ArrayList<>(data.size()); for (JsonNode datum : data) { Object blueprint = getMapper().reader().forType(blueprintType).readValue(datum); Object entity = createUnder(inv, parentPath, elementType, blueprint); result.add(entity); } frame.commit(); return result; } catch (Throwable t) { frame.rollback(); throw t; } } else { Object blueprint = getMapper().reader().forType(blueprintType).readValue(data); return createUnder(inventory(uriInfo), parentPath, elementType, blueprint); } } protected ObjectMapper getMapper() { //hackity hack - we detect whether we need an object mapper for the deprecated or new API by checking the //advertised path prefix length - the deprecated API sets this to 0 (through the use of the default ctor of //this class), while the classes in the new API declare their context prefix length correctly. if (pathLength == 0) { return deprecatedMapper; } else { return defaultMapper; } } protected void setupMapper(CanonicalPath relativePathOrigin) { DetypedPathDeserializer.setCurrentCanonicalOrigin(getTenantPath()); DetypedPathDeserializer.setCurrentRelativePathOrigin(relativePathOrigin); DetypedPathDeserializer.setCurrentEntityType(null); } protected Inventory inventory(UriInfo uriInfo) { String atStr = uriInfo.getQueryParameters().getFirst("at"); Instant time = parseTime(atStr).orElse(null); return time == null ? inventory : inventory.at(time); } protected Optional<Instant> parseTime(String timestampOrDateTime) { if (timestampOrDateTime == null) { return Optional.empty(); } try { long timestamp = Long.parseLong(timestampOrDateTime); return Optional.of(Instant.ofEpochMilli(timestamp)); } catch (NumberFormatException ignored) { return Optional.of(Instant.parse(timestampOrDateTime)); } } protected List<Change<?>> getHistory(@Context UriInfo uriInfo, CanonicalPath path) { String fromStr = uriInfo.getQueryParameters().getFirst("from"); String toStr = uriInfo.getQueryParameters().getFirst("to"); String atStr = uriInfo.getQueryParameters().getFirst("at"); Instant from = parseTime(fromStr).orElse(Instant.ofEpochMilli(0)); Instant to = parseTime(toStr).orElseGet(() -> parseTime(atStr).orElse(Instant.ofEpochMilli(Long.MAX_VALUE))); return inventory(uriInfo).inspect(path, ResolvableToSingleEntity.class).history(from, to); } }