/* * Copyright 2013 Cloudera. * * 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 * * 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 com.cloudera.cdk.data.spi; import com.cloudera.cdk.data.FieldPartitioner; import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import java.util.Map; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** * A {@code Marker} is a bookmark in the partition space, which is the space of * each possible partition in a {@link com.cloudera.cdk.data.PartitionStrategy}. * * A {@code Marker} holds un-ordered values that can be used to create keys by * a {@code PartitionStrategy}. It can hold either source values, like a * timestamp, or concrete values, like a year. * * A {@code Marker} can be used as a generic placeholder or bookmark for an * entity (an object in a {@link com.cloudera.cdk.data.Dataset}. For example, when fetching a record * from HBase, you can use a {@code Marker} to hold the information that * {@code PartitionStrategy} uses to build a concrete key and fetch the entity. * * A {@code Marker} can also be used as a partial key, where some of the values * needed to create a complete key are missing. In this case, a {@code Marker} * contains a subset of the partition space: all {@code Marker} the keys that * share the values that are set in the Marker. * * The {@code Marker.Builder} class is the easiest way to make a {@code Marker}. * Any {@code Marker} created by the {@link Marker.Builder} is {@link Immutable} * and will not change. The {@code Builder} can copy values from other * {@code Marker} objects and provides a fluent interface to easily create new * instances: * <pre> * // a partial Marker for all events with the same hash code and approx time * Marker partial = new Marker.Builder() * .add("shard", id.hashCode()) * .add("timestamp", System.getTimeMillis()) * .get(); * * // a Marker that represents a particular object * Marker concrete = new Marker.Builder(partial).add("id", id).get(); * * // a partial Marker for approx time, for all shards * new Marker.Builder("timestamp", System.getTimeMillis()).get(); * </pre> * * @since 0.9.0 */ public abstract class Marker { /** * Returns whether {@code name} is stored in this Marker. * * @param name a String name * @return true if there is a value for {@code name} */ public abstract boolean has(String name); /** * Returns the value for {@code name}. * * @param name the String name of the value to return * @return the Object stored for {@code name} */ public abstract Object get(String name); /** * Returns the value for {@code name} coerced to the given type, T. * * @param <T> the return type * @param name the String name of the value to return * @param returnType The return type, which must be assignable from Long, * Integer, String, or Object * @return the Object stored for {@code name} coerced to a T * @throws ClassCastException if the return type is unknown */ public <T> T getAs(String name, Class<T> returnType) { return Conversions.convert(get(name), returnType); } /** * Return the value of a {@code FieldPartitioner} field for this {@link Marker}. * * If the {@code Marker} has a value for the field's name, that value is * returned using {@link Marker#getAs(java.lang.String, java.lang.Class)}. If * the {@code Marker} only has a value for the the source field name, then * that value is retrieved using * {@link com.cloudera.cdk.data.spi.Marker#getAs(java.lang.String, * java.lang.Class)} and the field's transformation is applied to it as the source value. * * @param fp a {@code FieldPartitioner} * @return the value of the field for this {@code marker}, or null * @since 0.9.0 */ @Nullable public <S, T> T valueFor(FieldPartitioner<S, T> fp) { if (has(fp.getName())) { return getAs(fp.getName(), fp.getType()); } else if (has(fp.getSourceName())) { return fp.apply(getAs(fp.getSourceName(), fp.getSourceType())); } else { return null; } } /** * A basic Marker implementation backed by a Map. * * @since 0.9.0 */ @Immutable static class ImmutableMarker extends Marker { final Map<String, Object> values; public ImmutableMarker(Map<String, Object> content) { this.values = ImmutableMap.copyOf(content); } @Override public boolean has(String name) { return values.containsKey(name); } @Override public Object get(String name) { return values.get(name); } @Override public String toString() { return Objects.toStringHelper(this).add("values", values).toString(); } } /** * A fluent builder for creating Marker instances. * * @since 0.9.0 */ public static class Builder { private final Map<String, Object> content; /** * Constructs a {@code Builder} from the contents of a {@code Marker}. * * @param toCopy a Marker that will be copied */ public Builder(ImmutableMarker toCopy) { this(toCopy.values); } /** * Constructs a {@code Builder} from the contents of another {@code Builder}. * * @param toCopy a Builder that will be copied */ public Builder(Builder toCopy) { this(toCopy.content); } /** * Constructs a {@code Builder} from the contents of a {@code Map}. * * @param content a Map from String names to value Objects */ public Builder(Map<String, Object> content) { // defensive copy: don't trust the incoming map isn't Immutable or reused this.content = Maps.newHashMap(content); } /** * A convenience constructor for a {@code Builder} with only one value. * * <pre> * // this makes sense when using only Calendar partition fields * Marker end = new Marker.Builder("timestamp", System.currentTimeMillis()).get(); * </pre> * * @param name a String name * @param value an Object value */ public Builder(String name, Object value) { this.content = Maps.newHashMapWithExpectedSize(1); content.put(name, value); } /** * Constructs an empty {@code Builder}. */ public Builder() { this.content = Maps.newHashMap(); } /** * Clears the content already added to this builder so it can be reused. */ public void clear() { content.clear(); } /** * Adds a named value to this {@code Builder}. * * @param name a String name for the value * @param value a value * @return this Builder, for method chaining */ public Builder add(String name, Object value) { content.put(name, value); return this; } /** * Builds a {@link Marker} from the content added to this {@code Builder}. * * @return a Marker for the content in this Builder. */ public Marker build() { return new ImmutableMarker(content); } } }