Java tutorial
/** * (c) Copyright 2012 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; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collection; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedSet; import org.kiji.annotations.ApiAudience; import org.kiji.annotations.ApiStability; /** * URI that uniquely identifies a Cassandra Kiji instance, table, column(s). * Currently there is no easy way to select the default URI. * * <p> * KijiURI objects can be constructed directly from parsing a URI string: * </p> * <pre><code> * final KijiURI uri = KijiURI * .newBuilder("kiji-cassandra://127.0.0.1:2181/127.0.0.1/9160/default/mytable/col") * .build(); * </code></pre> * * <p> * Alternately, CassandraKijiURI objects can be constructed from components by using the builder: * </p> * <pre><code> * final CassandraKijiURI uri = CassandraKijiURI.newBuilder() * .withZookeeperQuorum("127.0.0.1") * .withZookeeperClientPort(2181) * .withCassandraContactPoint("127.0.0.1") * .withCassandraClientPort(9042) * .withInstanceName("default") * .withTableName("mytable") * .addColumnName(new KijiColumnName(col)) * .build(); * </code></pre> * * Valid URI forms look like: * <li> "kiji://zkHost/cHost/cPort" * <li> "kiji://zkHost/(cHost1,cHost2)/cPort" * <li> "kiji://zkHost/cHost/cPort/instance" * <li> "kiji://zkHost/cHost/cPort/instance/table" * <li> "kiji://zkHost:zkPort/cHost/cPort/instance/table" * <li> "kiji://zkHost1,zkHost2/cHost/cPort/instance/table" * <li> "kiji://(zkHost1,zkHost2):zkPort/cHost/cPort/instance/table" * <li> "kiji://zkHost/cHost/cPort/instance/table/col" * <li> "kiji://zkHost/cHost/cPort/instance/table/col1,col2" */ @ApiAudience.Public @ApiStability.Stable public final class CassandraKijiURI extends KijiURI { /** URI/URL scheme used to fully qualify a Kiji table. */ public static final String KIJI_SCHEME = "kiji-cassandra"; /** Default Cassandra port. */ public static final int DEFAULT_CASSANDRA_CLIENT_PORT = 9160; /** Default Cassandra host. */ public static final String DEFAULT_CASSANDRA_HOST = "127.0.0.1"; /** * Ordered list of Cassandra cluster host names or IP addresses. * Preserves user ordering. Never null. */ private final ImmutableList<String> mCassandraNodes; /** Normalized (sorted) version of mZookeeperQuorum. Never null. */ private final ImmutableList<String> mCassandraNodesNormalized; /** Cassandra client port number. */ private final int mCassandraClientPort; /** * Constructs a new CassandraKijiURI with the given parameters. * * @param zookeeperQuorum Zookeeper quorum. * @param zookeeperClientPort Zookeeper client port. * @param cassandraNodes Addresses of Cassandra nodes to which to connect. * @param cassandraClientPort Cassandra native protocol port. * @param instanceName Instance name. * @param tableName Table name. * @param columnNames Column names. * @throws org.kiji.schema.KijiURIException If the parameters are invalid. */ private CassandraKijiURI(Iterable<String> zookeeperQuorum, int zookeeperClientPort, Iterable<String> cassandraNodes, int cassandraClientPort, String instanceName, String tableName, Iterable<KijiColumnName> columnNames) { super(zookeeperQuorum, zookeeperClientPort, instanceName, tableName, columnNames); mCassandraNodes = ImmutableList.copyOf(cassandraNodes); mCassandraNodesNormalized = ImmutableSortedSet.copyOf(mCassandraNodes).asList(); mCassandraClientPort = cassandraClientPort; } /** * Get ZooKeeper quorum for the URI. * * @param uri to parse. * @return Iterable over the ZooKeeper nodes. */ private static Iterable<String> parseZookeeperQuorum(URI uri) { final AuthorityParser parser = new AuthorityParser(uri); return parser.getZookeeperQuorum(); } /** * Get The ZooKeeper client port for the URI. * * @param uri to parse. * @return The ZooKeeper client port. */ private static int parseZookeeperClientPort(URI uri) { final AuthorityParser parser = new AuthorityParser(uri); return parser.getZookeeperClientPort(); } /** * Parse the Cassandra nodes for the URI. * * @param uri to parse. * @return The Cassandra nodes in the URI. */ private static Iterable<String> parseCassandraNodes(URI uri) { final String[] pathWithCassandraInfo = new File(uri.getPath()).toString().split("/"); // Grab the Cassandra hosts and port. if (pathWithCassandraInfo.length < 3) { throw new KijiURIException(uri.toString(), "Invalid path, expecting at least '/cassandra-hosts/cassandra-port'"); } String hosts = pathWithCassandraInfo[1]; // Check for ( and ) if (hosts.startsWith("(")) { if (!hosts.endsWith(")")) { throw new KijiURIException(uri.toString(), "Invalid Cassandra host list"); } hosts = hosts.substring(1, hosts.length() - 1); } String[] hostList = hosts.split(","); return new ImmutableList.Builder<String>().addAll(Arrays.asList(hostList)).build(); } /** * Parse the Cassandra native protocol client port from the URI. * * @param uri to parse. * @return The Cassandra native protocol port from the URI. */ private static int parseCassandraClientPort(URI uri) { final String[] pathWithCassandraInfo = new File(uri.getPath()).toString().split("/"); // Grab the Cassandra hosts and port. if (pathWithCassandraInfo.length < 3) { throw new KijiURIException(uri.toString(), "Invalid path, expecting at least '/cassandra-hosts/cassandra-port/'"); } try { return Integer.parseInt(pathWithCassandraInfo[2]); } catch (NumberFormatException nfe) { throw new KijiURIException(uri.toString(), "Could not parse Cassandra client port '" + pathWithCassandraInfo[2] + "'"); } } /** * Parse the Kiji instance name from the URI. * * @param uri to parse. * @return the Kiji instance name. */ private static String parseInstanceName(URI uri) { final String[] pathWithCassandraInfo = new File(uri.getPath()).toString().split("/"); // Copy the Kiji parts of the path final String[] path = Arrays.copyOfRange(pathWithCassandraInfo, 3, pathWithCassandraInfo.length); if (path.length > 3) { throw new KijiURIException(uri.toString(), "Invalid path, expecting '/kiji-instance/table-name/(column1, column2, ...)'"); } // Instance name: String instanceName; if (path.length >= 1) { instanceName = (path[0].equals(UNSET_URI_STRING)) ? null : path[0]; } else { instanceName = null; } return instanceName; } /** * Parse the Kiji table name from the URI. * * @param uri to parse. * @return The Kiji table name from the URI. */ private static String parseTableName(URI uri) { final String[] pathWithCassandraInfo = new File(uri.getPath()).toString().split("/"); // Copy the Kiji parts of the path final String[] path = Arrays.copyOfRange(pathWithCassandraInfo, 3, pathWithCassandraInfo.length); if (path.length > 3) { throw new KijiURIException(uri.toString(), "Invalid path, expecting '/kiji-instance/table-name/(column1, column2, ...)'"); } // Table name: String tableName; if (path.length >= 2) { tableName = (path[1].equals(UNSET_URI_STRING)) ? null : path[1]; } else { tableName = null; } return tableName; } /** * Parse all of the column names from the URI. * * @param uri to parse. * @return The Kiji column names in the URI. */ private static Iterable<KijiColumnName> parseColumnNames(URI uri) { final String[] pathWithCassandraInfo = new File(uri.getPath()).toString().split("/"); // Copy the Kiji parts of the path final String[] path = Arrays.copyOfRange(pathWithCassandraInfo, 3, pathWithCassandraInfo.length); if (path.length > 3) { throw new KijiURIException(uri.toString(), "Invalid path, expecting '/kiji-instance/table-name/(column1, column2, ...)'"); } // Columns: final ImmutableList.Builder<KijiColumnName> builder = ImmutableList.builder(); if (path.length >= 3) { if (!path[2].equals(UNSET_URI_STRING)) { String[] split = path[2].split(","); for (String name : split) { builder.add(new KijiColumnName(name)); } } } return builder.build(); } /** * Constructs a URI that fully qualifies a Cassandra-backed Kiji table. * * @param uri Kiji URI * @throws org.kiji.schema.KijiURIException if the URI is invalid. */ private CassandraKijiURI(URI uri) { this(parseZookeeperQuorum(uri), parseZookeeperClientPort(uri), parseCassandraNodes(uri), parseCassandraClientPort(uri), parseInstanceName(uri), parseTableName(uri), parseColumnNames(uri)); if (!uri.getScheme().equals(KIJI_SCHEME)) { throw new KijiURIException(uri.toString(), "URI scheme must be '" + KIJI_SCHEME + "'"); } } /** * Builder class for constructing KijiURIs. */ public static final class CassandraKijiURIBuilder extends KijiURI.KijiURIBuilder { /** * Cassandra nodes: comma-separated list of Cassandra host names or IP addresses. * Preserves user ordering. */ private ImmutableList<String> mCassandraNodes; /** Cassandra client port number. */ private int mCassandraClientPort; /** * Constructs a new builder for KijiURIs. * * @param zookeeperQuorum The initial zookeeper quorum. * @param zookeeperClientPort The initial zookeeper client port. * @param cassandraNodes The initial Cassandra nodes. * @param cassandraClientPort The Cassandra native-protocol port. * @param instanceName The initial instance name. * @param tableName The initial table name. * @param columnNames The initial column names. */ private CassandraKijiURIBuilder(Iterable<String> zookeeperQuorum, int zookeeperClientPort, Iterable<String> cassandraNodes, int cassandraClientPort, String instanceName, String tableName, Iterable<KijiColumnName> columnNames) { super(zookeeperQuorum, zookeeperClientPort, instanceName, tableName, columnNames); mCassandraNodes = ImmutableList.copyOf(cassandraNodes); mCassandraClientPort = cassandraClientPort; } /** * Constructs a new builder for KijiURIs with default values. * See {@link org.kiji.schema.CassandraKijiURI#newBuilder()} for specific values. */ private CassandraKijiURIBuilder() { super(); // Assign defaults for Cassandra nodes, ports mCassandraNodes = ImmutableList.copyOf(new String[] { DEFAULT_CASSANDRA_HOST }); mCassandraClientPort = DEFAULT_CASSANDRA_CLIENT_PORT; } /** * Configures the CassandraKijiURI with Cassandra nodes. * * @param cassandraNodes The C* nodes. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder withCassandraNodes(String[] cassandraNodes) { mCassandraNodes = ImmutableList.copyOf(cassandraNodes); return this; } /** * Configures the CassandraKijiURI with the C* client port. * * @param cassandraClientPort The port. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder withCassandraClientPort(int cassandraClientPort) { mCassandraClientPort = cassandraClientPort; return this; } /** * Configures the CassandraKijiURI with Zookeeper Quorum. * * @param zookeeperQuorum The zookeeper quorum. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder withZookeeperQuorum(String[] zookeeperQuorum) { return (CassandraKijiURIBuilder) super.withZookeeperQuorum(zookeeperQuorum); } /** * Configures the CassandraKijiURI with the Zookeeper client port. * * @param zookeeperClientPort The port. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder withZookeeperClientPort(int zookeeperClientPort) { return (CassandraKijiURIBuilder) super.withZookeeperClientPort(zookeeperClientPort); } /** * Configures the CassandraKijiURI with the Kiji instance name. * * @param instanceName The Kiji instance name. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder withInstanceName(String instanceName) { return (CassandraKijiURIBuilder) super.withInstanceName(instanceName); } /** * Configures the CassandraKijiURI with the Kiji table name. * * @param tableName The Kiji table name. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder withTableName(String tableName) { return (CassandraKijiURIBuilder) super.withTableName(tableName); } /** * Configures the CassandraKijiURI with the Kiji column names. * * @param columnNames The Kiji column names to configure. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder withColumnNames(Collection<String> columnNames) { return (CassandraKijiURIBuilder) super.withColumnNames(columnNames); } /** * Adds the column names to the CassandraKijiURI column names. * * @param columnNames The Kiji column names to add. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder addColumnNames(Collection<KijiColumnName> columnNames) { return (CassandraKijiURIBuilder) super.addColumnNames(columnNames); } /** * Adds the column name to the CassandraKijiURI column names. * * @param columnName The Kiji column name to add. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder addColumnName(KijiColumnName columnName) { return (CassandraKijiURIBuilder) super.addColumnName(columnName); } /** * Configures the CassandraKijiURI with the Kiji column names. * * @param columnNames The Kiji column names. * @return This builder instance so you may chain configuration method calls. */ public CassandraKijiURIBuilder withColumnNames(Iterable<KijiColumnName> columnNames) { return (CassandraKijiURIBuilder) super.withColumnNames(columnNames); } /** * Builds the configured CassandraKijiURI. * * @return A KijiURI. * @throws org.kiji.schema.KijiURIException If the KijiURI was configured improperly. */ public CassandraKijiURI build() { return new CassandraKijiURI(mZookeeperQuorum, mZookeeperClientPort, mCassandraNodes, mCassandraClientPort, mInstanceName, mTableName, mColumnNames); } } /** * 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(CassandraKijiURI uri) { return new CassandraKijiURIBuilder(uri.getZookeeperQuorumOrdered(), uri.getZookeeperClientPort(), uri.getCassandraNodesOrdered(), uri.getCassandraClientPort(), 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 DEFAULT_HBASE_URI. * * @param uri String specification of a Kiji URI. * @return A builder configured with uri. * @throws org.kiji.schema.KijiURIException If the uri is invalid. */ public static CassandraKijiURIBuilder newBuilder(String uri) { if (!uri.startsWith("kiji-cassandra://")) { uri = String.format("%s/%s/", KConstants.DEFAULT_CASSANDRA_URI, uri); } try { return newBuilder(new CassandraKijiURI(new URI(uri))); } catch (URISyntaxException exn) { throw new KijiURIException(uri, exn.getMessage()); } } /** * Returns the set of C* hosts (names or IPs). * * <p> Host names or IP addresses are de-duplicated and sorted. </p> * * @return the set of C* hosts (names or IPs). Never null. */ public ImmutableList<String> getCassandraNodes() { return mCassandraNodesNormalized; } /** * Returns the original user-specified list of C* hosts. * * <p> Host names are exactly as specified by the user. </p> * * @return the original user-specified list of C* hosts. Never null. */ public ImmutableList<String> getCassandraNodesOrdered() { return mCassandraNodes; } /** @return Cassandra client port. */ public int getCassandraClientPort() { return mCassandraClientPort; } /** * Resolve the path relative to this KijiURI. Returns a new instance. * * @param path The path to resolve. * @return The resolved KijiURI. * @throws KijiURIException If this KijiURI is malformed. */ 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 new CassandraKijiURI(uri); } catch (URISyntaxException e) { throw new RuntimeException(String.format( "CassandraKijiURI was incorrectly constructed (should never happen): %s", this.toString())); } catch (IllegalArgumentException e) { throw new KijiURIException(this.toString(), String.format("Path can not be resolved: %s", path)); } } /** * Returns a string representation of this URI. * * @param preserveOrdering Whether to preserve ordering of lsits in fields. * @return A string reprresentation of this URI. */ private String toString(boolean preserveOrdering) { // Remove trailing unset fields. if (!getColumns().isEmpty()) { return toStringCol(preserveOrdering); } else if (getTable() != null) { return toStringTable(preserveOrdering); } else if (getInstance() != null) { return toStringInstance(preserveOrdering); } else { return toStringAuthority(preserveOrdering); } } /** * Formats the full CassandraKijiURI up to the authority, preserving order. * * @param preserveOrdering Whether to preserve ordering. * @return Representation of this KijiURI up to the authority. */ String toStringAuthority(boolean preserveOrdering) { String zkQuorum; ImmutableList<String> zookeeperQuorum = preserveOrdering ? getZookeeperQuorumOrdered() : getZookeeperQuorum(); if (null == zookeeperQuorum) { zkQuorum = UNSET_URI_STRING; } else { if (zookeeperQuorum.size() == 1) { zkQuorum = zookeeperQuorum.get(0); } else { zkQuorum = String.format("(%s)", Joiner.on(",").join(zookeeperQuorum)); } } String cNodes; ImmutableList<String> cassandraNodes = preserveOrdering ? getCassandraNodesOrdered() : getCassandraNodes(); if (null == cassandraNodes) { cNodes = UNSET_URI_STRING; } else { if (cassandraNodes.size() == 1) { cNodes = cassandraNodes.get(0); } else { cNodes = String.format("(%s)", Joiner.on(",").join(cassandraNodes)); } } return String.format("%s://%s:%s/%s/%s/", KIJI_SCHEME, zkQuorum, getZookeeperClientPort(), cNodes, getCassandraClientPort()); } /** {@inheritDoc} */ @Override public boolean isCassandra() { return true; } }