/* * 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.json.mixins.model; import java.io.IOException; import java.time.Instant; import org.hawkular.inventory.api.Action; import org.hawkular.inventory.api.Inventory; import org.hawkular.inventory.api.model.Blueprint; import org.hawkular.inventory.api.model.Change; import org.hawkular.inventory.api.model.Entity; import org.hawkular.inventory.paths.CanonicalPath; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonLocation; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.type.SimpleType; /** * @author Lukas Krejci * @since 0.20.0 */ @JsonSerialize(using = ChangeMixin.Serializer.class) @JsonDeserialize(using = ChangeMixin.Deserializer.class) public final class ChangeMixin { public static final class Serializer extends JsonSerializer<Change<?>> { @Override public void serialize(Change<?> value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeStartObject(); gen.writeNumberField("time", value.getTime().toEpochMilli()); gen.writeStringField("action", value.getAction().asEnum().name().toLowerCase()); gen.writeObjectField("context", value.getActionContext()); gen.writeEndObject(); } } public static final class Deserializer extends JsonDeserializer<Change<?>> { @Override public Change<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { Instant time = null; String action = null; JsonNode actionContext = null; String currentField = ""; readFields: while (p.nextToken() != null) { switch (p.getCurrentToken()) { case END_OBJECT: break readFields; case FIELD_NAME: currentField = p.getCurrentName(); break; default: switch (currentField) { case "time": long timestamp = p.readValueAs(Long.class); time = Instant.ofEpochMilli(timestamp); break; case "action": action = p.readValueAs(String.class); break; case "context": actionContext = p.readValueAsTree(); break; } } } if (time == null || action == null || actionContext == null) { throw new JsonParseException("Incomplete change object.", JsonLocation.NA); } CanonicalPath cp = findCp(action, actionContext); @SuppressWarnings("unchecked") Class<? extends Entity<?, ?>> et = (Class<? extends Entity<?, ?>>) Inventory.types().bySegment(cp.getSegment().getElementType()).getElementType(); return construct(ctxt, (Class) et, action, time, actionContext); } private CanonicalPath findCp(String action, JsonNode context) { String cpStr; if ("updated".equals(action)) { cpStr = context.get("originalEntity").get("path").asText(); } else { cpStr = context.get("path").asText(); } return CanonicalPath.fromString(cpStr); } private <E extends Entity<B, U>, U extends Entity.Update, B extends Blueprint> Change<E> construct(DeserializationContext ctx, Class<E> entityType, String action, Instant time, JsonNode contextNode) throws IOException { JsonParser p = ctx.getParser(); E entity; switch (action) { case "created": entity = p.getCodec().treeToValue(contextNode, entityType); return new Change<>(time, Action.created(), entity); case "updated": Class<U> updateType = Inventory.types().byElement(entityType).getUpdateType(); JavaType updateJavaType = ctx.getTypeFactory() .constructSimpleType(Action.Update.class, Action.Update.class, new JavaType[] {SimpleType.construct(entityType), SimpleType.construct(updateType)}); Action.Update<E, U> update = p.getCodec() .readValue(p.getCodec().treeAsTokens(contextNode), updateJavaType); return new Change<E>(time, Action.updated(), update); case "deleted": entity = p.getCodec().treeToValue(contextNode, entityType); return new Change<>(time, Action.deleted(), entity); } throw new JsonParseException("Unknown action in change descriptor: " + action, JsonLocation.NA); } } }