/* * 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.api.model; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Set; import org.hawkular.inventory.paths.CanonicalPath; import org.hawkular.inventory.paths.RelativePath; import org.hawkular.inventory.paths.SegmentType; import io.swagger.annotations.ApiModel; /** * Base class for all Hawkular entities. * * @author Lukas Krejci * @since 0.0.1 */ @ApiModel(description = "Defines the basic properties of all entity types in inventory", subTypes = {Environment.class, SyncedEntity.class, MetadataPack.class, Tenant.class}) public abstract class Entity<B extends Blueprint, U extends Entity.Update> extends AbstractElement<B, U> { public static Class<?> typeFromSegmentType(SegmentType segmentType) { switch (segmentType) { case up: return RelativePath.Up.class; default: return entityTypeFromSegmentType(segmentType); } } public static Class<? extends Entity<? extends org.hawkular.inventory.api.model.Blueprint, ?>> entityTypeFromSegmentType(SegmentType segmentType) { switch (segmentType) { case t: return Tenant.class; case e: return Environment.class; case f: return Feed.class; case m: return Metric.class; case mt: return MetricType.class; case r: return Resource.class; case rt: return ResourceType.class; // case rl: // return Relationship.class; case d: return DataEntity.class; case ot: return OperationType.class; case mp: return MetadataPack.class; default: throw new IllegalStateException("There is no " + Entity.class.getName() + " type for " + segmentType.getClass().getName() + " '" + segmentType.name() + "'"); } } private final String name; Entity() { this.name = null; } Entity(CanonicalPath path) { this(path, null); } Entity(CanonicalPath path, Map<String, Object> properties) { this(null, path, properties); } public Entity(String name, CanonicalPath path) { this(name, path, null); } /** * Constructs a new entity * * @param name the human readable name of the entity, can be null * @param path the path of the entity, must not be null * @param properties the additional user-defined properties, can be null */ Entity(String name, CanonicalPath path, Map<String, Object> properties) { super(path, properties); this.name = name; if (!segmentTypeFromType(this.getClass()).equals(path.getSegment().getElementType())) { throw new IllegalArgumentException("Invalid path specified. Trying to create " + this.getClass().getSimpleName() + " but the path points to " + path.getSegment().getElementType().getSimpleName()); } } /** * @return The human readable name of the entity, can be null */ public String getName() { return name; } /** * Use this to append additional information to the string representation of this instance * returned from the (final) {@link #toString()}. * * <p>Generally, one should call the super method first and then only add additional information * to the builder. * * @param toStringBuilder the builder to append stuff to. */ protected void appendToString(StringBuilder toStringBuilder) { } @Override public final String toString() { StringBuilder bld = new StringBuilder(getClass().getSimpleName()); bld.append("[path='").append(getPath()).append('\''); appendToString(bld); bld.append(']'); return bld.toString(); } /** * Base class for the blueprint types of concrete subclasses. Note that while it will usually fit the purpose, * the subclasses are free to use a blueprint type not inheriting from this one. */ public abstract static class Blueprint extends AbstractElement.Blueprint { private final String id; private final String name; private final Map<String, Set<CanonicalPath>> outgoing; private final Map<String, Set<CanonicalPath>> incoming; /** * This no-arg constructor is provided for the needs of Jackson deserialization. The instance constructed using * it is semantically invalid and needs further processing (by reflection) to be correct. This is what Jackson * does but should not be relied upon in other circumstances. * <p> * Do <b>NOT</b> make this public in subclasses, rather make the no-arg constructor actually private in * final subclasses to stress this point even further. * <p> * Any constructor with arguments should <b>NOT</b> call this, because it will leave mandatory properties * uninitialized. Use one of the other provided constructors for initializing instances with arguments. */ protected Blueprint() { super(null); this.id = null; this.name = null; this.outgoing = null; this.incoming = null; } protected Blueprint(String id, Map<String, Object> properties) { this(id, properties, null, null); } protected Blueprint(String id, Map<String, Object> properties, Map<String, Set<CanonicalPath>> outgoing, Map<String, Set<CanonicalPath>> incoming) { this(id, null, properties, outgoing, incoming); } protected Blueprint(String id, String name, Map<String, Object> properties) { this(id, name, properties, null, null); } protected Blueprint(String id, String name, Map<String, Object> properties, Map<String, Set<CanonicalPath>> outgoing, Map<String, Set<CanonicalPath>> incoming) { super(properties); if (id == null) { throw new IllegalArgumentException("id == null"); } this.id = id; this.name = name; this.outgoing = outgoing == null ? Collections.emptyMap() : copyAsUnmodifiable(outgoing); this.incoming = incoming == null ? Collections.emptyMap() : copyAsUnmodifiable(incoming); } public String getId() { return id; } public String getName() { return name; } public Map<String, Set<CanonicalPath>> getOutgoingRelationships() { return outgoing == null ? Collections.emptyMap() : outgoing; } public Map<String, Set<CanonicalPath>> getIncomingRelationships() { return incoming == null ? Collections.emptyMap() : incoming; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Blueprint blueprint = (Blueprint) o; return id.equals(blueprint.id); } @Override public int hashCode() { return id.hashCode(); } public abstract static class Builder<Blueprint, This extends Builder<Blueprint, This>> extends AbstractElement.Blueprint.Builder<Blueprint, This> { protected String id; protected String name; protected Map<String, Set<CanonicalPath>> outgoing; protected Map<String, Set<CanonicalPath>> incoming; public This withId(String id) { this.id = id; return castThis(); } public This withName(String name) { this.name = name; return castThis(); } public This withOutgoingRelationships(Map<String, Set<CanonicalPath>> outgoing) { this.outgoing = outgoing; return castThis(); } public This withIncomingRelationships(Map<String, Set<CanonicalPath>> incoming) { this.incoming = incoming; return castThis(); } public This addOutgoingRelationship(String label, CanonicalPath target) { outgoing = addRelationship(outgoing, label, target); return castThis(); } public This addIncomingRelationship(String label, CanonicalPath source) { incoming = addRelationship(incoming, label, source); return castThis(); } private Map<String, Set<CanonicalPath>> addRelationship(Map<String, Set<CanonicalPath>> map, String label, CanonicalPath path) { if (map == null) { map = new HashMap<>(); } Set<CanonicalPath> paths = map.get(label); if (paths == null) { paths = new HashSet<>(); map.put(label, paths); } paths.add(path); return map; } } private static Map<String, Set<CanonicalPath>> copyAsUnmodifiable(Map<String, Set<CanonicalPath>> map) { Map<String, Set<CanonicalPath>> ret = new HashMap<>(map.size()); map.forEach((k, v) -> ret.put(k, Collections.unmodifiableSet(v))); return Collections.unmodifiableMap(ret); } } public abstract static class Update extends AbstractElement.Update { protected final String name; protected Update(String name, Map<String, Object> properties) { super(properties); this.name = name; } public String getName() { return name; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Update)) return false; if (!super.equals(o)) return false; Update update = (Update) o; return name != null ? name.equals(update.name) : update.name == null; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (name != null ? name.hashCode() : 0); return result; } public abstract static class Builder<E extends Entity<?, U>, U extends Update, This extends Builder<E, U, This>> extends AbstractElement.Update.Builder<E, U, This> { protected String name; public This withName(String name) { this.name = name; return castThis(); } @Override public This withDifference(E original, E updated) { if (!Objects.equals(original.getName(), updated.getName())) { withName(updated.getName()); } return super.withDifference(original, updated); } } } }