1 /**
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19 package org.apache.hadoop.hbase.util;
20
21 import java.util.StringTokenizer;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 import org.apache.hadoop.classification.InterfaceAudience;
26 import org.apache.hadoop.classification.InterfaceStability;
27
28 /**
29 * Utility creating hbase friendly keys.
30 * Use fabricating row names or column qualifiers.
31 * <p>TODO: Add createSchemeless key, a key that doesn't care if scheme is
32 * http or https.
33 * @see Bytes#split(byte[], byte[], int)
34 */
35 @InterfaceAudience.Public
36 @InterfaceStability.Stable
37 public class Keying {
38 private static final String SCHEME = "r:";
39 private static final Pattern URI_RE_PARSER =
40 Pattern.compile("^([^:/?#]+://(?:[^/?#@]+@)?)([^:/?#]+)(.*)$");
41
42 /**
43 * Makes a key out of passed URI for use as row name or column qualifier.
44 *
45 * This method runs transforms on the passed URI so it sits better
46 * as a key (or portion-of-a-key) in hbase. The <code>host</code> portion of
47 * the URI authority is reversed so subdomains sort under their parent
48 * domain. The returned String is an opaque URI of an artificial
49 * <code>r:</code> scheme to prevent the result being considered an URI of
50 * the original scheme. Here is an example of the transform: The url
51 * <code>http://lucene.apache.org/index.html?query=something#middle<code> is
52 * returned as
53 * <code>r:http://org.apache.lucene/index.html?query=something#middle</code>
54 * The transforms are reversible. No transform is done if passed URI is
55 * not hierarchical.
56 *
57 * <p>If authority <code>userinfo</code> is present, will mess up the sort
58 * (until we do more work).</p>
59 *
60 * @param u URL to transform.
61 * @return An opaque URI of artificial 'r' scheme with host portion of URI
62 * authority reversed (if present).
63 * @see #keyToUri(String)
64 * @see <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC2396</a>
65 */
66 public static String createKey(final String u) {
67 if (u.startsWith(SCHEME)) {
68 throw new IllegalArgumentException("Starts with " + SCHEME);
69 }
70 Matcher m = getMatcher(u);
71 if (m == null || !m.matches()) {
72 // If no match, return original String.
73 return u;
74 }
75 return SCHEME + m.group(1) + reverseHostname(m.group(2)) + m.group(3);
76 }
77
78 /**
79 * Reverse the {@link #createKey(String)} transform.
80 *
81 * @param s <code>URI</code> made by {@link #createKey(String)}.
82 * @return 'Restored' URI made by reversing the {@link #createKey(String)}
83 * transform.
84 */
85 public static String keyToUri(final String s) {
86 if (!s.startsWith(SCHEME)) {
87 return s;
88 }
89 Matcher m = getMatcher(s.substring(SCHEME.length()));
90 if (m == null || !m.matches()) {
91 // If no match, return original String.
92 return s;
93 }
94 return m.group(1) + reverseHostname(m.group(2)) + m.group(3);
95 }
96
97 private static Matcher getMatcher(final String u) {
98 if (u == null || u.length() <= 0) {
99 return null;
100 }
101 return URI_RE_PARSER.matcher(u);
102 }
103
104 private static String reverseHostname(final String hostname) {
105 if (hostname == null) {
106 return "";
107 }
108 StringBuilder sb = new StringBuilder(hostname.length());
109 for (StringTokenizer st = new StringTokenizer(hostname, ".", false);
110 st.hasMoreElements();) {
111 Object next = st.nextElement();
112 if (sb.length() > 0) {
113 sb.insert(0, ".");
114 }
115 sb.insert(0, next);
116 }
117 return sb.toString();
118 }
119
120 }