blob: a56c8c45b5ea94ffecbe87c1f74c46c43a596684 [file] [log] [blame]
/*
* 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;
}
}