/* * Copyright 2013 Cloudera Inc. * * 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 org.kitesdk.data.spi.partition; import com.google.common.base.CharMatcher; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import javax.annotation.concurrent.Immutable; import org.kitesdk.data.spi.FieldPartitioner; import org.kitesdk.data.spi.predicates.Exists; import org.kitesdk.data.spi.predicates.In; import org.kitesdk.data.spi.predicates.Predicates; import org.kitesdk.data.spi.predicates.Range; import org.kitesdk.data.spi.predicates.Ranges; /** * A FieldPartitioner that formats a timestamp (long) in milliseconds since * epoch, such as those returned by {@link System#currentTimeMillis()}, using * {@link SimpleDateFormat}. * * @since 0.9.0 */ @edu.umd.cs.findbugs.annotations.SuppressWarnings(value={ "NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE", "SE_COMPARATOR_SHOULD_BE_SERIALIZABLE"}, justification="False positive due to generics.") @Immutable public class DateFormatPartitioner extends FieldPartitioner<Long, String> { private static final String DEFAULT_TIME_ZONE = "UTC"; private final SimpleDateFormat format; /** * Construct a new {@link DateFormatPartitioner} for Universal Coordinated * Time, UTC (+00:00), and cardinality 1095 (3 years, 1 day = 1 partition). * @param sourceName Source field name (the field should be a long) * @param name Partition name * @param format A String format for the {@link SimpleDateFormat} constructor */ public DateFormatPartitioner(String sourceName, String name, String format) { this(sourceName, name, format, 1095, TimeZone.getTimeZone(DEFAULT_TIME_ZONE)); } /** * Construct a new {@link DateFormatPartitioner} for Universal Coordinated * Time, UTC (+00:00). * @param sourceName Source field name (the field should be a long) * @param name Partition name * @param format A String format for the {@link SimpleDateFormat} constructor * @param cardinality * A cardinality hint for the number of partitions that will be * created by this partitioner. For example, "MM-dd" produces about * 365 partitions per year. */ public DateFormatPartitioner(String sourceName, String name, String format, int cardinality, TimeZone zone) { super(sourceName, name, Long.class, String.class, cardinality); Preconditions.checkArgument(CharMatcher.is('/').matchesNoneOf(format), "Illegal format: \"/\" is not allowed (use multiple partition fields)"); this.format = new SimpleDateFormat(format); this.format.setTimeZone(zone); } public String getPattern() { return format.toPattern(); } @Override public String apply(Long value) { return format.format(new Date(value)); } @Override public Predicate<String> project(Predicate<Long> predicate) { if (predicate instanceof Exists) { return Predicates.exists(); } else if (predicate instanceof In) { return ((In<Long>) predicate).transform(this); } else if (predicate instanceof Range) { // FIXME: This project is only true in some cases // true for yyyy-MM-dd, but not dd-MM-yyyy // this is lossy, so the final range must be closed: // (2013-10-4 20:17:55, ...] => [2013-10-4, ...] return Ranges.transformClosed((Range<Long>) predicate, this); } else { return null; } } @Override public Predicate<String> projectStrict(Predicate<Long> predicate) { if (predicate instanceof Exists) { return Predicates.exists(); } else { return null; } } @Override public int compare(String o1, String o2) { return o1.compareTo(o2); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || !getClass().equals(o.getClass())) { return false; } DateFormatPartitioner that = (DateFormatPartitioner) o; return Objects.equal(this.getSourceName(), that.getSourceName()) && Objects.equal(this.getName(), that.getName()) && Objects.equal(this.format, that.format) && Objects.equal(this.getCardinality(), that.getCardinality()); } @Override public int hashCode() { return Objects.hashCode(getSourceName(), getName(), format, getCardinality()); } }