Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.oodt.cas.pge.metadata; //JDK imports import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; //Apache imports import org.apache.commons.lang.Validate; //OODT imports import org.apache.oodt.cas.metadata.Metadata; //Google imports import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; /** * A wrapper class to act as a facade interface to all the different * {@link Metadata} sources given to a PGE. * * NOTE: 2 ways to update DYNAMIC metadata: 1) Create a key link to a DYNAMIC metadata * key, then change the value of the key link or 2) add metadata then mark the * key as dynamic and commit it. * * @author bfoster (Brian Foster) * @author mattmann (Chris Mattmann) */ public class PgeMetadata { public enum Type { STATIC, DYNAMIC, LOCAL } public static final List<Type> DEFAULT_COMBINE_ORDER = Lists.newArrayList(Type.LOCAL, Type.DYNAMIC, Type.STATIC); public static final List<Type> DEFAULT_QUERY_ORDER = Lists.newArrayList(Type.STATIC, Type.DYNAMIC, Type.LOCAL); private final Metadata staticMetadata; private final Metadata dynamicMetadata; private final Metadata localMetadata; private final Map<String, String> keyLinkMap; private final Set<String> markedAsDynamicMetKeys; public PgeMetadata() { keyLinkMap = Maps.newHashMap(); markedAsDynamicMetKeys = Sets.newHashSet(); staticMetadata = new Metadata(); dynamicMetadata = new Metadata(); localMetadata = new Metadata(); } public PgeMetadata(PgeMetadata pgeMetadata) { this(); Validate.notNull(pgeMetadata, "pgeMetadata cannot be null"); replaceMetadata(pgeMetadata); } public PgeMetadata(Metadata staticMetadata, Metadata dynamicMetadata) { this(); Validate.notNull(staticMetadata, "staticMetadata cannot be null"); Validate.notNull(dynamicMetadata, "dynamicMetadata cannot be null"); this.staticMetadata.replaceMetadata(staticMetadata); this.dynamicMetadata.replaceMetadata(dynamicMetadata); } /** * Replaces or creates this {@link PgeMetadata}'s metadata with given * {@link PgeMetadata}'s metadata. Also adds in the list of given * {@link PgeMetadata}'s LOCAL metadata marked for promotion DYNAMIC * metadata and list of key links. * * @param pgeMetadata * A {@link PgeMetadata} whose metadata and key links will be added * to this {@link PgeMetadata}'s metadata and key links. */ public void replaceMetadata(PgeMetadata pgeMetadata) { Validate.notNull(pgeMetadata, "pgeMetadata cannot be null"); staticMetadata.replaceMetadata(pgeMetadata.staticMetadata); dynamicMetadata.replaceMetadata(pgeMetadata.dynamicMetadata); localMetadata.replaceMetadata(pgeMetadata.localMetadata); keyLinkMap.putAll(pgeMetadata.keyLinkMap); markedAsDynamicMetKeys.addAll(pgeMetadata.markedAsDynamicMetKeys); } /** * Replaces or creates this {@link PgeMetadata}'s metadata with given * {@link PgeMetadata}'s metadata. The provided "group" will be used to * namespace the given {@link PgeMetadata}'s LOCAL metadata when add to this * {@link PgeMetadata}'s LOCAL metadata. It will also namespace given * {@link PgeMetadata}'s key links before adding then to this * {@link PgeMetadata}'s key links. Also add in the list of given * {@link PgeMetadata}'s LOCAL metadata marked for promotion DYNAMIC * metadata. * * @param pgeMetadata * A {@link PgeMetadata} whose metadata and key links will be added * to this {@link PgeMetadata}'s metadata and key links. * @param group * The namespace which will be used to namespace given * {@link PgeMetadata}'s LOCAL metadata and key links before being * added to this {@link PgeMetadata}'s LOCAL metadata and key * links. */ public void replaceMetadata(PgeMetadata pgeMetadata, String group) { Validate.notNull(pgeMetadata, "pgeMetadata cannot be null"); Validate.notNull(group, "group cannot be null"); staticMetadata.replaceMetadata(pgeMetadata.staticMetadata); dynamicMetadata.replaceMetadata(pgeMetadata.dynamicMetadata); localMetadata.replaceMetadata(group, pgeMetadata.localMetadata); // Namespace link keys that point to either importing // metadata's local key or link key. for (String keyLink : pgeMetadata.keyLinkMap.keySet()) { String key = pgeMetadata.keyLinkMap.get(keyLink); // Check if key is was local key or a link key if (pgeMetadata.localMetadata.containsKey(key) || pgeMetadata.keyLinkMap.containsKey(key)) { key = group + "/" + key; } linkKey(group + "/" + keyLink, key); } // Namespace workflow keys that point to either importing // metadata's local key or link key. for (String key : pgeMetadata.markedAsDynamicMetKeys) { if (pgeMetadata.localMetadata.containsKey(key) || pgeMetadata.keyLinkMap.containsKey(key)) { key = group + "/" + key; } markAsDynamicMetadataKey(key); } } /** * Use to mark LOCAL keys which should be moved into DYNAMIC metadata when * {@link #commitMarkedDynamicMetadataKeys(String...)} is invoked. If no * args are specified then all LOCAL metadata is marked for move to * DYNAMIC metadata. * * @param keys * Keys to mark as to be made DYNAMIC, otherwise if no keys then * all LOCAL metadata keys are mark for move to DYNAMIC. */ public void markAsDynamicMetadataKey(String... keys) { List<String> markedKeys = Lists.newArrayList(keys); if (markedKeys.isEmpty()) { markedKeys.addAll(localMetadata.getAllKeys()); } markedAsDynamicMetKeys.addAll(markedKeys); } /** * Use to commit marked LOCAL keys to DYNAMIC keys. Specify a list of keys * only if you want to limit the keys which get committed, otherwise all * marked keys will be moved into DYNAMIC metadata. * * @param keys * The list of marked LOCAL metadata keys which should be moved * into DYNAMIC metadata. If no keys are specified then all marked * keys are moved. */ public void commitMarkedDynamicMetadataKeys(String... keys) { Set<String> commitKeys = Sets.newHashSet(keys); if (commitKeys.isEmpty()) { commitKeys.addAll(markedAsDynamicMetKeys); } else { commitKeys.retainAll(markedAsDynamicMetKeys); } for (String key : commitKeys) { dynamicMetadata.replaceMetadata(key, localMetadata.getAllMetadata(resolveKey(key))); localMetadata.removeMetadata(key); markedAsDynamicMetKeys.remove(key); } } @VisibleForTesting protected Set<String> getMarkedAsDynamicMetadataKeys() { return Collections.unmodifiableSet(markedAsDynamicMetKeys); } /** * Create a key which is a link to another key, such that if you get the * metadata values for the created link it will return the current metadata * values of the key it was linked to. NOTE: if the key's metadata values * change, then the metadata values for the link key will also be the changed * values. If you want to create a key which holds the current value of a * key, then create a new metadata key. * * @param keyLink * The name of the link key you wish to create. * @param key * The key you which to link to (may also be a key link) */ public void linkKey(String keyLink, String key) { Validate.notNull(keyLink, "keyLink cannot be null"); Validate.notNull(key, "key cannot be null"); localMetadata.removeMetadata(keyLink); keyLinkMap.put(keyLink, key); } /** * Removes a key link reference. The key which the key link was linked to * remains unchanged. * * @param keyLink * The key link which you wish to destroy. */ public void unlinkKey(String keyLink) { Validate.notNull(keyLink, "keyLink cannot be null"); keyLinkMap.remove(keyLink); } /** * Check if the given key name is a key link. * * @param key * The key name in question. * @return True is the given key name is a key link, false if key name is an * actual key. */ public boolean isLink(String key) { Validate.notNull(key, "key cannot be null"); return keyLinkMap.containsKey(key); } /** * Find the actual key whose value will be returned for the given key. If the * given key is a key (not a key link) then the given key will just be * returned, otherwise it will trace through key link mapping to find the key * which the given key link points to. * * @param key * The name of a key or key link. * @return The key whose value will be returned for the given key or key * link. */ public String resolveKey(String key) { Validate.notNull(key, "key cannot be null"); while (keyLinkMap.containsKey(key)) { key = keyLinkMap.get(key); } return key; } /** * Determines the path by which the given key (if it is a key link) links to * the key whose value it will return. If the given key is a key link and * points to a key then the returning {@link List} will be of size 1 and will * contain just that key. However, if the given key is a key link which * points to another key link then the returning {@link List} will be greater * than 1 (will depend on how many key links are connected before they actual * point to a key. If the given key is a key, then the returning {@link List} * will be empty. * * @param key * The path to the key whose value will be returned for the give * key. * @return A key path {@link List}. */ public List<String> getReferenceKeyPath(String key) { Validate.notNull(key, "key cannot be null"); List<String> keyPath = Lists.newArrayList(); while (keyLinkMap.containsKey(key)) { keyPath.add(key = keyLinkMap.get(key)); } return keyPath; } public void replaceMetadata(PgeTaskMetKeys key, String value) { Validate.notNull(key, "key cannot be null"); replaceMetadata(key.getName(), value); } /** * Replace the given key's value with the given value. If the given key is a * key link, then it will update the value of the key it is linked to if that * key is DYNAMIC or LOCAL. If given key is a key link and it links to a * STATIC key, then a new LOCAL key will be create. * * @param key * The key or key link for whose value should be replaced. * @param value * The value to give the given key. Will replace any existing value * or will be the value of a newly created LOCAL key. */ public void replaceMetadata(String key, String value) { Validate.notNull(key, "key cannot be null"); Validate.notNull(value, "value cannot be null"); String resolveKey = resolveKey(key); // If key is a key link which points to a DYNAMIC key then update the // DYNAMIC key's value. if (keyLinkMap.containsKey(key) && dynamicMetadata.containsKey(resolveKey)) { dynamicMetadata.replaceMetadata(resolveKey, value); } else { localMetadata.replaceMetadata(resolveKey, value); } } /** * Replace all key values with the given key values in the provided * {@link Metadata}. If the key does not exist it will be created. * * @param metadata * {@link Metadata} to replace or create. */ public void replaceMetadata(Metadata metadata) { Validate.notNull(metadata, "metadata cannot be null"); for (String key : metadata.getAllKeys()) { replaceMetadata(key, metadata.getAllMetadata(key)); } } public void replaceMetadata(PgeTaskMetKeys key, List<String> values) { Validate.notNull(key, "key cannot be null"); replaceMetadata(key.getName(), values); } /** * Replace the given key's values with the given values. If the given key is * a key link, then it will update the values of the key it is linked to if * that key is DYNAMIC or LOCAL. If given key is a key link and it links to a * STATIC key, then a new LOCAL key will be create. * * @param key * The key or key link for whose values should be replaced. * @param values * The values to give the given key. Will replace any existing * values or will be the values of a newly created LOCAL key. */ public void replaceMetadata(String key, List<String> values) { Validate.notNull(key, "key cannot be null"); Validate.notNull(values, "values cannot be null"); String resolveKey = resolveKey(key); if (keyLinkMap.containsKey(key) && dynamicMetadata.containsKey(resolveKey)) { dynamicMetadata.replaceMetadata(resolveKey, values); } else { localMetadata.replaceMetadata(resolveKey, values); } } /** * Combines STATIC, DYNAMIC, and LOCAL metadata into one metadata object. You * can restrict which metadata you want combined and change the order in * which combining takes place by specifying Type arguments in the order you * which precedence to be observed. For example, if you perform the * following: pgeMetadata.asMetadata(LOCAL, STATIC) then only LOCAL and * STATIC metadata will be combined and LOCAL metadata will trump STATIC * metadata if they both contain the same key. If no arguments are specified * then DEFAULT_COMBINE_ORDER is used. * * @param types * The Type hierarchy you which to use when metadata is combined, * if no args then DEFAULT_COMBINE_ORDER is used. * @return Combined metadata. */ public Metadata asMetadata(Type... types) { List<Type> combineOrder = Lists.newArrayList(types); if (combineOrder.isEmpty()) { combineOrder.addAll(DEFAULT_COMBINE_ORDER); } Metadata combinedMetadata = new Metadata(); for (Type type : combineOrder) { switch (type) { case DYNAMIC: combinedMetadata.replaceMetadata(dynamicMetadata); break; case STATIC: combinedMetadata.replaceMetadata(staticMetadata); break; case LOCAL: combinedMetadata.replaceMetadata(localMetadata); for (String key : keyLinkMap.keySet()) { List<String> values = getAllMetadata(key); if (values != null) { combinedMetadata.replaceMetadata(key, values); } } break; } } return combinedMetadata; } public List<String> getAllMetadata(PgeTaskMetKeys key, Type... types) { return getAllMetadata(key.getName(), types); } /** * Get metadata values for given key. If Types are specified then it provides * the precedence order in which to search for the key. If no Type args are * specified then DEFAULT_QUERY_ORDER will be used. For example if * you pass in Type args: STATIC, LOCAL then STATIC metadata will first be * checked for the key and if it contains it, then it will return the found * value, otherwise it will then check LOCAL metadata for the key and if it * finds the value it will return it, otherwise null. * * @param key * The key for whose metadata values should be returned. * @param types * The type hierarchy which should be used, if no Types specified * DEFAULT_QUERY_ORDER will be used. * @return Metadata values for given key. */ public List<String> getAllMetadata(String key, Type... types) { List<Type> queryOrder = Lists.newArrayList(types); if (queryOrder.isEmpty()) { queryOrder.addAll(DEFAULT_QUERY_ORDER); } String useKey = resolveKey(key); for (Type type : queryOrder) { switch (type) { case DYNAMIC: if (dynamicMetadata.containsKey(useKey)) { return dynamicMetadata.getAllMetadata(useKey); } break; case STATIC: if (staticMetadata.containsKey(useKey)) { return staticMetadata.getAllMetadata(useKey); } break; case LOCAL: if (localMetadata.containsKey(useKey)) { return localMetadata.getAllMetadata(useKey); } break; } } return new Vector<String>(); } public String getMetadata(PgeTaskMetKeys key, Type... types) { return getMetadata(key.getName(), types); } /** * Returns the first value returned by {@link #getAllMetadata(String, Type...)}, if it returns * null then this method will also return null. */ public String getMetadata(String key, Type... types) { List<String> values = getAllMetadata(key, types); return values != null && values.size() > 0 ? values.get(0) : null; } }