/** * Copyright 2007-2008 University Of Southern California * * <p>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 * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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 edu.isi.pegasus.planner.classes; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import edu.isi.pegasus.planner.common.PegasusJsonDeserializer; import edu.isi.pegasus.planner.common.PegasusJsonSerializer; import edu.isi.pegasus.planner.dax.Invoke; import edu.isi.pegasus.planner.dax.Invoke.WHEN; import java.io.IOException; import java.util.Collection; import java.util.EnumMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * A container class that stores all the notifications that need to be done indexed by the various * conditions. * * @author Karan Vahi * @version $Revision$ */ @JsonDeserialize(using = Notifications.JsonDeserializer.class) @JsonSerialize(using = Notifications.JsonSerializer.class) public class Notifications extends Data { /** * An enum map that associates the various notification events with the list of actions that * need to be taken. */ private EnumMap<Invoke.WHEN, List<Invoke>> mInvokeMap; /** The default constructor. */ public Notifications() { reset(); } /** Resets the internal invoke map. */ public void reset() { mInvokeMap = new EnumMap<Invoke.WHEN, List<Invoke>>(Invoke.WHEN.class); Invoke.WHEN[] values = Invoke.WHEN.values(); for (int i = 0; i < values.length; i++) { mInvokeMap.put(values[i], new LinkedList()); } } /** * Adds a Invoke object correpsonding to a notification. * * @param notification the notification object */ public void add(Invoke notification) { if (notification == null) { return; // do nothing } // retrieve the appropriate namespace and then add List<Invoke> l = (List) mInvokeMap.get(Invoke.WHEN.valueOf(notification.getWhen())); l.add(notification); } /** * Adds all the notifications passed to the underlying container. * * @param notifications the notification object */ public void addAll(Notifications notifications) { if (notifications == null) { return; // do nothing } for (Invoke.WHEN when : Invoke.WHEN.values()) { this.addAll(when, notifications.getNotifications(when)); } } /** * Returns a collection of all the notifications that need to be done for a particular condition * * @param when the condition * @return */ public Collection<Invoke> getNotifications(String when) { return this.getNotifications(Invoke.WHEN.valueOf(when)); } /** * Returns a collection of all the notifications that need to be done for a particular condition * * @param when the condition * @return */ public Collection<Invoke> getNotifications(Invoke.WHEN when) { return this.mInvokeMap.get(when); } /** * Returns a boolean indicating whether the notifications object is empty or not. * * @return true if empty else false */ public boolean isEmpty() { Invoke.WHEN[] values = Invoke.WHEN.values(); for (int i = 0; i < values.length; i++) { if (!mInvokeMap.get(values[i]).isEmpty()) return false; } return true; } /** * Returns the clone of the object. * * @return the clone */ public Object clone() { Notifications inv; try { inv = (Notifications) super.clone(); } catch (CloneNotSupportedException e) { // somewhere in the hierarch chain clone is not implemented throw new RuntimeException( "Clone not implemented in the base class of " + this.getClass().getName(), e); } // traverse through all the enum keys for (Invoke.WHEN when : Invoke.WHEN.values()) { Collection<Invoke> c = this.getNotifications(when); inv.addAll(when, c); } return inv; } /** * Returns a String description of the object * * @return */ public String toString() { StringBuffer sb = new StringBuffer(); for (Invoke.WHEN when : Invoke.WHEN.values()) { Collection<Invoke> c = this.getNotifications(when); for (Invoke invoke : c) { sb.append(invoke.toString()); } } return sb.toString(); } /** * Convenience method at add all the notifications corresponding to a particular event * * @param when when does the event happen * @param notifications the list of notificiations */ private void addAll(WHEN when, Collection<Invoke> invokes) { Collection<Invoke> c = this.mInvokeMap.get(when); c.addAll(invokes); } /** * Returns whether a particular notification exists or not * * @param invoke * @return */ boolean contains(Invoke invoke) { Collection<Invoke> c = this.getNotifications(invoke.getWhen()); return c == null ? false : c.contains(invoke); } /** * Custom deserializer for YAML representation of Notifications/hooks * * @author Karan Vahi */ static class JsonDeserializer extends PegasusJsonDeserializer<Notifications> { public JsonDeserializer() {} /** * Deserializes a Transformation YAML description of the type * * <pre> * shell: * - _on: start * cmd: /bin/date * - _on: end * cmd: /bin/echo "Finished" * </pre> * * @param parser * @param dc * @return * @throws IOException * @throws JsonProcessingException */ @Override public Notifications deserialize(JsonParser parser, DeserializationContext dc) throws IOException, JsonProcessingException { ObjectCodec oc = parser.getCodec(); JsonNode node = oc.readTree(parser); Notifications notifications = new Notifications(); for (Iterator<Map.Entry<String, JsonNode>> it = node.fields(); it.hasNext(); ) { Map.Entry<String, JsonNode> e = it.next(); String key = e.getKey(); WorkflowKeywords reservedKey = WorkflowKeywords.getReservedKey(key); if (reservedKey == null) { this.complainForIllegalKey(WorkflowKeywords.HOOKS.getReservedName(), key, node); } notifications.addAll(this.createNotifications(key, node.get(key))); } return notifications; } /** * Parses an array of notifications of same type * * <pre> * - _on: start * cmd: /bin/date * - _on: end * cmd: /bin/echo "Finished" * </pre> * * @param type * @param node * @return */ protected Notifications createNotifications(String type, JsonNode node) { Notifications notifications = new Notifications(); if (type.equals("shell")) { if (node.isArray()) { for (JsonNode hook : node) { notifications.add( new Invoke( Invoke.WHEN.valueOf(hook.get("_on").asText()), hook.get("cmd").asText())); } } else { throw new RuntimeException("Expected an array of shell hooks " + node); } } else { throw new RuntimeException( "Unsupported notifications of type " + type + " - " + node); } return notifications; } @Override public RuntimeException getException(String message) { return new RuntimeException(message); } } /** * Custom serializer for YAML representation of Notifications * * @author Karan Vahi */ static class JsonSerializer extends PegasusJsonSerializer<Notifications> { public JsonSerializer() {} /** * Serializes contents into YAML representation Sample serialization * * <pre> * shell: * - _on: start * cmd: /bin/date * - _on: end * cmd: /bin/echo "Finished" * </pre> * * @param notifications * @param gen * @param sp * @throws IOException */ public void serialize(Notifications notifications, JsonGenerator gen, SerializerProvider sp) throws IOException { if (notifications == null) { return; } gen.writeStartObject(); // we only have one site associated // only type of hooks supported so far gen.writeFieldName("shell"); gen.writeStartArray(); gen.writeStartObject(); // traverse through all the enum keys for (Invoke.WHEN when : Invoke.WHEN.values()) { Collection<Invoke> c = notifications.getNotifications(when); for (Invoke iv : c) { writeStringField(gen, "_on", iv.getWhen()); writeStringField(gen, "cmd", iv.getWhat()); } } gen.writeEndObject(); gen.writeEndArray(); gen.writeEndObject(); } } }