package org.ovirt.engine.ui.frontend.server.gwt.plugin; import java.util.Objects; import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.node.JsonNodeFactory; import org.codehaus.jackson.node.ObjectNode; /** * Immutable structure that contains UI plugin descriptor/configuration data and associated logic. */ public class PluginData implements Comparable<PluginData> { public interface ValidationCallback { void descriptorError(String message); void configurationError(String message); } private static final String ATT_NAME = "name"; //$NON-NLS-1$ private static final String ATT_URL = "url"; //$NON-NLS-1$ private static final String ATT_CONFIG = "config"; //$NON-NLS-1$ private static final String ATT_RESOURCEPATH = "resourcePath"; //$NON-NLS-1$ private static final String ATT_LAZYLOAD = "lazyLoad"; //$NON-NLS-1$ private static final String ATT_ENABLED = "enabled"; //$NON-NLS-1$ private static final String ATT_ORDER = "order"; //$NON-NLS-1$ private final JsonNode descriptorNode; private final long descriptorLastModified; private final JsonNode configurationNode; private final long configurationLastModified; private final JsonNodeFactory nodeFactory; public PluginData(JsonNode descriptorNode, long descriptorLastModified, JsonNode configurationNode, long configurationLastModified, JsonNodeFactory nodeFactory) { this.descriptorNode = descriptorNode; this.descriptorLastModified = descriptorLastModified; this.configurationNode = configurationNode; this.configurationLastModified = configurationLastModified; this.nodeFactory = nodeFactory; } public JsonNode getDescriptorNode() { // TODO should return a deep copy, see JACKSON-707 return descriptorNode; } public JsonNode getConfigurationNode() { // TODO should return a deep copy, see JACKSON-707 return configurationNode; } public long getDescriptorLastModified() { return descriptorLastModified; } public long getConfigurationLastModified() { return configurationLastModified; } public boolean validate(ValidationCallback callback) { boolean isValid = true; // Validate descriptor data if (!checkRequiredNonEmptyStringNode(descriptorNode, ATT_NAME)) { callback.descriptorError("Required non-empty string attribute: " + ATT_NAME); //$NON-NLS-1$ isValid = false; } if (!checkRequiredNonEmptyStringNode(descriptorNode, ATT_URL)) { callback.descriptorError("Required non-empty string attribute: " + ATT_URL); //$NON-NLS-1$ isValid = false; } if (!checkOptionalObjectNode(descriptorNode, ATT_CONFIG)) { callback.descriptorError("Optional attribute must be object: " + ATT_CONFIG); //$NON-NLS-1$ isValid = false; } if (!checkOptionalNonEmptyStringNode(descriptorNode, ATT_RESOURCEPATH)) { callback.descriptorError("Optional attribute must be non-empty string: " + ATT_RESOURCEPATH); //$NON-NLS-1$ isValid = false; } if (!checkOptionalBooleanNode(descriptorNode, ATT_LAZYLOAD)) { callback.descriptorError("Optional attribute must be boolean: " + ATT_LAZYLOAD); //$NON-NLS-1$ isValid = false; } // Validate configuration data if (!checkOptionalObjectNode(configurationNode, ATT_CONFIG)) { callback.configurationError("Optional attribute must be object: " + ATT_CONFIG); //$NON-NLS-1$ isValid = false; } if (!checkOptionalBooleanNode(configurationNode, ATT_ENABLED)) { callback.configurationError("Optional attribute must be boolean: " + ATT_ENABLED); //$NON-NLS-1$ isValid = false; } if (!checkOptionalIntegerNode(configurationNode, ATT_ORDER)) { callback.configurationError("Optional attribute must be integer: " + ATT_ORDER); //$NON-NLS-1$ isValid = false; } return isValid; } boolean checkRequiredNonEmptyStringNode(JsonNode root, String fieldName) { JsonNode target = root.path(fieldName); return !target.isMissingNode() && target.isTextual() && !target.getTextValue().trim().isEmpty(); } boolean checkOptionalNonEmptyStringNode(JsonNode root, String fieldName) { JsonNode target = root.path(fieldName); return !target.isMissingNode() ? target.isTextual() && !target.getTextValue().trim().isEmpty() : true; } boolean checkOptionalObjectNode(JsonNode root, String fieldName) { JsonNode target = root.path(fieldName); return !target.isMissingNode() ? target.isObject() : true; } boolean checkOptionalBooleanNode(JsonNode root, String fieldName) { JsonNode target = root.path(fieldName); return !target.isMissingNode() ? target.isBoolean() : true; } boolean checkOptionalIntegerNode(JsonNode root, String fieldName) { JsonNode target = root.path(fieldName); return !target.isMissingNode() ? target.isIntegralNumber() : true; } /** * Applies custom (user-defined) configuration on top of default (descriptor-defined) configuration, and returns the * resulting JSON object. */ public ObjectNode mergeConfiguration() { JsonNode descriptorConfigNode = descriptorNode.path(ATT_CONFIG); ObjectNode result = nodeFactory.objectNode(); // Apply default configuration, if any if (!descriptorConfigNode.isMissingNode() && descriptorConfigNode.isObject()) { result.putAll((ObjectNode) descriptorConfigNode); } // Apply custom configuration, if any JsonNode customConfigNode = configurationNode.path(ATT_CONFIG); if (customConfigNode != null && !customConfigNode.isMissingNode() && customConfigNode.isObject()) { result.putAll((ObjectNode) customConfigNode); } return result; } public String getName() { return descriptorNode.path(ATT_NAME).getTextValue(); } public String getUrl() { return descriptorNode.path(ATT_URL).getTextValue(); } public String getResourcePath() { JsonNode target = descriptorNode.path(ATT_RESOURCEPATH); return !target.isMissingNode() ? target.getTextValue() : null; } public boolean isLazyLoad() { JsonNode target = descriptorNode.path(ATT_LAZYLOAD); return !target.isMissingNode() ? target.getBooleanValue() : true; } public boolean isEnabled() { JsonNode target = configurationNode.path(ATT_ENABLED); return !target.isMissingNode() ? target.getBooleanValue() : true; } public int getOrder() { JsonNode target = configurationNode.path(ATT_ORDER); return !target.isMissingNode() ? target.getIntValue() : Integer.MAX_VALUE; } /** * Natural comparison method based on {@linkplain #getOrder plugin load order}. */ @Override public int compareTo(PluginData other) { if (this == other) { return 0; } int o1 = this.getOrder(); int o2 = other.getOrder(); if (o1 < o2) { return -1; } else if (o1 > o2) { return 1; } return 0; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof PluginData)) { return false; } PluginData other = (PluginData) obj; return Objects.equals(getOrder(), other.getOrder()); } @Override public int hashCode() { return Objects.hashCode(getOrder()); } @Override public String toString() { return "Plugin " + getName(); //$NON-NLS-1$ } }