Index: src/main/java/org/gbif/api/service/metrics/CubeService.java =================================================================== --- src/main/java/org/gbif/api/service/metrics/CubeService.java (revision 0) +++ src/main/java/org/gbif/api/service/metrics/CubeService.java (revision 0) @@ -0,0 +1,40 @@ +/* + * Copyright 2012 Global Biodiversity Information Facility (GBIF) + * 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.gbif.api.service.metrics; + +import org.gbif.api.model.metrics.cube.ReadBuilder; +import org.gbif.api.model.metrics.cube.Rollup; + +import java.util.List; + +/** + * The cube API service, for reading addressable counts from a basic cube. + */ +public interface CubeService { + + /** + * Using the supplied {@link ReadBuilder} to obtain the address, looks up the cube value. + * + * @param addressBuilder To obtain the address at which to look up from the cube. + * @return The value which might be 0. A value of 0 means that the count is truly at 0. + * @throws IllegalArgumentException Should the addressBuilder provide an address that does not exist in the cube. + */ + long get(ReadBuilder addressBuilder) throws IllegalArgumentException; + + /** + * Provides the list of rollups which specifies the available combinations of addressable dimensions for a cube. + * + * @return The list of rollups, effectively documenting how one could query the cube. + */ + List getSchema(); +} Index: src/main/java/org/gbif/api/model/metrics/cube/Dimension.java =================================================================== --- src/main/java/org/gbif/api/model/metrics/cube/Dimension.java (revision 0) +++ src/main/java/org/gbif/api/model/metrics/cube/Dimension.java (revision 0) @@ -0,0 +1,77 @@ +/* + * Copyright 2012 Global Biodiversity Information Facility (GBIF) + * 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.gbif.api.model.metrics.cube; + +import com.google.common.base.Objects; + +/** + * A typed dimension to a cube, which is constructed with a unique key. This key defines the HTTP parameter name during + * serialization, and the type is respected by the {@link ReadBuilder} during lookups. + * + * @param The value type supported for the dimension + */ +public class Dimension { + + private final String key; + + /** + * Force the provision of the key in construction, which should be unique for the cube. + * + * @param key Which is used in the HTTP parameters + */ + public Dimension(String key) { + this.key = key; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Dimension other = (Dimension) obj; + if (key == null) { + if (other.key != null) { + return false; + } + } else if (!key.equals(other.key)) { + return false; + } + return true; + } + + /** + * @return The dimension key + */ + public String getKey() { + return key; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((key == null) ? 0 : key.hashCode()); + return result; + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("key", key).toString(); + } +} Index: src/main/java/org/gbif/api/model/metrics/cube/OccurrenceCube.java =================================================================== --- src/main/java/org/gbif/api/model/metrics/cube/OccurrenceCube.java (revision 0) +++ src/main/java/org/gbif/api/model/metrics/cube/OccurrenceCube.java (revision 0) @@ -0,0 +1,59 @@ +/* + * Copyright 2012 Global Biodiversity Information Facility (GBIF) + * 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.gbif.api.model.metrics.cube; + +import org.gbif.api.vocabulary.BasisOfRecord; +import org.gbif.api.vocabulary.Country; +import org.gbif.api.vocabulary.Kingdom; + +import java.util.List; +import java.util.UUID; + +import com.google.common.collect.ImmutableList; + +/** + * The occurrence cube dimension definitions and the way in which they are rolled up into counts. + */ +public class OccurrenceCube { + + public static final Dimension KINGDOM = new Dimension("kingdom"); + public static final Dimension COUNTRY = new Dimension("country"); + public static final Dimension YEAR = new Dimension("year"); + public static final Dimension IS_GEOREFERENCED = new Dimension("georeferenced"); + public static final Dimension BASIS_OF_RECORD = new Dimension("basisOfRecord"); + public static final Dimension HOST_COUNTRY = new Dimension("hostCountry"); + public static final Dimension DATASET_KEY = new Dimension("datasetKey"); + public static final Dimension MONTH = new Dimension("month"); + public static final Dimension NUB_KEY = new Dimension("nubKey"); + + + public static final List rollups = ImmutableList.of( + new Rollup(BASIS_OF_RECORD), + new Rollup(IS_GEOREFERENCED), + new Rollup(KINGDOM), + new Rollup(COUNTRY), + new Rollup(HOST_COUNTRY), + new Rollup(DATASET_KEY), + new Rollup(YEAR), + new Rollup(NUB_KEY), + new Rollup(KINGDOM, BASIS_OF_RECORD), + new Rollup(KINGDOM, IS_GEOREFERENCED), + new Rollup(KINGDOM, BASIS_OF_RECORD, IS_GEOREFERENCED), + new Rollup(NUB_KEY, YEAR), + new Rollup(NUB_KEY, MONTH), + new Rollup(DATASET_KEY, NUB_KEY), + new Rollup(DATASET_KEY, YEAR), + new Rollup(DATASET_KEY, KINGDOM, BASIS_OF_RECORD, IS_GEOREFERENCED), + new Rollup(COUNTRY, HOST_COUNTRY, KINGDOM, BASIS_OF_RECORD) + ); +} Index: src/main/java/org/gbif/api/model/metrics/cube/ReadBuilder.java =================================================================== --- src/main/java/org/gbif/api/model/metrics/cube/ReadBuilder.java (revision 0) +++ src/main/java/org/gbif/api/model/metrics/cube/ReadBuilder.java (revision 0) @@ -0,0 +1,89 @@ +/* + * Copyright 2012 Global Biodiversity Information Facility (GBIF) + * 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.gbif.api.model.metrics.cube; + +import java.util.Map; +import java.util.UUID; + +import javax.annotation.concurrent.NotThreadSafe; + +import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; + +/** + * Provides building of addresses for reading the cube. + * This class ensures the type safety of dimensions as they are added to the builder. + */ +@NotThreadSafe +public class ReadBuilder { + + private final Map, String> address = Maps.newHashMap(); + + /** + * Adds an enumerated type dimension to the address. + */ + public ReadBuilder at(Dimension> dim, Enum value) { + Preconditions.checkNotNull(value, "Dimension cannot be null"); + address.put(dim, value.name()); + return this; + } + + /** + * Adds a double typed dimension to the address. + */ + public ReadBuilder at(Dimension dim, double value) { + address.put(dim, String.valueOf(value)); + return this; + } + + /** + * Adds a float typed dimension to the address. + */ + public ReadBuilder at(Dimension dim, float value) { + address.put(dim, String.valueOf(value)); + return this; + } + + /** + * Adds an integer typed dimension to the address. + */ + public ReadBuilder at(Dimension dim, int value) { + address.put(dim, String.valueOf(value)); + return this; + } + + /** + * Adds an String typed dimension to the address. + */ + public ReadBuilder at(Dimension dim, String value) { + Preconditions.checkNotNull(value, "Dimension cannot be null"); + address.put(dim, value); + return this; + } + + /** + * Adds a UUID typed dimension to the address. + */ + public ReadBuilder at(Dimension dim, UUID value) { + Preconditions.checkNotNull(value, "Dimension cannot be null"); + address.put(dim, value.toString()); + return this; + } + + /** + * @return The built address. + */ + public Map, String> build() { + return address; + } +} Index: src/main/java/org/gbif/api/model/metrics/cube/Rollup.java =================================================================== --- src/main/java/org/gbif/api/model/metrics/cube/Rollup.java (revision 0) +++ src/main/java/org/gbif/api/model/metrics/cube/Rollup.java (revision 0) @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Global Biodiversity Information Facility (GBIF) + * 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.gbif.api.model.metrics.cube; + +import java.util.Arrays; +import java.util.Set; + +import com.google.common.collect.ImmutableSet; + +/** + * A rollup defines an addressable count that is maintained for a set of dimensions. + */ +public class Rollup { + + private final Set> components; + + public Rollup(Dimension d) { + this(ImmutableSet.>of(d)); + } + + public Rollup(Dimension ... d) { + this(new ImmutableSet.Builder>().addAll(Arrays.asList(d)).build()); + } + + public Rollup(Dimension d1, Dimension d2) { + this(ImmutableSet.>of(d1, d2)); + } + + public Rollup(Set> components) { + this.components = ImmutableSet.copyOf(components); // defensive copy + } + + public Set> getComponents() { + return components; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("(Rollup over "); + sb.append(components); + sb.append(")"); + return sb.toString(); + } +} Index: src/main/java/org/gbif/api/model/metrics/cube/package-info.java =================================================================== --- src/main/java/org/gbif/api/model/metrics/cube/package-info.java (revision 0) +++ src/main/java/org/gbif/api/model/metrics/cube/package-info.java (revision 0) @@ -0,0 +1,17 @@ +/** + * Provides the classes necessary to construct read only requests to well defined cubes. + * Cubes are defined with typed dimensions, and a {@link ReadBuilder} is supplied to help ensure types are respected + * when constructing cube lookups. A typical usage might be as follows: + * + *
+ * {@code
+ *   new ReadBuilder()
+ *     .at(OccurrenceCube.YEAR, 2012)
+ *     .at(OccurrenceCube.KINGDOM, Kingdom.ANIMALIA)
+ *     .build();
+ * }
+ * 
+ * + * @since 0.1 + */ +package org.gbif.api.model.metrics.cube; \ No newline at end of file Index: src/test/java/org/gbif/api/model/metrics/cube/ReadBuilderTest.java =================================================================== --- src/test/java/org/gbif/api/model/metrics/cube/ReadBuilderTest.java (revision 0) +++ src/test/java/org/gbif/api/model/metrics/cube/ReadBuilderTest.java (revision 0) @@ -0,0 +1,95 @@ +package org.gbif.api.model.metrics.cube; + +import org.gbif.api.vocabulary.Kingdom; + +import java.util.UUID; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests the type safety of the builder, and illustrates usage. + */ +public class ReadBuilderTest { + + private static final class DoubleDimension extends Dimension { + + public DoubleDimension() { + super("double"); + } + } + + private static final class EnumDimension extends Dimension { + + public EnumDimension() { + super("enum"); + } + } + + private static final class FloatDimension extends Dimension { + + public FloatDimension() { + super("float"); + } + } + + private static final class IntDimension extends Dimension { + + public IntDimension() { + super("int"); + } + } + + private static final class StringDimension extends Dimension { + + public StringDimension() { + super("string"); + } + } + + private static final class UUIDDimension extends Dimension { + + public UUIDDimension() { + super("uuid"); + } + } + + + @Test + public void testDimensions() { + ReadBuilder b = new ReadBuilder(); + UUID uuid = UUID.randomUUID(); + + b.at(new StringDimension(), "1"); + assertNotNull(b.build()); + assertEquals(1, b.build().size()); + assertEquals("1", b.build().get(new StringDimension())); + + b.at(new IntDimension(), 1); + assertNotNull(b.build()); + assertEquals(2, b.build().size()); + assertEquals("1", b.build().get(new IntDimension())); + + b.at(new FloatDimension(), 1f); + assertNotNull(b.build()); + assertEquals(3, b.build().size()); + assertEquals("1.0", b.build().get(new FloatDimension())); + + b.at(new DoubleDimension(), 1d); + assertNotNull(b.build()); + assertEquals(4, b.build().size()); + assertEquals("1.0", b.build().get(new DoubleDimension())); + + b.at(new UUIDDimension(), uuid); + assertNotNull(b.build()); + assertEquals(5, b.build().size()); + assertEquals(uuid.toString(), b.build().get(new UUIDDimension())); + + b.at(new EnumDimension(), Kingdom.ANIMALIA); + assertNotNull(b.build()); + assertEquals(6, b.build().size()); + assertEquals(Kingdom.ANIMALIA.toString(), b.build().get(new EnumDimension())); + } +}