Java tutorial
/** * (c) Copyright 2014 WibiData, Inc. * * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * 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.kiji.schema.cassandra; import java.net.URI; import java.net.URISyntaxException; import java.util.Collection; import java.util.List; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedSet; import org.kiji.annotations.ApiAudience; import org.kiji.annotations.ApiStability; import org.kiji.schema.InternalKijiError; import org.kiji.schema.KijiColumnName; import org.kiji.schema.KijiURI; import org.kiji.schema.KijiURIException; import org.kiji.schema.impl.KijiURIParser; import org.kiji.schema.impl.cassandra.CassandraKijiFactory; import org.kiji.schema.impl.cassandra.CassandraKijiInstaller; /** * a {@link KijiURI} that uniquely identifies a Cassandra Kiji instance, table, and column set. * * <h2>{@code CassandraKijiURI} Scheme</h2> * * The scheme for {@code CassandraKijiURI}s is {@code kiji-cassandra}. When a parsing a KijiURI with * this scheme, the resulting {@code URI} or builder will be a Cassandra specific type. * * <h2>{@code CassandraKijiURI} Cluster Identifier</h2> * * Cassandra Kiji needs a valid ZooKeeper ensemble, as well as a set of contact points in the * Cassandra cluster. The ZooKeeper ensemble is specified in the same way as the default * {@code KijiURI}. The contact points follow the ZooKeeper ensemble separated with a slash. The * contact points take one of the following forms, depending on whether one or more hosts is * specified, and whether a port is specified: * * <li> {@code host} * <li> {@code host:port} * <li> {@code host1,host2} * <li> {@code (host1,host2):port} * * <H2>{@code CassandraKijiURI} Examples</H2> * * The following are valid example {@code CassandraKijiURI}s: * * <li> {@code kiji-cassandra://zkEnsemble/contactPoint} * <li> {@code kiji-cassandra://zkEnsemble/contactPoint,contactPoint} * <li> {@code kiji-cassandra://zkEnsemble/contactPoint:9042/instance} * <li> {@code kiji-cassandra://zkEnsemble/contactPoint:9042/instance/table} * <li> {@code kiji-cassandra://zkEnsemble/contactPoint:9042/instance/table/col1,col2} * <li> {@code kiji-cassandra://zkEnsemble/(contactPoint,contactPoint):9042} * <li> {@code kiji-cassandra://zkEnsemble/(contactPoint,contactPoint):9042/instance/table} */ @ApiAudience.Framework @ApiStability.Evolving public final class CassandraKijiURI extends KijiURI { /** URI scheme used to fully qualify a Cassandra Kiji instance. */ public static final String CASSANDRA_SCHEME = "kiji-cassandra"; /** Default Cassandra contact host. */ private static final String DEFAULT_CONTACT_POINT = "127.0.0.1"; /** Default Cassandra contact port. */ private static final int DEFAULT_CONTACT_PORT = 9042; /** * Cassandra contact point host names or IP addresses. preserves user specified ordering. * Not null. */ private final ImmutableList<String> mContactPoints; /** Normalized (sorted and de-duplicated) contact point host names or IP addresses. Not null. */ private final ImmutableList<String> mContactPointsNormalized; /** Cassandra contact points port number. */ private final int mContactPort; private final String mUsername; private final String mPassword; // CSOFF: ParameterNumberCheck /** * Constructs a new CassandraKijiURI with the given parameters. * * @param scheme of the URI. * @param zookeeperQuorum Zookeeper quorum. * @param zookeeperClientPort Zookeeper client port. * @param contactPoints The host names of Cassandra contact points. * @param contactPort The port of Cassandra contact points. * @param username Optional username for Cassandra authentication. * @param password Optional password for Cassandra authentication. * @param instanceName Instance name. * @param tableName Table name. * @param columnNames Column names. * @throws KijiURIException If the parameters are invalid. */ private CassandraKijiURI(final String scheme, final Iterable<String> zookeeperQuorum, final int zookeeperClientPort, final ImmutableList<String> contactPoints, final int contactPort, final String username, final String password, final String instanceName, final String tableName, final Iterable<KijiColumnName> columnNames) { super(scheme, zookeeperQuorum, zookeeperClientPort, instanceName, tableName, columnNames); mContactPoints = contactPoints; mContactPointsNormalized = ImmutableSortedSet.copyOf(mContactPoints).asList(); mContactPort = contactPort; mUsername = username; mPassword = password; } // CSON /** * Returns the normalized (de-duplicated and sorted) Cassandra contact point hosts. * * @return the normalized Cassandra contact point hosts. */ public ImmutableList<String> getContactPoints() { return mContactPointsNormalized; } /** * Returns the original user-specified list of Cassandra contact point hosts. * * @return the user-specified Cassandra contact point hosts. */ public ImmutableList<String> getContactPointsOrdered() { return mContactPoints; } /** * Returns the Cassandra contact point port. * * @return the Cassandra contact point port. */ public int getContactPort() { return mContactPort; } /** * Returns the Cassandra username. * * @return the Cassandra username. */ public String getUsername() { return mUsername; } /** * Returns the Cassandra password. * * @return the Cassandra password. */ public String getPassword() { return mPassword; } /** * Builder class for constructing CassandraKijiURIs. */ public static final class CassandraKijiURIBuilder extends KijiURIBuilder { private ImmutableList<String> mContactPoints; private int mContactPort; private String mUsername; private String mPassword; // CSOFF: ParameterNumberCheck /** * Constructs a new builder for CassandraKijiURIs. * * @param scheme of the URI. * @param zookeeperQuorum The initial zookeeper quorum. * @param zookeeperClientPort The initial zookeeper client port. * @param contactPoints The host names of Cassandra contact points. * @param contactPort The port of Cassandra contact points. * @param username for Cassandra authentication (can be null) * @param password for Cassandra authentication (can be null) * @param instanceName The initial instance name. * @param tableName The initial table name. * @param columnNames The initial column names. */ private CassandraKijiURIBuilder(final String scheme, final Iterable<String> zookeeperQuorum, final int zookeeperClientPort, final Iterable<String> contactPoints, final int contactPort, final String username, final String password, final String instanceName, final String tableName, final Iterable<KijiColumnName> columnNames) { super(scheme, zookeeperQuorum, zookeeperClientPort, instanceName, tableName, columnNames); mContactPoints = ImmutableList.copyOf(contactPoints); mContactPort = contactPort; mUsername = username; mPassword = password; } // CSOFF /** * Constructs a new builder for CassandraKijiURIs. */ public CassandraKijiURIBuilder() { super(); mContactPoints = ImmutableList.of(DEFAULT_CONTACT_POINT); mContactPort = DEFAULT_CONTACT_PORT; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder withZookeeperQuorum(String[] zookeeperQuorum) { super.withZookeeperQuorum(zookeeperQuorum); return this; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder withZookeeperQuorum(Iterable<String> zookeeperQuorum) { super.withZookeeperQuorum(zookeeperQuorum); return this; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder withZookeeperClientPort(int zookeeperClientPort) { super.withZookeeperClientPort(zookeeperClientPort); return this; } /** * Provide a list of Cassandra contact points. These contact points will be used to initially * connect to Cassandra. * * @param contactPoints used create initial connection to Cassandra cluster. * @return this. */ public CassandraKijiURIBuilder withContactPoints(final Iterable<String> contactPoints) { mContactPoints = ImmutableList.copyOf(contactPoints); return this; } /** * Provide a port to connect to Cassandra contact points. * * @param contactPort to use when connecting to Cassandra contact points. * @return this. */ public CassandraKijiURIBuilder withContactPort(final int contactPort) { mContactPort = contactPort; return this; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder withInstanceName(String instanceName) { super.withInstanceName(instanceName); return this; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder withTableName(String tableName) { super.withTableName(tableName); return this; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder withColumnNames(Collection<String> columnNames) { super.withColumnNames(columnNames); return this; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder addColumnNames(Collection<KijiColumnName> columnNames) { super.addColumnNames(columnNames); return this; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder addColumnName(KijiColumnName columnName) { super.addColumnName(columnName); return this; } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURIBuilder withColumnNames(Iterable<KijiColumnName> columnNames) { super.withColumnNames(columnNames); return this; } /** * Builds the configured CassandraKijiURI. * * @return A CassandraKijiURI. * @throws KijiURIException If the CassandraKijiURI was configured improperly. */ @Override public CassandraKijiURI build() { return new CassandraKijiURI(mScheme, mZookeeperQuorum, mZookeeperClientPort, mContactPoints, mContactPort, mUsername, mPassword, mInstanceName, mTableName, mColumnNames); } } /** * A {@link KijiURIParser} for {@link CassandraKijiURI}s. */ public static final class CassandraKijiURIParser implements KijiURIParser { /** {@inheritDoc} */ @Override public CassandraKijiURIBuilder parse(final URI uri) { // Parse the ZooKeeper portion of the authority. final ZooKeeperAuthorityParser zooKeeperAuthorityParser = ZooKeeperAuthorityParser .getAuthorityParser(uri); // Cassandra Kiji URIs aren't strictly legal - the Cassandra "authority" is really the first // part of the path. final List<String> segments = Splitter.on('/').omitEmptyStrings().splitToList(uri.getPath()); if (segments.size() < 1) { throw new KijiURIException(uri.toString(), "Cassandra contact points must be specified."); } final String cassandraAuthority = segments.get(0); final AuthorityParser cassandraAuthorityParser = AuthorityParser.getAuthorityParser(cassandraAuthority, uri); // We currently support either neither a username nor a password, OR a username and a // password, but not a username without a password. if (null != cassandraAuthorityParser.getUsername() && null == cassandraAuthorityParser.getPassword()) { throw new KijiURIException(uri.toString(), "Cassandra Kiji URIs do not support a username without a password."); } final PathParser segmentParser = new PathParser(uri, 1); return new CassandraKijiURIBuilder(uri.getScheme(), zooKeeperAuthorityParser.getZookeeperQuorum(), zooKeeperAuthorityParser.getZookeeperClientPort(), cassandraAuthorityParser.getHosts(), cassandraAuthorityParser.getHostPort() == null ? DEFAULT_CONTACT_PORT : cassandraAuthorityParser.getHostPort(), cassandraAuthorityParser.getUsername(), cassandraAuthorityParser.getPassword(), segmentParser.getInstance(), segmentParser.getTable(), segmentParser.getColumns()); } @Override public String getName() { return CASSANDRA_SCHEME; } } /** {@inheritDoc} */ @Override protected StringBuilder appendClusterIdentifier(final StringBuilder sb, final boolean preserveOrdering) { super.appendClusterIdentifier(sb, preserveOrdering); ImmutableList<String> contactPoints = preserveOrdering ? mContactPoints : mContactPointsNormalized; if (contactPoints.size() == 1) { sb.append(contactPoints.get(0)); } else { sb.append('('); Joiner.on(',').appendTo(sb, contactPoints); sb.append(')'); } sb.append(':').append(mContactPort).append('/'); return sb; } /** {@inheritDoc} */ @Override public int hashCode() { return toString().hashCode(); } /** {@inheritDoc} */ @Override public boolean equals(Object object) { if (object == null) { return false; } return object.getClass() == this.getClass() && object.toString().equals(this.toString()); } /** * Gets a builder configured with default Kiji URI fields. * * More precisely, the following defaults are initialized: * <ul> * <li>The Zookeeper quorum and client port is taken from the Hadoop <tt>Configuration</tt></li> * <li>The Kiji instance name is set to <tt>KConstants.DEFAULT_INSTANCE_NAME</tt> * (<tt>"default"</tt>).</li> * <li>The table name and column names are explicitly left unset.</li> * </ul> * * @return A builder configured with this Kiji URI. */ public static CassandraKijiURIBuilder newBuilder() { return new CassandraKijiURIBuilder(); } /** * Gets a builder configured with a Kiji URI. * * @param uri The Kiji URI to configure the builder from. * @return A builder configured with uri. */ public static CassandraKijiURIBuilder newBuilder(KijiURI uri) { final ImmutableList<String> contactPoints; final int contactPort; final String username; final String password; if (uri instanceof CassandraKijiURI) { final CassandraKijiURI cassandraKijiURI = (CassandraKijiURI) uri; contactPoints = cassandraKijiURI.mContactPoints; contactPort = cassandraKijiURI.mContactPort; username = cassandraKijiURI.mUsername; password = cassandraKijiURI.mPassword; } else { contactPoints = ImmutableList.of(DEFAULT_CONTACT_POINT); contactPort = DEFAULT_CONTACT_PORT; username = null; password = null; } return new CassandraKijiURIBuilder(uri.getScheme(), uri.getZookeeperQuorumOrdered(), uri.getZookeeperClientPort(), contactPoints, contactPort, username, password, uri.getInstance(), uri.getTable(), uri.getColumnsOrdered()); } /** * Gets a builder configured with the Kiji URI. * * <p> The String parameter can be a relative URI (with a specified instance), in which * case it is automatically normalized relative to the default Cassandra URI. * * @param uri String specification of a Kiji URI. * @return A builder configured with uri. * @throws KijiURIException If the uri is invalid. */ public static CassandraKijiURIBuilder newBuilder(String uri) { if (!(uri.startsWith(CASSANDRA_SCHEME))) { uri = String.format("kiji-cassandra://%s/%s:%s/%s", KijiURI.ENV_URI_STRING, DEFAULT_CONTACT_POINT, DEFAULT_CONTACT_PORT, uri); } try { return new CassandraKijiURIParser().parse(new URI(uri)); } catch (URISyntaxException exn) { throw new KijiURIException(uri, exn.getMessage()); } } /** * Overridden to provide specific return type. * * {@inheritDoc} */ @Override public CassandraKijiURI resolve(String path) { try { // Without the "./", URI will assume a path containing a colon // is a new URI, for example "family:column". URI uri = new URI(toString()).resolve(String.format("./%s", path)); return newBuilder(uri.toString()).build(); } catch (URISyntaxException e) { // This should never happen throw new InternalKijiError(String.format("KijiURI was incorrectly constructed: %s.", this)); } catch (IllegalArgumentException e) { throw new KijiURIException(this.toString(), String.format("Path can not be resolved: %s", path)); } } /** {@inheritDoc} */ @Override protected CassandraKijiURIBuilder getBuilder() { return newBuilder(this); } /** {@inheritDoc} */ @Override protected CassandraKijiFactory getKijiFactory() { return new CassandraKijiFactory(); } /** {@inheritDoc} */ @Override protected CassandraKijiInstaller getKijiInstaller() { return CassandraKijiInstaller.get(); } }