RYA-81 Added a structure that provides metadata about an instance of Rya as well as a repository that may be used to store and update it. This information could be used to configure clients when querying the instance.
Project: http://git-wip-us.apache.org/repos/asf/incubator-rya/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-rya/commit/9bdbbf5d Tree: http://git-wip-us.apache.org/repos/asf/incubator-rya/tree/9bdbbf5d Diff: http://git-wip-us.apache.org/repos/asf/incubator-rya/diff/9bdbbf5d Branch: refs/heads/develop Commit: 9bdbbf5dd9ba5f7662e0044c09a1acf17095b616 Parents: d49aa6d Author: Kevin Chilton <[email protected]> Authored: Sat Jun 4 17:46:43 2016 -0400 Committer: Aaron Mihalik <[email protected]> Committed: Tue Aug 23 10:41:37 2016 -0400 ---------------------------------------------------------------------- .../java/mvm/rya/api/instance/RyaDetails.java | 908 +++++++++++++++++++ .../rya/api/instance/RyaDetailsRepository.java | 123 +++ .../mvm/rya/api/instance/RyaDetailsTest.java | 149 +++ dao/accumulo.rya/pom.xml | 90 ++ .../AccumuloRyaInstanceDetailsRepository.java | 229 +++++ .../accumulo/instance/RyaDetailsSerializer.java | 96 ++ .../AccumuloRyaDetailsRepositoryIT.java | 368 ++++++++ 7 files changed, 1963 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9bdbbf5d/common/rya.api/src/main/java/mvm/rya/api/instance/RyaDetails.java ---------------------------------------------------------------------- diff --git a/common/rya.api/src/main/java/mvm/rya/api/instance/RyaDetails.java b/common/rya.api/src/main/java/mvm/rya/api/instance/RyaDetails.java new file mode 100644 index 0000000..895eb36 --- /dev/null +++ b/common/rya.api/src/main/java/mvm/rya/api/instance/RyaDetails.java @@ -0,0 +1,908 @@ +package mvm.rya.api.instance; + +/* + * 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. + */ + +import static java.util.Objects.requireNonNull; + +import java.io.Serializable; +import java.util.Date; +import java.util.Objects; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.concurrent.Immutable; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; + +/** + * Details about how a Rya instance's state. + */ +@Immutable +@ParametersAreNonnullByDefault +public class RyaDetails implements Serializable { + private static final long serialVersionUID = 1L; + + // General metadata about the instance. + private final String instanceName; + private final String version; + + // Secondary Index Details. + private final EntityCentricIndexDetails entityCentricDetails; + private final GeoIndexDetails geoDetails; + private final PCJIndexDetails pcjDetails; + private final TemporalIndexDetails temporalDetails; + private final FreeTextIndexDetails freeTextDetails; + + // Statistics Details. + private final ProspectorDetails prospectorDetails; + private final JoinSelectivityDetails joinSelectivityDetails; + + /** + * Private to prevent initialization through the constructor. To build + * instances of this class, use the {@link Builder}. + */ + private RyaDetails( + final String instanceName, + final String version, + final EntityCentricIndexDetails entityCentricDetails, + final GeoIndexDetails geoDetails, + final PCJIndexDetails pcjDetails, + final TemporalIndexDetails temporalDetails, + final FreeTextIndexDetails freeTextDetails, + final ProspectorDetails prospectorDetails, + final JoinSelectivityDetails joinSelectivityDetails) { + this.instanceName = requireNonNull(instanceName); + this.version = requireNonNull(version); + this.entityCentricDetails = requireNonNull(entityCentricDetails); + this.geoDetails = requireNonNull(geoDetails); + this.pcjDetails = requireNonNull(pcjDetails); + this.temporalDetails = requireNonNull(temporalDetails); + this.freeTextDetails = requireNonNull(freeTextDetails); + this.prospectorDetails = requireNonNull(prospectorDetails); + this.joinSelectivityDetails = requireNonNull(joinSelectivityDetails); + } + + /** + * @return The name that uniquely identifies the instance of Rya within + * the system that hosts it. + */ + public String getRyaInstanceName() { + return instanceName; + } + + /** + * @return The version of Rya this instance uses. + */ + public String getRyaVersion() { + return version; + } + + /** + * @return Information about the instance's Entity Centric Index. + */ + public EntityCentricIndexDetails getEntityCentricIndexDetails() { + return entityCentricDetails; + } + + /** + * @return Information about the instance's Geospatial Index. + */ + public GeoIndexDetails getGeoIndexDetails() { + return geoDetails; + } + + /** + * @return Information about the instance's Precomputed Join Index. + */ + public PCJIndexDetails getPCJIndexDetails() { + return pcjDetails; + } + + /** + * @return Information about the instance's Temporal Index. + */ + public TemporalIndexDetails getTemporalIndexDetails() { + return temporalDetails; + } + + /** + * @return Information about the instance's Free Text Index. + */ + public FreeTextIndexDetails getFreeTextIndexDetails() { + return freeTextDetails; + } + + /** + * @return Information about the instance's Prospector Statistics. + */ + public ProspectorDetails getProspectorDetails() { + return prospectorDetails; + } + + /** + * @return Information about the instance's Join Selectivity Statistics. + */ + public JoinSelectivityDetails getJoinSelectivityDetails() { + return joinSelectivityDetails; + } + + @Override + public int hashCode() { + return Objects.hash( + instanceName, + version, + entityCentricDetails, + geoDetails, + pcjDetails, + temporalDetails, + freeTextDetails, + prospectorDetails, + joinSelectivityDetails); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof RyaDetails) { + final RyaDetails details = (RyaDetails) obj; + return Objects.equals(instanceName, details.instanceName) && + Objects.equals(version, details.version) && + Objects.equals(entityCentricDetails, details.entityCentricDetails) && + Objects.equals(geoDetails, details.geoDetails) && + Objects.equals(pcjDetails, details.pcjDetails) && + Objects.equals(temporalDetails, details.temporalDetails) && + Objects.equals(freeTextDetails, details.freeTextDetails) && + Objects.equals(prospectorDetails, details.prospectorDetails) && + Objects.equals(joinSelectivityDetails, details.joinSelectivityDetails); + } + return false; + } + + /** + * @return An empty instance of {@link Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builds instances of {@link RyaDetails}. + */ + @ParametersAreNonnullByDefault + public static class Builder { + + // General metadata about the instance. + private String instanceName; + private String version; + + // Secondary Index Details. + private EntityCentricIndexDetails entityCentricDetails; + private GeoIndexDetails geoDetails; + private PCJIndexDetails pcjDetails; + private TemporalIndexDetails temporalDetails; + private FreeTextIndexDetails freeTextDetails; + + // Statistics Details. + private ProspectorDetails prospectorDetails; + private JoinSelectivityDetails joinSelectivityDetails; + + /** + * Construcst an empty instance of {@link Builder}. + */ + public Builder() { } + + /** + * Constructs an instance of {@link Builder} that is initialized with + * a {@link RyaDetails}'s values. + * + * @param detials - The builder will be initialized with this object's values. (not null) + */ + public Builder(final RyaDetails details) { + requireNonNull(details); + + this.instanceName = details.instanceName; + this.version = details.version; + this.entityCentricDetails = details.entityCentricDetails; + this.geoDetails = details.geoDetails; + this.pcjDetails = details.pcjDetails; + this.temporalDetails = details.temporalDetails; + this.freeTextDetails = details.freeTextDetails; + this.prospectorDetails = details.prospectorDetails; + this.joinSelectivityDetails = details.joinSelectivityDetails; + } + + /** + * @param instanceName - The name that uniquely identifies the instance of Rya within + * the system that hosts it. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setRyaInstanceName(@Nullable final String instanceName) { + this.instanceName = instanceName; + return this; + } + + /** + * @param version - The version of Rya this instance uses. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setRyaVersion(@Nullable final String version) { + this.version = version; + return this; + } + + /** + * @param entityCentricDetails - Information about the instance's Entity Centric Index. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setEntityCentricIndexDetails(@Nullable final EntityCentricIndexDetails entityCentricDetails) { + this.entityCentricDetails = entityCentricDetails; + return this; + } + + /** + * + * @param geoDetails - Information about the instance's Geospatial Index. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setGeoIndexDetails(@Nullable final GeoIndexDetails geoDetails) { + this.geoDetails = geoDetails; + return this; + } + + /** + * @param temporalDetails - Information about the instance's Temporal Index. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setTemporalIndexDetails(@Nullable final TemporalIndexDetails temporalDetails) { + this.temporalDetails = temporalDetails; + return this; + } + + /** + * @param freeTextDetails - Information about the instance's Free Text Index. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setFreeTextDetails(@Nullable final FreeTextIndexDetails freeTextDetails) { + this.freeTextDetails = freeTextDetails; + return this; + } + + /** + * @param pcjDetails - Information about the instance's Precomputed Join Index. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setPCJIndexDetails(@Nullable final PCJIndexDetails pcjDetails) { + this.pcjDetails = pcjDetails; + return this; + } + + /** + * @param prospectorDetails - Information about the instance's Prospector Statistics. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setProspectorDetails(@Nullable final ProspectorDetails prospectorDetails) { + this.prospectorDetails = prospectorDetails; + return this; + } + + /** + * @param joinSelectivityDetails - Information about the instance's Join Selectivity Statistics. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setJoinSelectivityDetails(@Nullable final JoinSelectivityDetails joinSelectivityDetails) { + this.joinSelectivityDetails = joinSelectivityDetails; + return this; + } + + /** + * @return An instance of {@link RyaDetails} built using this + * builder's values. + */ + public RyaDetails build() { + return new RyaDetails( + instanceName, + version, + entityCentricDetails, + geoDetails, + pcjDetails, + temporalDetails, + freeTextDetails, + prospectorDetails, + joinSelectivityDetails); + } + } + + /** + * Details about a Rya instance's Geospatial Index. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class GeoIndexDetails implements Serializable { + private static final long serialVersionUID = 1L; + + private final boolean enabled; + + /** + * Constructs an instance of {@link GeoIndexDetails}. + * + * @param enabled - Whether or not a Geospatial Index will be maintained by the Rya instance. + */ + public GeoIndexDetails(final boolean enabled) { + this.enabled = enabled; + } + + /** + * @return Whether or not a Geospatial Index will be maintained by the Rya instance. + */ + public boolean isEnabled() { + return enabled; + } + + @Override + public int hashCode() { + return Objects.hash( enabled ); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof GeoIndexDetails) { + final GeoIndexDetails details = (GeoIndexDetails) obj; + return enabled == details.enabled; + } + return false; + } + } + + /** + * Details about a Rya instance's Temporal Index. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class TemporalIndexDetails implements Serializable { + private static final long serialVersionUID = 1L; + + private final boolean enabled; + + /** + * Constructs an instance of {@link TemporalIndexDetails}. + * + * @param enabled - Whether or not a Temporal Index will be maintained by the Rya instance. + */ + public TemporalIndexDetails(final boolean enabled) { + this.enabled = enabled; + } + + /** + * @return Whether or not a Temporal Index will be maintained by the Rya instance. + */ + public boolean isEnabled() { + return enabled; + } + + @Override + public int hashCode() { + return Objects.hash( enabled ); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof TemporalIndexDetails) { + final TemporalIndexDetails details = (TemporalIndexDetails) obj; + return enabled == details.enabled; + } + return false; + } + } + + /** + * Details about a Rya instance's Entity Centric Index. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class EntityCentricIndexDetails implements Serializable { + private static final long serialVersionUID = 1L; + + private final boolean enabled; + + /** + * Constructs an instance of {@link EntityCentricIndexDetails}. + * + * @param enabled - Whether or not a Entity Centric Index will be maintained by the Rya instance. + */ + public EntityCentricIndexDetails(final boolean enabled) { + this.enabled = enabled; + } + + /** + * @return Whether or not a Entity Centric Index will be maintained by the Rya instance. + */ + public boolean isEnabled() { + return enabled; + } + + @Override + public int hashCode() { + return Objects.hash( enabled ); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof EntityCentricIndexDetails) { + final EntityCentricIndexDetails details = (EntityCentricIndexDetails) obj; + return enabled == details.enabled; + } + return false; + } + } + + /** + * Details about a Rya instance's Free Text Index. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class FreeTextIndexDetails implements Serializable { + private static final long serialVersionUID = 1L; + + private final boolean enabled; + + /** + * Constructs an instance of {@link FreeTextIndexDetails}. + * + * @param enabled - Whether or not a Free Text Index will be maintained by the Rya instance. + */ + public FreeTextIndexDetails(final boolean enabled) { + this.enabled = enabled; + } + + /** + * @return Whether or not a Free Text Index will be maintained by the Rya instance. + */ + public boolean isEnabled() { + return enabled; + } + + @Override + public int hashCode() { + return Objects.hash( enabled ); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof FreeTextIndexDetails) { + final FreeTextIndexDetails details = (FreeTextIndexDetails) obj; + return enabled == details.enabled; + } + return false; + } + } + + /** + * Details about a Rya instance's PCJ Index. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class PCJIndexDetails implements Serializable { + private static final long serialVersionUID = 1L; + + public final boolean enabled; + private final Optional<FluoDetails> fluoDetails; + private final ImmutableList<PCJDetails> pcjDetails; + + /** + * Private to prevent initialization through the constructor. To build + * instances of this class, use the {@link Builder}. + * + * @param enabled - Whether or not a Precomputed Join Index will be maintained by the Rya instance. + * @param fluoDetails - Details about a Fluo application that is used to + * incrementally update PCJs if one has been installed for this RYA + * instance. (not null) + * @param pcjDetails - Details about the PCJs that have been created + * for this Rya instance. (not null) + */ + private PCJIndexDetails( + final boolean enabled, + final Optional<FluoDetails> fluoDetails, + final ImmutableList<PCJDetails> pcjDetails) { + this.enabled = enabled; + this.fluoDetails = requireNonNull(fluoDetails); + this.pcjDetails = requireNonNull(pcjDetails); + } + + /** + * @return Whether or not a Precomputed Join Index will be maintained by the Rya instance. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * @return Details about a Fluo application that is used to incrementally + * update PCJs if one has been installed for this RYA instance. + */ + public Optional<FluoDetails> getFluoDetails() { + return fluoDetails; + } + + /** + * @return Details about the PCJs that have been created for this Rya instance. + */ + public ImmutableList<PCJDetails> getPCJDetails() { + return pcjDetails; + } + + @Override + public int hashCode() { + return Objects.hash(enabled, fluoDetails, pcjDetails); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof PCJIndexDetails) { + final PCJIndexDetails details = (PCJIndexDetails) obj; + return Objects.equals(enabled, details.enabled) && + Objects.equals(fluoDetails, details.fluoDetails) && + Objects.equals(pcjDetails, details.pcjDetails); + } + return false; + } + + /** + * @return A new instance of {@link Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builds instance of {@link PCJIndexDetails). + */ + @ParametersAreNonnullByDefault + public static class Builder { + + private Boolean enabled = null; + private FluoDetails fluoDetails = null; + private final ImmutableList.Builder<PCJDetails> pcjDetails = ImmutableList.builder(); + + /** + * @param enabled - Whether or not a Precomputed Join Index will be maintained by the Rya instance. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setEnabled(final Boolean enabled) { + this.enabled = enabled; + return this; + } + + /** + * @param fluoDetails - Details about a Fluo application that is used + * to incrementally update PCJs if one has been installed for this RYA instance. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setFluoDetails(@Nullable final FluoDetails fluoDetails) { + this.fluoDetails = fluoDetails; + return this; + } + + /** + * @param pcjDetails - Details about the PCJs that have been created for this Rya instance. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder addPCJDetails(@Nullable final PCJDetails pcjDetails) { + this.pcjDetails.add( pcjDetails ); + return this; + } + + /** + * @return Builds an instance of {@link PCJIndexDetails} using this builder's values. + */ + public PCJIndexDetails build() { + return new PCJIndexDetails( + enabled, + Optional.fromNullable( fluoDetails ), + pcjDetails.build()); + } + } + + /** + * Details about a Fluo Incremental PCJ application that has been installed + * as part of this Rya instance. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class FluoDetails implements Serializable { + private static final long serialVersionUID = 1L; + + private final String updateAppName; + + /** + * Constructs an instance of {@link FluoDetails}. + * + * @param updateAppName - The name of the Fluo application that is + * updating this Rya instance's incremental PCJs. (not null) + */ + public FluoDetails(final String updateAppName) { + this.updateAppName = requireNonNull(updateAppName); + } + + @Override + public int hashCode() { + return Objects.hash(updateAppName); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof FluoDetails) { + final FluoDetails details = (FluoDetails) obj; + return Objects.equals(updateAppName, details.updateAppName); + } + return false; + } + } + + /** + * Details about a specific PCJ that is being maintained within the Rya instance. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class PCJDetails implements Serializable { + private static final long serialVersionUID = 1L; + + private final String id; + private final PCJUpdateStrategy updateStrategy; + private final Optional<Date> lastUpdateTime; + + /** + * Private to prevent initialization through the constructor. To build + * instances of this class, use the {@link Builder}. + * + * @param id - Uniquely identifies the PCJ within this instance of Rya. (not null) + * @param updateStrategy - Describes how the PCJ is being updated. (not null) + * @param lastUpdateTime - The last time the PCJ was updated. This information + * may not be provided. (not null) + */ + private PCJDetails( + final String id, + final PCJUpdateStrategy updateStrategy, + final Optional<Date> lastUpdateTime) { + this.id = requireNonNull(id); + this.updateStrategy = requireNonNull(updateStrategy); + this.lastUpdateTime = requireNonNull(lastUpdateTime); + } + + /** + * @return Uniquely identifies the PCJ within this instance of Rya. + */ + public String getId() { + return id; + } + + /** + * @return Describes how the PCJ is being updated. + */ + public PCJUpdateStrategy getUpdateStrategy() { + return updateStrategy; + } + + /** + * @return The last time the PCJ was updated. This information + * may not be provided. + */ + public Optional<Date> getLastUpdateTime() { + return lastUpdateTime; + } + + @Override + public int hashCode() { + return Objects.hash(id, updateStrategy, lastUpdateTime); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof PCJDetails) { + final PCJDetails details = (PCJDetails) obj; + return Objects.equals(id, details.id) && + Objects.equals(updateStrategy, details.updateStrategy) && + Objects.equals(lastUpdateTime, details.lastUpdateTime); + } + return false; + } + + /** + * @return A new instance of {@link Builder}. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builds instance of {@link PCJDetails}. + */ + @ParametersAreNonnullByDefault + public static class Builder { + + private String id; + private PCJUpdateStrategy updateStrategy; + private Date lastUpdateTime; + + /** + * @param id - Uniquely identifies the PCJ within this instance of Rya. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setId(@Nullable final String id) { + this.id = id; + return this; + } + + /** + * @param updateStrategy - Describes how the PCJ is being updated. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setUpdateStrategy(@Nullable final PCJUpdateStrategy updateStrategy) { + this.updateStrategy = updateStrategy; + return this; + } + + /** + * @param lastUpdateTime - The last time the PCJ was updated. This information + * may not be provided. + * @return This {@link Builder} so that method invocations may be chained. + */ + public Builder setLastUpdateTime(@Nullable final Date lastUpdateTime) { + this.lastUpdateTime = lastUpdateTime; + return this; + } + + /** + * @return An instance of {@link PCJDetails} built using this builder's values. + */ + public PCJDetails build() { + return new PCJDetails(id, updateStrategy, Optional.fromNullable(lastUpdateTime)); + } + } + + /** + * Describes the different strategies that may be used to update a PCJ index. + */ + public static enum PCJUpdateStrategy { + /** + * The PCJ is being updated by periodically rebuilding all of the results. + */ + BATCH, + + /** + * The PCJ is being updated frequently and incrementally as new + * Statements are inserted into the Tya instance. + */ + INCREMENTAL; + } + } + } + + /** + * Details about a Rya instance's Prospector statistics. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class ProspectorDetails implements Serializable { + private static final long serialVersionUID = 1L; + + private final Optional<Date> lastUpdated; + + /** + * Constructs an instance of {@link ProspectorDetails}. + * + * @param lastUpdated - The last time the Prospector statistics were updated for the Rya instance. (not null) + */ + public ProspectorDetails(final Optional<Date> lastUpdated) { + this.lastUpdated = requireNonNull(lastUpdated); + } + + /** + * @return The last time the Prospector statistics were updated for the Rya instance. + */ + public Optional<Date> getLastUpdated() { + return lastUpdated; + } + + @Override + public int hashCode() { + return Objects.hash( lastUpdated ); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof ProspectorDetails) { + final ProspectorDetails details = (ProspectorDetails) obj; + return Objects.equals(lastUpdated, details.lastUpdated); + } + return false; + } + } + + /** + * Details about a Rya instance's Join Selectivity statistics. + */ + @Immutable + @ParametersAreNonnullByDefault + public static class JoinSelectivityDetails implements Serializable { + private static final long serialVersionUID = 1L; + + private final Optional<Date> lastUpdated; + + /** + * Constructs an instance of {@link JoinSelectivityDetails}. + * + * @param lastUpdated - The last time the Join Selectivity statistics were updated for the Rya instance. (not null) + */ + public JoinSelectivityDetails(final Optional<Date> lastUpdated) { + this.lastUpdated = requireNonNull(lastUpdated); + } + + /** + * @return The last time the Join Selectivity statistics were updated for the Rya instance. + */ + public Optional<Date> getLastUpdated() { + return lastUpdated; + } + + @Override + public int hashCode() { + return Objects.hash( lastUpdated ); + } + + @Override + public boolean equals(final Object obj) { + if(this == obj) { + return true; + } + if(obj instanceof JoinSelectivityDetails) { + final JoinSelectivityDetails details = (JoinSelectivityDetails) obj; + return Objects.equals(lastUpdated, details.lastUpdated); + } + return false; + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9bdbbf5d/common/rya.api/src/main/java/mvm/rya/api/instance/RyaDetailsRepository.java ---------------------------------------------------------------------- diff --git a/common/rya.api/src/main/java/mvm/rya/api/instance/RyaDetailsRepository.java b/common/rya.api/src/main/java/mvm/rya/api/instance/RyaDetailsRepository.java new file mode 100644 index 0000000..d05c65a --- /dev/null +++ b/common/rya.api/src/main/java/mvm/rya/api/instance/RyaDetailsRepository.java @@ -0,0 +1,123 @@ +package mvm.rya.api.instance; + +/* + * 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. + */ + +import javax.annotation.ParametersAreNonnullByDefault; + +/** + * Provides access to the {@link RyaDetails} information that describes + * an instance of Rya. + */ +@ParametersAreNonnullByDefault +public interface RyaDetailsRepository { + + /** + * Check whether the details for this instance of Rya have been initialized or not. + * + * @return {@code true} if it has been initialized; otherwise {@code false}. + * @throws RyaDetailsRepositoryException Something caused this operation to fail. + */ + public boolean isInitialized() throws RyaDetailsRepositoryException; + + /** + * Initializes the {@link RyaDetails} that is stored for an instance of Rya. + * + * @param details - A Rya instance's details at installation time. (not null) + * @throws AlreadyInitializedException This repository has already been initialized. + * @throws RyaDetailsRepositoryException Something caused this operation to fail. + */ + public void initialize(RyaDetails details) throws AlreadyInitializedException, RyaDetailsRepositoryException; + + /** + * Get the {@link RyaDetails} that describe this instance of Rya. + * + * @return The details that describe this instance of Rya. + * @throws NotInitializedException The Rya instance's details have not been initialized yet. + * @throws RyaDetailsRepositoryException Something caused this operation to fail. + */ + public RyaDetails getRyaInstanceDetails() throws NotInitializedException, RyaDetailsRepositoryException; + + /** + * Update the {@link RyaDetails} that describe this instance of Rya. + * + * @param oldDetails - The copy of the details that have been updated. (not null) + * @param newDetails - The updated details. (not null) + * @throws NotInitializedException The Rya instance's details have not been initialized yet. + * @throws ConcurrentUpdateException An update couldn't be performed because + * the old state of the object no longer matches the current state. + * @throws RyaDetailsRepositoryException Something caused this operation to fail. + */ + public void update(RyaDetails oldDetails, RyaDetails newDetails) throws NotInitializedException, ConcurrentUpdateException, RyaDetailsRepositoryException; + + /** + * The root exception of all {@link RyaDetailsRepository} operations. + */ + public static class RyaDetailsRepositoryException extends Exception { + private static final long serialVersionUID = 1L; + + public RyaDetailsRepositoryException(final String message) { + super(message); + } + + public RyaDetailsRepositoryException(final String message, final Throwable cause) { + super(message, cause); + } + } + + /** + * You can not initialize a {@link RyaDetailsRepository} that has already + * been initialized. + */ + public static class AlreadyInitializedException extends RyaDetailsRepositoryException { + private static final long serialVersionUID = 1L; + + public AlreadyInitializedException(final String message) { + super(message); + } + } + + /** + * Some methods of {@link RyaDetailsRepository} may only be invoked after + * it has been initialized. + */ + public static class NotInitializedException extends RyaDetailsRepositoryException { + private static final long serialVersionUID = 1L; + + public NotInitializedException(final String message) { + super(message); + } + + public NotInitializedException(final String message, final Throwable cause) { + super(message, cause); + } + } + + /** + * An update couldn't be performed because the old state of the object no + * longer matches the current state. + */ + public static class ConcurrentUpdateException extends RyaDetailsRepositoryException { + private static final long serialVersionUID = 1L; + + public ConcurrentUpdateException(final String message) { + super(message); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9bdbbf5d/common/rya.api/src/test/java/mvm/rya/api/instance/RyaDetailsTest.java ---------------------------------------------------------------------- diff --git a/common/rya.api/src/test/java/mvm/rya/api/instance/RyaDetailsTest.java b/common/rya.api/src/test/java/mvm/rya/api/instance/RyaDetailsTest.java new file mode 100644 index 0000000..c629fb6 --- /dev/null +++ b/common/rya.api/src/test/java/mvm/rya/api/instance/RyaDetailsTest.java @@ -0,0 +1,149 @@ +package mvm.rya.api.instance; + +/* + * 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. + */ + +import static org.junit.Assert.assertEquals; + +import java.util.Date; + +import org.junit.Test; + +import com.google.common.base.Optional; + +import mvm.rya.api.instance.RyaDetails.EntityCentricIndexDetails; +import mvm.rya.api.instance.RyaDetails.FreeTextIndexDetails; +import mvm.rya.api.instance.RyaDetails.GeoIndexDetails; +import mvm.rya.api.instance.RyaDetails.JoinSelectivityDetails; +import mvm.rya.api.instance.RyaDetails.PCJIndexDetails; +import mvm.rya.api.instance.RyaDetails.PCJIndexDetails.FluoDetails; +import mvm.rya.api.instance.RyaDetails.PCJIndexDetails.PCJDetails; +import mvm.rya.api.instance.RyaDetails.PCJIndexDetails.PCJDetails.PCJUpdateStrategy; +import mvm.rya.api.instance.RyaDetails.ProspectorDetails; +import mvm.rya.api.instance.RyaDetails.TemporalIndexDetails; + +/** + * Tests the methods of {@link RyaDetails}. + */ +public class RyaDetailsTest { + + @Test + public void equals() { + final RyaDetails.Builder builder = RyaDetails.builder(); + + builder.setRyaInstanceName("test_instance") + .setRyaVersion("1.2.3.4") + .setEntityCentricIndexDetails( new EntityCentricIndexDetails(true) ) + .setGeoIndexDetails( new GeoIndexDetails(true) ) + .setTemporalIndexDetails( new TemporalIndexDetails(true) ) + .setFreeTextDetails( new FreeTextIndexDetails(true) ) + .setPCJIndexDetails( + PCJIndexDetails.builder() + .setEnabled(true) + .setFluoDetails( new FluoDetails("test_instance_rya_pcj_updater") ) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 1") + .setUpdateStrategy(PCJUpdateStrategy.BATCH) + .setLastUpdateTime( new Date() ) + .build()) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 2") + .setUpdateStrategy(PCJUpdateStrategy.INCREMENTAL) + .build()) + .build()) + .setProspectorDetails( new ProspectorDetails(Optional.of(new Date())) ) + .setJoinSelectivityDetails( new JoinSelectivityDetails(Optional.of(new Date())) ); + + final RyaDetails details1 = builder.build(); + final RyaDetails details2 = builder.build(); + assertEquals(details1, details2); + } + + @Test + public void hashcode() { + final RyaDetails.Builder builder = RyaDetails.builder(); + + builder.setRyaInstanceName("test_instance") + .setRyaVersion("1.2.3.4") + .setEntityCentricIndexDetails( new EntityCentricIndexDetails(true) ) + .setGeoIndexDetails( new GeoIndexDetails(true) ) + .setTemporalIndexDetails( new TemporalIndexDetails(true) ) + .setFreeTextDetails( new FreeTextIndexDetails(true) ) + .setPCJIndexDetails( + PCJIndexDetails.builder() + .setEnabled(true) + .setFluoDetails( new FluoDetails("test_instance_rya_pcj_updater") ) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 1") + .setUpdateStrategy(PCJUpdateStrategy.BATCH) + .setLastUpdateTime( new Date() ) + .build()) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 2") + .setUpdateStrategy(PCJUpdateStrategy.INCREMENTAL) + .build()) + .build()) + .setProspectorDetails( new ProspectorDetails(Optional.of(new Date())) ) + .setJoinSelectivityDetails( new JoinSelectivityDetails(Optional.of(new Date())) ); + + final RyaDetails details1 = builder.build(); + final RyaDetails details2 = builder.build(); + assertEquals(details1.hashCode(), details2.hashCode()); + } + + @Test + public void constructor() { + final RyaDetails originalDetails = RyaDetails.builder() + .setRyaInstanceName("test_instance") + .setRyaVersion("1.2.3.4") + .setEntityCentricIndexDetails( new EntityCentricIndexDetails(true) ) + .setGeoIndexDetails( new GeoIndexDetails(true) ) + .setTemporalIndexDetails( new TemporalIndexDetails(true) ) + .setFreeTextDetails( new FreeTextIndexDetails(true) ) + .setPCJIndexDetails( + PCJIndexDetails.builder() + .setEnabled(true) + .setFluoDetails( new FluoDetails("test_instance_rya_pcj_updater") ) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 1") + .setUpdateStrategy(PCJUpdateStrategy.BATCH) + .setLastUpdateTime( new Date() ) + .build()) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 2") + .setUpdateStrategy(PCJUpdateStrategy.INCREMENTAL) + .build()) + .build()) + .setProspectorDetails( new ProspectorDetails(Optional.of(new Date())) ) + .setJoinSelectivityDetails( new JoinSelectivityDetails(Optional.of(new Date())) ) + .build(); + + // Create a new Builder using another RyaDetails object. + final RyaDetails.Builder builder = new RyaDetails.Builder( originalDetails ); + + // Show it builds the object that was passed into it. + assertEquals(originalDetails, builder.build()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9bdbbf5d/dao/accumulo.rya/pom.xml ---------------------------------------------------------------------- diff --git a/dao/accumulo.rya/pom.xml b/dao/accumulo.rya/pom.xml index 295b755..1fa6f55 100644 --- a/dao/accumulo.rya/pom.xml +++ b/dao/accumulo.rya/pom.xml @@ -36,15 +36,105 @@ under the License. <artifactId>rya.api</artifactId> </dependency> + <!-- Accumulo deps --> <dependency> <groupId>org.apache.accumulo</groupId> <artifactId>accumulo-core</artifactId> </dependency> <dependency> + <groupId>org.openrdf.sesame</groupId> + <artifactId>sesame-rio-ntriples</artifactId> + </dependency> + <dependency> + <groupId>org.openrdf.sesame</groupId> + <artifactId>sesame-rio-nquads</artifactId> + </dependency> + <dependency> + <groupId>org.openrdf.sesame</groupId> + <artifactId>sesame-queryalgebra-evaluation</artifactId> + </dependency> + + <dependency> + <groupId>org.openrdf.sesame</groupId> + <artifactId>sesame-rio-trig</artifactId> + <scope>test</scope> + </dependency> + + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.mrunit</groupId> + <artifactId>mrunit</artifactId> + <classifier>hadoop2</classifier> + <version>1.1.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.accumulo</groupId> + <artifactId>accumulo-minicluster</artifactId> + <version>${accumulo.version}</version> + <scope>test</scope> + </dependency> </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-failsafe-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>integration-test</goal> + <goal>verify</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.rat</groupId> + <artifactId>apache-rat-plugin</artifactId> + <configuration> + <excludes> + <!-- RDF data Files --> + <exclude>**/*.ntriples</exclude> + <exclude>**/*.trig</exclude> + </excludes> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> + + <profiles> + <profile> + <id>mr</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <executions> + <execution> + <configuration> + <transformers> + <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" /> + </transformers> + </configuration> + </execution> + </executions> + + </plugin> + </plugins> + </build> + </profile> + </profiles> </project> http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9bdbbf5d/dao/accumulo.rya/src/main/java/mvm/rya/accumulo/instance/AccumuloRyaInstanceDetailsRepository.java ---------------------------------------------------------------------- diff --git a/dao/accumulo.rya/src/main/java/mvm/rya/accumulo/instance/AccumuloRyaInstanceDetailsRepository.java b/dao/accumulo.rya/src/main/java/mvm/rya/accumulo/instance/AccumuloRyaInstanceDetailsRepository.java new file mode 100644 index 0000000..ec9f266 --- /dev/null +++ b/dao/accumulo.rya/src/main/java/mvm/rya/accumulo/instance/AccumuloRyaInstanceDetailsRepository.java @@ -0,0 +1,229 @@ +package mvm.rya.accumulo.instance; + +/* + * 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. + */ + +import static java.util.Objects.requireNonNull; + +import java.util.Map.Entry; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.BatchWriter; +import org.apache.accumulo.core.client.BatchWriterConfig; +import org.apache.accumulo.core.client.ConditionalWriter; +import org.apache.accumulo.core.client.ConditionalWriter.Result; +import org.apache.accumulo.core.client.ConditionalWriterConfig; +import org.apache.accumulo.core.client.Connector; +import org.apache.accumulo.core.client.MutationsRejectedException; +import org.apache.accumulo.core.client.Scanner; +import org.apache.accumulo.core.client.TableExistsException; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.admin.TableOperations; +import org.apache.accumulo.core.data.Condition; +import org.apache.accumulo.core.data.ConditionalMutation; +import org.apache.accumulo.core.data.Key; +import org.apache.accumulo.core.data.Mutation; +import org.apache.accumulo.core.data.Value; +import org.apache.accumulo.core.security.Authorizations; +import org.apache.hadoop.io.Text; + +import mvm.rya.api.instance.RyaDetails; +import mvm.rya.api.instance.RyaDetailsRepository; + +/** + * An implementation of {@link RyaDetailsRepository} that stores a Rya + * instance's {@link RyaDetails} in an Accumulo table. + * </p> + * XXX + * This implementation writes the details object as a serialized byte array to + * a row in Accumulo. Storing the entire structure within a single value is + * attractive because Accumulo's conditional writer will let us do checkAndSet + * style operations to synchronize writes to the object. On the downside, only + * Java clients will work. + */ +@ParametersAreNonnullByDefault +public class AccumuloRyaInstanceDetailsRepository implements RyaDetailsRepository { + + private static final String INSTANCE_DETAILS_TABLE_NAME = "instance_details"; + + private static final Text ROW_ID = new Text("instance metadata"); + private static final Text COL_FAMILY = new Text("instance"); + private static final Text COL_QUALIFIER = new Text("details"); + + private final RyaDetailsSerializer serializer = new RyaDetailsSerializer(); + + private final Connector connector; + private final String instanceName; + private final String detailsTableName; + + + /** + * Constructs an instance of {@link AccumuloRyaInstanceDetailsRepository}. + * + * @param connector - Connects to the instance of Accumulo that hosts the Rya instance. (not null) + * @param instanceName - The name of the Rya instance this repository represents. (not null) + */ + public AccumuloRyaInstanceDetailsRepository(final Connector connector, final String instanceName) { + this.connector = requireNonNull( connector ); + this.instanceName = requireNonNull( instanceName ); + this.detailsTableName = instanceName + INSTANCE_DETAILS_TABLE_NAME; + } + + @Override + public boolean isInitialized() throws RyaDetailsRepositoryException { + try { + final Scanner scanner = connector.createScanner(detailsTableName, new Authorizations()); + scanner.fetchColumn(COL_FAMILY, COL_QUALIFIER); + return scanner.iterator().hasNext(); + } catch (final TableNotFoundException e) { + return false; + } + } + + @Override + public void initialize(final RyaDetails details) throws AlreadyInitializedException, RyaDetailsRepositoryException { + // Preconditions. + requireNonNull( details ); + + if(!details.getRyaInstanceName().equals( instanceName )) { + throw new RyaDetailsRepositoryException("The instance name that was in the provided 'details' does not match " + + "the instance name that this repository is connected to. Make sure you're connected to the" + + "correct Rya instance."); + } + + if(isInitialized()) { + throw new AlreadyInitializedException("The repository has already been initialized for the Rya instance named '" + + instanceName + "'."); + } + + // Create the table that hosts the details if it has not been created yet. + final TableOperations tableOps = connector.tableOperations(); + if(!tableOps.exists(detailsTableName)) { + try { + tableOps.create(detailsTableName); + } catch (AccumuloException | AccumuloSecurityException | TableExistsException e) { + throw new RyaDetailsRepositoryException("Could not initialize the Rya instance details for the instance named '" + + instanceName + "' because the the table that holds that information could not be created."); + } + } + + // Write the details to the table. + BatchWriter writer = null; + try { + writer = connector.createBatchWriter(detailsTableName, new BatchWriterConfig()); + + final byte[] bytes = serializer.serialize(details); + final Mutation mutation = new Mutation(ROW_ID); + mutation.put(COL_FAMILY, COL_QUALIFIER, new Value(bytes)); + writer.addMutation( mutation ); + + } catch (final TableNotFoundException | MutationsRejectedException e) { + throw new RyaDetailsRepositoryException("Could not initialize the Rya instance details for the instance named '" + instanceName + "'.", e); + } finally { + if(writer != null) { + try { + writer.close(); + } catch (final MutationsRejectedException e) { + throw new RyaDetailsRepositoryException("", e); + } + } + } + } + + @Override + public RyaDetails getRyaInstanceDetails() throws NotInitializedException, RyaDetailsRepositoryException { + // Preconditions. + if(!isInitialized()) { + throw new NotInitializedException("Could not fetch the details for the Rya instanced named '" + + instanceName + "' because it has not been initialized yet."); + } + + // Read it from the table. + try { + // Fetch the value from the table. + final Scanner scanner = connector.createScanner(detailsTableName, new Authorizations()); + scanner.fetchColumn(COL_FAMILY, COL_QUALIFIER); + final Entry<Key, Value> entry = scanner.iterator().next(); + + // Deserialize it. + final byte[] bytes = entry.getValue().get(); + return serializer.deserialize( bytes ); + + } catch (final TableNotFoundException e) { + throw new RyaDetailsRepositoryException("Could not get the details from the table.", e); + } + } + + @Override + public void update(final RyaDetails oldDetails, final RyaDetails newDetails) + throws NotInitializedException, ConcurrentUpdateException, RyaDetailsRepositoryException { + // Preconditions. + requireNonNull(oldDetails); + requireNonNull(newDetails); + + if(!newDetails.getRyaInstanceName().equals( instanceName )) { + throw new RyaDetailsRepositoryException("The instance name that was in the provided 'newDetails' does not match " + + "the instance name that this repository is connected to. Make sure you're connected to the" + + "correct Rya instance."); + } + + if(!isInitialized()) { + throw new NotInitializedException("Could not update the details for the Rya instanced named '" + + instanceName + "' because it has not been initialized yet."); + } + + // Use a conditional writer so that we can detect when the old details + // are no longer the currently stored ones. + ConditionalWriter writer = null; + try { + // Setup the condition that ensures the details have not changed since the edits were made. + final byte[] oldDetailsBytes = serializer.serialize(oldDetails); + final Condition condition = new Condition(COL_FAMILY, COL_QUALIFIER); + condition.setValue( oldDetailsBytes ); + + // Create the mutation that only performs the update if the details haven't changed. + final ConditionalMutation mutation = new ConditionalMutation(ROW_ID); + mutation.addCondition( condition ); + final byte[] newDetailsBytes = serializer.serialize(newDetails); + mutation.put(COL_FAMILY, COL_QUALIFIER, new Value(newDetailsBytes)); + + // Do the write. + writer = connector.createConditionalWriter(detailsTableName, new ConditionalWriterConfig()); + final Result result = writer.write(mutation); + switch(result.getStatus()) { + case REJECTED: + case VIOLATED: + throw new ConcurrentUpdateException("Could not update the details for the Rya instance named '" + + instanceName + "' because the old value is out of date."); + case UNKNOWN: + case INVISIBLE_VISIBILITY: + throw new RyaDetailsRepositoryException("Could not update the details for the Rya instance named '" + instanceName + "'."); + } + } catch (final TableNotFoundException | AccumuloException | AccumuloSecurityException e) { + throw new RyaDetailsRepositoryException("Could not update the details for the Rya instance named '" + instanceName + "'."); + } finally { + if(writer != null) { + writer.close(); + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9bdbbf5d/dao/accumulo.rya/src/main/java/mvm/rya/accumulo/instance/RyaDetailsSerializer.java ---------------------------------------------------------------------- diff --git a/dao/accumulo.rya/src/main/java/mvm/rya/accumulo/instance/RyaDetailsSerializer.java b/dao/accumulo.rya/src/main/java/mvm/rya/accumulo/instance/RyaDetailsSerializer.java new file mode 100644 index 0000000..8c863ea --- /dev/null +++ b/dao/accumulo.rya/src/main/java/mvm/rya/accumulo/instance/RyaDetailsSerializer.java @@ -0,0 +1,96 @@ +package mvm.rya.accumulo.instance; + +/* + * 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. + */ + +import static java.util.Objects.requireNonNull; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import javax.annotation.ParametersAreNonnullByDefault; + +import mvm.rya.api.instance.RyaDetails; +import mvm.rya.api.instance.RyaDetailsRepository.RyaDetailsRepositoryException; + +/** + * Serializes {@link RyaDetails} instances. + */ +@ParametersAreNonnullByDefault +public class RyaDetailsSerializer { + + /** + * Serializes an instance of {@link RyaDetails}. + * + * @param details - The details that will be serialized. (not null) + * @return The serialized details. + */ + public byte[] serialize(final RyaDetails details) throws SerializationException { + requireNonNull(details); + + try { + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + new ObjectOutputStream(stream).writeObject( details ); + return stream.toByteArray(); + } catch (final IOException e) { + throw new SerializationException("Could not serialize an instance of RyaDetails.", e); + } + } + + /** + * Deserializes an instance of {@link RyaDetails}. + * + * @param bytes - The serialized for of a {@link RyaDetails}. (not null) + * @return The deserialized object. + */ + public RyaDetails deserialize(final byte[] bytes) throws SerializationException { + requireNonNull(bytes); + + try { + final ByteArrayInputStream stream = new ByteArrayInputStream( bytes ); + final Object o = new ObjectInputStream( stream ).readObject(); + + if(! (o instanceof RyaDetails) ) { + throw new SerializationException("Wrong type of object was deserialized. Class: " + o.getClass().getName() ); + } + + return (RyaDetails) o; + } catch (final ClassNotFoundException | IOException e) { + throw new SerializationException("Could not deserialize an instance of RyaDetails.", e); + } + } + + /** + * Could not serialize an instance of {@link RyaDetails}. + */ + public static class SerializationException extends RyaDetailsRepositoryException { + private static final long serialVersionUID = 1L; + + public SerializationException(final String message) { + super(message); + } + + public SerializationException(final String message, final Throwable cause) { + super(message, cause); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-rya/blob/9bdbbf5d/dao/accumulo.rya/src/test/java/mvm/rya/accumulo/instance/AccumuloRyaDetailsRepositoryIT.java ---------------------------------------------------------------------- diff --git a/dao/accumulo.rya/src/test/java/mvm/rya/accumulo/instance/AccumuloRyaDetailsRepositoryIT.java b/dao/accumulo.rya/src/test/java/mvm/rya/accumulo/instance/AccumuloRyaDetailsRepositoryIT.java new file mode 100644 index 0000000..8df8a9b --- /dev/null +++ b/dao/accumulo.rya/src/test/java/mvm/rya/accumulo/instance/AccumuloRyaDetailsRepositoryIT.java @@ -0,0 +1,368 @@ +package mvm.rya.accumulo.instance; + +/* + * 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. + */ + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.apache.accumulo.core.client.AccumuloException; +import org.apache.accumulo.core.client.AccumuloSecurityException; +import org.apache.accumulo.core.client.Connector; +import org.apache.accumulo.core.client.Instance; +import org.apache.accumulo.core.client.TableNotFoundException; +import org.apache.accumulo.core.client.ZooKeeperInstance; +import org.apache.accumulo.core.client.admin.TableOperations; +import org.apache.accumulo.core.client.mock.MockInstance; +import org.apache.accumulo.core.client.security.tokens.PasswordToken; +import org.apache.accumulo.minicluster.MiniAccumuloCluster; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.common.base.Optional; + +import mvm.rya.api.instance.RyaDetails; +import mvm.rya.api.instance.RyaDetails.EntityCentricIndexDetails; +import mvm.rya.api.instance.RyaDetails.FreeTextIndexDetails; +import mvm.rya.api.instance.RyaDetails.GeoIndexDetails; +import mvm.rya.api.instance.RyaDetails.JoinSelectivityDetails; +import mvm.rya.api.instance.RyaDetails.PCJIndexDetails; +import mvm.rya.api.instance.RyaDetails.PCJIndexDetails.FluoDetails; +import mvm.rya.api.instance.RyaDetails.PCJIndexDetails.PCJDetails; +import mvm.rya.api.instance.RyaDetails.PCJIndexDetails.PCJDetails.PCJUpdateStrategy; +import mvm.rya.api.instance.RyaDetails.ProspectorDetails; +import mvm.rya.api.instance.RyaDetails.TemporalIndexDetails; +import mvm.rya.api.instance.RyaDetailsRepository; +import mvm.rya.api.instance.RyaDetailsRepository.AlreadyInitializedException; +import mvm.rya.api.instance.RyaDetailsRepository.ConcurrentUpdateException; +import mvm.rya.api.instance.RyaDetailsRepository.NotInitializedException; +import mvm.rya.api.instance.RyaDetailsRepository.RyaDetailsRepositoryException; + +/** + * Tests the methods of {@link AccumuloRyaDetailsRepository} by using a {@link MiniAccumuloCluster}. + */ +public class AccumuloRyaDetailsRepositoryIT { + + private static MiniAccumuloCluster cluster = null; + private static List<String> originalTableNames = new ArrayList<>(); + + @BeforeClass + public static void startMiniAccumulo() throws IOException, InterruptedException, AccumuloException, AccumuloSecurityException { + // Setup the mini cluster. + final File tempDirectory = Files.createTempDirectory("testDir").toFile(); + cluster = new MiniAccumuloCluster(tempDirectory, "password"); + cluster.start(); + + // Store a list of the original table names. + final Instance instance = new ZooKeeperInstance(cluster.getInstanceName(), cluster.getZooKeepers()); + final Connector connector = instance.getConnector("root", new PasswordToken("password")); + originalTableNames.addAll( connector.tableOperations().list() ); + } + + @Before + public void clearLastTest() throws AccumuloException, AccumuloSecurityException, TableNotFoundException { + // Get a list of the tables that have been added by the test. + final Instance instance = new ZooKeeperInstance(cluster.getInstanceName(), cluster.getZooKeepers()); + final Connector connector = instance.getConnector("root", new PasswordToken("password")); + final TableOperations tableOps = connector.tableOperations(); + + final List<String> newTables = new ArrayList<>(); + newTables.addAll( tableOps.list() ); + newTables.removeAll( originalTableNames ); + + // Delete all the new tables. + for(final String newTable : newTables) { + tableOps.delete( newTable ); + } + } + + @AfterClass + public static void stopMiniAccumulo() throws IOException, InterruptedException { + cluster.stop(); + } + + @Test + public void initializeAndGet() throws AccumuloException, AccumuloSecurityException, AlreadyInitializedException, RyaDetailsRepositoryException { + final String instanceName = "testInstance"; + + // Create the metadata object the repository will be initialized with. + final RyaDetails details = RyaDetails.builder() + .setRyaInstanceName(instanceName) + .setRyaVersion("1.2.3.4") + .setEntityCentricIndexDetails( new EntityCentricIndexDetails(true) ) + .setGeoIndexDetails( new GeoIndexDetails(true) ) + .setTemporalIndexDetails( new TemporalIndexDetails(true) ) + .setFreeTextDetails( new FreeTextIndexDetails(true) ) + .setPCJIndexDetails( + PCJIndexDetails.builder() + .setEnabled(true) + .setFluoDetails( new FluoDetails("test_instance_rya_pcj_updater") ) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 1") + .setUpdateStrategy(PCJUpdateStrategy.BATCH) + .setLastUpdateTime( new Date() ) + .build()) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 2") + .setUpdateStrategy(PCJUpdateStrategy.INCREMENTAL) + .build()) + .build()) + .setProspectorDetails( new ProspectorDetails(Optional.of(new Date())) ) + .setJoinSelectivityDetails( new JoinSelectivityDetails(Optional.of(new Date())) ) + .build(); + + // Setup the repository that will be tested using a mock instance of Accumulo. + final Instance instance = new ZooKeeperInstance(cluster.getInstanceName(), cluster.getZooKeepers()); + final Connector connector = instance.getConnector("root", new PasswordToken("password")); + final RyaDetailsRepository repo = new AccumuloRyaInstanceDetailsRepository(connector, instanceName); + + // Initialize the repository + repo.initialize(details); + + // Fetch the stored details. + final RyaDetails stored = repo.getRyaInstanceDetails(); + + // Ensure the fetched object is equivalent to what was stored. + assertEquals(details, stored); + } + + @Test(expected = AlreadyInitializedException.class) + public void initialize_alreadyInitialized() throws AlreadyInitializedException, RyaDetailsRepositoryException, AccumuloException, AccumuloSecurityException { + final String instanceName = "testInstance"; + + // Create the metadata object the repository will be initialized with. + final RyaDetails details = RyaDetails.builder() + .setRyaInstanceName(instanceName) + .setRyaVersion("1.2.3.4") + .setEntityCentricIndexDetails( new EntityCentricIndexDetails(true) ) + .setGeoIndexDetails( new GeoIndexDetails(true) ) + .setTemporalIndexDetails( new TemporalIndexDetails(true) ) + .setFreeTextDetails( new FreeTextIndexDetails(true) ) + .setPCJIndexDetails( + PCJIndexDetails.builder() + .setEnabled(true) + .setFluoDetails( new FluoDetails("test_instance_rya_pcj_updater") ) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 1") + .setUpdateStrategy(PCJUpdateStrategy.BATCH) + .setLastUpdateTime( new Date() ) + .build()) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 2") + .setUpdateStrategy(PCJUpdateStrategy.INCREMENTAL) + .build()) + .build()) + .setProspectorDetails( new ProspectorDetails(Optional.of(new Date())) ) + .setJoinSelectivityDetails( new JoinSelectivityDetails(Optional.of(new Date())) ) + .build(); + + // Setup the repository that will be tested using a mock instance of Accumulo. + final Instance instance = new ZooKeeperInstance(cluster.getInstanceName(), cluster.getZooKeepers()); + final Connector connector = instance.getConnector("root", new PasswordToken("password")); + final RyaDetailsRepository repo = new AccumuloRyaInstanceDetailsRepository(connector, instanceName); + + // Initialize the repository + repo.initialize(details); + + // Initialize it again. + repo.initialize(details); + } + + @Test(expected = NotInitializedException.class) + public void getRyaInstance_notInitialized() throws AccumuloException, AccumuloSecurityException, NotInitializedException, RyaDetailsRepositoryException { + // Setup the repository that will be tested using a mock instance of Accumulo. + final Instance instance = new MockInstance(); + final Connector connector = instance.getConnector("username", new PasswordToken("password")); + final RyaDetailsRepository repo = new AccumuloRyaInstanceDetailsRepository(connector, "testInstance"); + + // Try to fetch the details from the uninitialized repository. + repo.getRyaInstanceDetails(); + } + + @Test + public void isInitialized_true() throws AccumuloException, AccumuloSecurityException, AlreadyInitializedException, RyaDetailsRepositoryException { + final String instanceName = "testInstance"; + + // Create the metadata object the repository will be initialized with. + final RyaDetails details = RyaDetails.builder() + .setRyaInstanceName(instanceName) + .setRyaVersion("1.2.3.4") + .setEntityCentricIndexDetails( new EntityCentricIndexDetails(true) ) + .setGeoIndexDetails( new GeoIndexDetails(true) ) + .setTemporalIndexDetails( new TemporalIndexDetails(true) ) + .setFreeTextDetails( new FreeTextIndexDetails(true) ) + .setPCJIndexDetails( + PCJIndexDetails.builder() + .setEnabled(true) + .setFluoDetails( new FluoDetails("test_instance_rya_pcj_updater") ) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 1") + .setUpdateStrategy(PCJUpdateStrategy.BATCH) + .setLastUpdateTime( new Date() ) + .build()) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 2") + .setUpdateStrategy(PCJUpdateStrategy.INCREMENTAL) + .build()) + .build()) + .setProspectorDetails( new ProspectorDetails(Optional.of(new Date())) ) + .setJoinSelectivityDetails( new JoinSelectivityDetails(Optional.of(new Date())) ) + .build(); + + // Setup the repository that will be tested using a mock instance of Accumulo. + final Instance instance = new ZooKeeperInstance(cluster.getInstanceName(), cluster.getZooKeepers()); + final Connector connector = instance.getConnector("root", new PasswordToken("password")); + final RyaDetailsRepository repo = new AccumuloRyaInstanceDetailsRepository(connector, "testInstance"); + + // Initialize the repository + repo.initialize(details); + + // Ensure the repository reports that it has been initialized. + assertTrue( repo.isInitialized() ); + } + + @Test + public void isInitialized_false() throws AccumuloException, AccumuloSecurityException, RyaDetailsRepositoryException { + // Setup the repository that will be tested using a mock instance of Accumulo. + final Instance instance = new ZooKeeperInstance(cluster.getInstanceName(), cluster.getZooKeepers()); + final Connector connector = instance.getConnector("root", new PasswordToken("password")); + final RyaDetailsRepository repo = new AccumuloRyaInstanceDetailsRepository(connector, "testInstance"); + + // Ensure the repository reports that is has not been initialized. + assertFalse( repo.isInitialized() ); + } + + @Test + public void update() throws AlreadyInitializedException, RyaDetailsRepositoryException, AccumuloException, AccumuloSecurityException { + final String instanceName = "testInstance"; + + // Create the metadata object the repository will be initialized with. + final RyaDetails details = RyaDetails.builder() + .setRyaInstanceName(instanceName) + .setRyaVersion("1.2.3.4") + .setEntityCentricIndexDetails( new EntityCentricIndexDetails(true) ) + .setGeoIndexDetails( new GeoIndexDetails(true) ) + .setTemporalIndexDetails( new TemporalIndexDetails(true) ) + .setFreeTextDetails( new FreeTextIndexDetails(true) ) + .setPCJIndexDetails( + PCJIndexDetails.builder() + .setEnabled(true) + .setFluoDetails( new FluoDetails("test_instance_rya_pcj_updater") ) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 1") + .setUpdateStrategy(PCJUpdateStrategy.BATCH) + .setLastUpdateTime( new Date() ) + .build()) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 2") + .setUpdateStrategy(PCJUpdateStrategy.INCREMENTAL) + .build()) + .build()) + .setProspectorDetails( new ProspectorDetails(Optional.of(new Date())) ) + .setJoinSelectivityDetails( new JoinSelectivityDetails(Optional.of(new Date())) ) + .build(); + + // Setup the repository that will be tested using a mock instance of Accumulo. + final Instance instance = new ZooKeeperInstance(cluster.getInstanceName(), cluster.getZooKeepers()); + final Connector connector = instance.getConnector("root", new PasswordToken("password")); + final RyaDetailsRepository repo = new AccumuloRyaInstanceDetailsRepository(connector, "testInstance"); + + // Initialize the repository + repo.initialize(details); + + // Create a new state for the details. + final RyaDetails updated = new RyaDetails.Builder( details ) + .setGeoIndexDetails( new GeoIndexDetails(false) ) + .build(); + + // Execute the update. + repo.update(details, updated); + + // Show the new state that is stored matches the updated state. + final RyaDetails fetched = repo.getRyaInstanceDetails(); + assertEquals(updated, fetched); + } + + @Test(expected = ConcurrentUpdateException.class) + public void update_outOfDate() throws AccumuloException, AccumuloSecurityException, AlreadyInitializedException, RyaDetailsRepositoryException { + final String instanceName = "testInstance"; + + // Create the metadata object the repository will be initialized with. + final RyaDetails details = RyaDetails.builder() + .setRyaInstanceName(instanceName) + .setRyaVersion("1.2.3.4") + .setEntityCentricIndexDetails( new EntityCentricIndexDetails(true) ) + .setGeoIndexDetails( new GeoIndexDetails(true) ) + .setTemporalIndexDetails( new TemporalIndexDetails(true) ) + .setFreeTextDetails( new FreeTextIndexDetails(true) ) + .setPCJIndexDetails( + PCJIndexDetails.builder() + .setEnabled(true) + .setFluoDetails( new FluoDetails("test_instance_rya_pcj_updater") ) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 1") + .setUpdateStrategy(PCJUpdateStrategy.BATCH) + .setLastUpdateTime( new Date() ) + .build()) + .addPCJDetails( + PCJDetails.builder() + .setId("pcj 2") + .setUpdateStrategy(PCJUpdateStrategy.INCREMENTAL) + .build()) + .build()) + .setProspectorDetails( new ProspectorDetails(Optional.of(new Date())) ) + .setJoinSelectivityDetails( new JoinSelectivityDetails(Optional.of(new Date())) ) + .build(); + + // Setup the repository that will be tested using a mock instance of Accumulo. + final Instance instance = new ZooKeeperInstance(cluster.getInstanceName(), cluster.getZooKeepers()); + final Connector connector = instance.getConnector("root", new PasswordToken("password")); + final RyaDetailsRepository repo = new AccumuloRyaInstanceDetailsRepository(connector, "testInstance"); + + // Initialize the repository + repo.initialize(details); + + // Create a new state for the details. + final RyaDetails updated = new RyaDetails.Builder( details ) + .setGeoIndexDetails( new GeoIndexDetails(false) ) + .build(); + + // Try to execute the update where the old state is not the currently stored state. + repo.update(updated, updated); + } +} \ No newline at end of file
