/* * The MIT License * * Copyright 2011 Sony Ericsson Mobile Communications. All rights reserved. * Copyright 2012 Sony Mobile Communications AB. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data; import com.sonyericsson.hudson.plugins.metadata.model.JsonUtils; import com.sonyericsson.hudson.plugins.metadata.model.MetadataContainer; import com.sonyericsson.hudson.plugins.metadata.model.MetadataNodeProperty; import com.sonyericsson.hudson.plugins.metadata.model.MetadataParent; import com.sonyericsson.hudson.plugins.metadata.model.values.AbstractMetadataValue; import com.sonyericsson.hudson.plugins.metadata.model.values.MetadataValue; import com.sonyericsson.hudson.plugins.metadata.model.values.TreeNodeMetadataValue; import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Constants; import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Messages; import com.sonyericsson.jenkins.plugins.externalresource.dispatcher.PluginImpl; import com.thoughtworks.xstream.annotations.XStreamAlias; import hudson.EnvVars; import hudson.Extension; import hudson.ExtensionList; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.security.ACL; import hudson.security.Permission; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.bind.JavaScriptMethod; import java.io.IOException; import java.util.LinkedList; import java.util.List; import static com.sonyericsson.hudson.plugins.metadata.Constants.REQUEST_ATTR_METADATA_CONTAINER; import static com.sonyericsson.hudson.plugins.metadata.model.JsonUtils.CHILDREN; import static com.sonyericsson.hudson.plugins.metadata.model.JsonUtils.DESCRIPTION; import static com.sonyericsson.hudson.plugins.metadata.model.JsonUtils.EXPOSED; import static com.sonyericsson.hudson.plugins.metadata.model.JsonUtils.GENERATED; import static com.sonyericsson.hudson.plugins.metadata.model.JsonUtils.NAME; import static com.sonyericsson.hudson.plugins.metadata.model.JsonUtils.checkRequiredJsonAttribute; import static com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Constants.JSON_ATTR_ENABLED; import static com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Constants.JSON_ATTR_ID; import static com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Constants.JSON_ATTR_LOCKED; import static com.sonyericsson.jenkins.plugins.externalresource.dispatcher.Constants.JSON_ATTR_RESERVED; /** * Metadata type representing an external resource attached to a Node. * * @author Robert Sandell <robert.sandell@sonyericsson.com> */ @XStreamAlias(Constants.SERIALIZATION_ALIAS_EXTERNAL_RESOURCE) public class ExternalResource extends TreeNodeMetadataValue { /** * IllegalStateException message from some methods if they are called before a monitor has been set. */ private static final String NO_RESOURCE_MONITOR_EXCEPTION_MSG = "No resource monitor is currently active, this operation is not permitted."; private String id; private transient StashInfo reserved; private transient StashInfo locked; /** * For access control purposes enabled can internally have 3 values; not set, true or false. * All logic related to enabled should handle "not set" as enabled. * @see #isEnabled() */ private Boolean enabled; /** * Standard DataBound Constructor. * * @param name the name to identify it amongst its siblings. * @param description description * @param id The unique ID of the resource * @param enabled if the resource is enabled or not. * @param children associated metadata. * @see TreeNodeMetadataValue#TreeNodeMetadataValue(String, String, java.util.List) */ @DataBoundConstructor public ExternalResource(String name, String description, String id, Boolean enabled, List<MetadataValue> children) { super(name, description, children); this.id = id; this.enabled = enabled; } /** * Standard Constructor. * * @param name the name to identify it amongst its siblings. * @param description description * @param id The unique ID of the resource * @param children associated metadata. * @see TreeNodeMetadataValue#TreeNodeMetadataValue(String, String, java.util.List) */ public ExternalResource(String name, String description, String id, List<MetadataValue> children) { super(name, description, children); this.id = id; } /** * Standard Constructor. * * @param name the name to identify it amongst its siblings. * @param description description * @param id The unique ID of the resource * @see TreeNodeMetadataValue#TreeNodeMetadataValue(String, String) */ public ExternalResource(String name, String id, String description) { super(name, description); this.id = id; } /** * Standard Constructor. * * @param name the name to identify it amongst its siblings. * @param id The unique ID of the resource * @param children associated metadata. * @see TreeNodeMetadataValue#TreeNodeMetadataValue(String, List) */ public ExternalResource(String name, String id, List<MetadataValue> children) { super(name, children); this.id = id; } /** * Standard Constructor. * * @param name the name to identify it amongst its siblings. * @param id The unique ID of the resource * @see TreeNodeMetadataValue#TreeNodeMetadataValue(String) */ public ExternalResource(String name, String id) { super(name); this.id = id; } /** * The unique ID of the resource. * * @return the id. */ public String getId() { return id; } @Override public void setName(String name) { super.setName(name); } /** * Information about the reservation status if the resource. Null indicating not reserved. * * @return the reservation status. */ public StashInfo getReserved() { return reserved; } /** * Information about the reservation status if the resource. Null indicating not reserved. * * @param reserved the reservation status. */ public void setReserved(StashInfo reserved) { this.reserved = reserved; } /** * Information about the lock status if the resource. Null indicating not locked. * * @return the lock status. */ public StashInfo getLocked() { return locked; } /** * Information about the lock status if the resource. Null indicating not locked. * * @param locked the lock status. */ public void setLocked(StashInfo locked) { this.locked = locked; if(locked != null){ setReserved(null); } } /** * If this resource is enabled (true) or disabled (false) on the node. A disabled resource won't be selected for * reservation by the {@link com.sonyericsson.jenkins.plugins.externalresource.dispatcher.utils.AvailabilityFilter}. * * @return enabled or not. */ public boolean isEnabled() { if (enabled == null) { return true; } else { return enabled; } } /** * If this resource is enabled (true) or disabled (false) on the node. A disabled resource won't be selected for * reservation by the {@link com.sonyericsson.jenkins.plugins.externalresource.dispatcher.utils.AvailabilityFilter}. * The idea is that instead of removing a resource when it is temporarily removed and potentially loosing all * configuration, a monitoring service could just disable it instead. Intended for internal calls and serialization. * For external "user calls" see {@link #doEnable(boolean)}. * * @param enabled enabled or not. */ public void setEnabled(boolean enabled) { this.enabled = enabled; } /** * Enables this resource and save the Node's config. The method first checks if the user has the required * permissions. Called from javascript and CLI. * * @param enable true to enable, false to disable. * @throws IOException if so during save. * @see PluginImpl#ENABLE_DISABLE_EXTERNAL_RESOURCE * @see com.sonyericsson.hudson.plugins.metadata.model.MetadataContainer#save() * @see #setEnabled(boolean) */ @JavaScriptMethod public synchronized void doEnable(boolean enable) throws IOException { getACL().checkPermission(PluginImpl.ENABLE_DISABLE_EXTERNAL_RESOURCE); setEnabled(enable); getContainer().save(); } /** * Locks a resource. * * @param info the StashInfo containing the lock information. * @throws IOException if the container cannot be saved. */ public synchronized void doLock(StashInfo info) throws IOException { if (!(PluginImpl.getInstance().getManager().isExternalLockingOk())) { throw new IllegalStateException(NO_RESOURCE_MONITOR_EXCEPTION_MSG); } getACL().checkPermission(PluginImpl.LOCK_RELEASE_EXTERNAL_RESOURCE); setLocked(info); setReserved(null); getContainer().save(); } /** * Reserves a resource. * * @param info the StashInfo containing the reservation information. * @throws IOException if the container cannot be saved. */ public synchronized void doReserve(StashInfo info) throws IOException { if (!(PluginImpl.getInstance().getManager().isExternalLockingOk())) { throw new IllegalStateException(NO_RESOURCE_MONITOR_EXCEPTION_MSG); } getACL().checkPermission(PluginImpl.LOCK_RELEASE_EXTERNAL_RESOURCE); setReserved(info); setLocked(null); getContainer().save(); } /** * Releases a resource from its reservations and locks. * * @throws IOException if the container cannot be saved. */ public synchronized void doRelease() throws IOException { if (!(PluginImpl.getInstance().getManager().isExternalLockingOk())) { throw new IllegalStateException(NO_RESOURCE_MONITOR_EXCEPTION_MSG); } getACL().checkPermission(PluginImpl.LOCK_RELEASE_EXTERNAL_RESOURCE); setLocked(null); setReserved(null); getContainer().save(); } /** * Make this resource reservation expired and save the Node's config. * * @throws IOException if so during save. * @see com.sonyericsson.hudson.plugins.metadata.model.MetadataContainer#save() * @see #setReserved(com.sonyericsson.jenkins.plugins.externalresource.dispatcher.data.StashInfo) */ public synchronized void doExpireReservation() throws IOException { setReserved(null); getContainer().save(); } /** * If this resource is available or not. I.e. it has neither a {@link #getReserved()} nor a {@link #getLocked()} * set. Not counting if {@link #isEnabled()} is true or not. * * @return true if the resource is available to take. */ public boolean isAvailable() { return getReserved() == null && getLocked() == null; } /** * Gives the container's ACL. * * @return the ACL of the container. * * @see #getContainer() * @see com.sonyericsson.hudson.plugins.metadata.model.MetadataContainer#getACL() */ public synchronized ACL getACL() { return getContainer().getACL(); } /** * Control method to see if the current user has the {@link PluginImpl#ENABLE_DISABLE_EXTERNAL_RESOURCE} permission * or not. Convenience method for easy invocation from Jelly. * * @return true if the current user has the required permission. * * @see #getACL() * @see ACL#hasPermission(hudson.security.Permission) */ public boolean hasEnableDisablePermission() { return getACL().hasPermission(PluginImpl.ENABLE_DISABLE_EXTERNAL_RESOURCE); } /** * Control method to see if the resource can be enabled/disabled or not. * Checks {@link #hasEnableDisablePermission()} and that the container is a {@link MetadataNodeProperty}. * Otherwise the Enable/Disable button should not be shown. * * @return true if so. */ @SuppressWarnings("unused") public boolean canEnableDisable() { if (hasEnableDisablePermission()) { MetadataContainer<MetadataValue> container = getContainer(); return container != null && container instanceof MetadataNodeProperty; } return false; } /** * Searches up the parent hierarchy for the container. * * @return the container for this resource or null if something is wrong. */ private synchronized MetadataContainer<MetadataValue> getContainer() { return getContainer(getParent()); } /** * Searches up the parent hierarchy for the container, starting with the provided parent. * * @param parent the parent to check/recursively search. * @return the container or null. */ private synchronized MetadataContainer<MetadataValue> getContainer(MetadataParent<MetadataValue> parent) { if (parent instanceof MetadataContainer) { return (MetadataContainer<MetadataValue>)parent; } else if (parent == null) { return null; } else { return getContainer(((MetadataValue)parent).getParent()); } } @Override public Descriptor<AbstractMetadataValue> getDescriptor() { return Hudson.getInstance().getDescriptorByType(ExternalResourceDescriptor.class); } @Override public void addEnvironmentVariables(EnvVars variables, boolean exposeAll) { super.addEnvironmentVariables(variables, exposeAll); if (isExposedToEnvironment() || exposeAll) { variables.put(getEnvironmentName() + com.sonyericsson.hudson.plugins.metadata.Constants.ENVIRONMENT_SEPARATOR + "ID", getId()); } } @Override public boolean requiresReplacement() { return true; } @Override public void replacementOf(MetadataValue old) { super.replacementOf(old); if (old instanceof ExternalResource) { ExternalResource other = (ExternalResource)old; if (reserved == null) { reserved = other.reserved; } if (locked == null) { locked = other.locked; } if (enabled == null) { enabled = other.enabled; } if (id == null) { id = other.id; } } } @Override public ExternalResource clone() throws CloneNotSupportedException { ExternalResource other = (ExternalResource)super.clone(); if (reserved != null) { other.reserved = reserved.clone(); } if (locked != null) { other.locked = this.locked.clone(); } return other; } @Override public JSONObject toJson() { JSONObject json = super.toJson(); json.put(JSON_ATTR_ID, id); json.put(JSON_ATTR_ENABLED, isEnabled()); if (reserved != null) { json.put(JSON_ATTR_RESERVED, reserved.toJson()); } else { json.put(JSON_ATTR_RESERVED, new JSONObject(true)); } if (locked != null) { json.put(JSON_ATTR_LOCKED, locked.toJson()); } else { json.put(JSON_ATTR_LOCKED, new JSONObject(true)); } return json; } /** * Descriptor for {@link ExternalResource} metadata type. */ @Extension public static final class ExternalResourceDescriptor extends TreeNodeMetaDataValueDescriptor { @Override public String getJsonType() { return Constants.SERIALIZATION_ALIAS_EXTERNAL_RESOURCE; } /** * Convenience method for easier reach via Jelly. * * @return the Disable/Enable permission. */ @SuppressWarnings("unused") public Permission getDisablePermission() { return PluginImpl.ENABLE_DISABLE_EXTERNAL_RESOURCE; } @Override public MetadataValue fromJson(JSONObject json, MetadataContainer<MetadataValue> container) throws JsonUtils.ParseException { checkRequiredJsonAttribute(json, JSON_ATTR_ID); checkRequiredJsonAttribute(json, NAME); List<MetadataValue> children = new LinkedList<MetadataValue>(); if (json.has(CHILDREN)) { JSONArray array = json.getJSONArray(CHILDREN); for (int i = 0; i < array.size(); i++) { JSONObject obj = array.getJSONObject(i); children.add(JsonUtils.toValue(obj, container)); } } ExternalResource value = new ExternalResource( json.getString(NAME), json.optString(DESCRIPTION), json.getString(JSON_ATTR_ID), children); if (json.has(JSON_ATTR_ENABLED)) { container.getACL().checkPermission(PluginImpl.ENABLE_DISABLE_EXTERNAL_RESOURCE); value.setEnabled(json.getBoolean(JSON_ATTR_ENABLED)); } if (json.has(EXPOSED)) { value.setExposeToEnvironment(json.getBoolean(EXPOSED)); } if (json.has(GENERATED)) { value.setGenerated(json.getBoolean(GENERATED)); } else { //TODO Decide if this is really what should be done. value.setGenerated(true); } return value; } @Override public boolean appliesTo(Descriptor containerDescriptor) { return containerDescriptor instanceof ExternalResourceTreeNode.ExternalResourceTreeNodeDescriptor; } @Override public List<AbstractMetaDataValueDescriptor> getValueDescriptors(StaplerRequest request) { Object containerObj = request.getAttribute(REQUEST_ATTR_METADATA_CONTAINER); request.setAttribute(REQUEST_ATTR_METADATA_CONTAINER, this); Descriptor container = null; if ((containerObj != null) && containerObj instanceof Descriptor) { container = (Descriptor)containerObj; } List<AbstractMetaDataValueDescriptor> list = new LinkedList<AbstractMetaDataValueDescriptor>(); ExtensionList<AbstractMetaDataValueDescriptor> extensionList = Hudson.getInstance().getExtensionList(AbstractMetaDataValueDescriptor.class); for (AbstractMetaDataValueDescriptor d : extensionList) { if (!(d instanceof ExternalResourceDescriptor) && d.appliesTo(container) && d.appliesTo(this)) { list.add(d); } } return list; } @Override public String getDisplayName() { return Messages.ExternalResource_DisplayName(); } } }