mirror of
https://github.com/google/nomulus.git
synced 2025-07-01 08:43:34 +02:00
mv com/google/domain/registry google/registry
This change renames directories in preparation for the great package rename. The repository is now in a broken state because the code itself hasn't been updated. However this should ensure that git correctly preserves history for each file.
This commit is contained in:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
83
java/google/registry/util/AppEngineTimeLimiter.java
Normal file
83
java/google/registry/util/AppEngineTimeLimiter.java
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.appengine.api.ThreadManager.currentRequestThreadFactory;
|
||||
|
||||
import com.google.common.util.concurrent.SimpleTimeLimiter;
|
||||
import com.google.common.util.concurrent.TimeLimiter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.AbstractExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
* A factory for {@link TimeLimiter} instances that use request threads, which carry the namespace
|
||||
* and live only as long as the request that spawned them.
|
||||
* <p>
|
||||
* It is safe to reuse instances of this class, but there is no benefit in doing so over creating a
|
||||
* fresh instance each time.
|
||||
*/
|
||||
public class AppEngineTimeLimiter {
|
||||
|
||||
/**
|
||||
* An {@code ExecutorService} that uses a new thread for every task.
|
||||
* <p>
|
||||
* We need to use fresh threads for each request so that we can use App Engine's request threads.
|
||||
* If we cached these threads in a thread pool (and if we were executing on a backend, where there
|
||||
* is no time limit on requests) the caching would cause the thread to keep the task that opened
|
||||
* it alive even after returning an http response, and would also cause the namespace that the
|
||||
* original thread was created in to leak out to later reuses of the thread.
|
||||
* <p>
|
||||
* Since there are no cached resources, this class doesn't have to support being shutdown.
|
||||
*/
|
||||
private static class NewRequestThreadExecutorService extends AbstractExecutorService {
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
currentRequestThreadFactory().newThread(command).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Runnable> shutdownNow() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, TimeUnit unit) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public static TimeLimiter create() {
|
||||
return new SimpleTimeLimiter(new NewRequestThreadExecutorService());
|
||||
}
|
||||
}
|
33
java/google/registry/util/BUILD
Normal file
33
java/google/registry/util/BUILD
Normal file
|
@ -0,0 +1,33 @@
|
|||
package(
|
||||
default_visibility = ["//java/com/google/domain/registry:registry_project"],
|
||||
)
|
||||
|
||||
|
||||
java_library(
|
||||
name = "util",
|
||||
srcs = glob(["*.java"]),
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/cache",
|
||||
"//java/com/google/common/collect",
|
||||
"//java/com/google/common/escape",
|
||||
"//java/com/google/common/io",
|
||||
"//java/com/google/common/math",
|
||||
"//java/com/google/common/net",
|
||||
"//java/com/google/common/primitives",
|
||||
"//java/com/google/common/reflect",
|
||||
"//java/com/google/common/util/concurrent",
|
||||
"//java/com/google/domain/registry/config",
|
||||
"//third_party/java/appengine:appengine-api",
|
||||
"//third_party/java/dagger",
|
||||
"//third_party/java/icu4j",
|
||||
"//third_party/java/joda_time",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
"//third_party/java/jsr330_inject",
|
||||
"//third_party/java/objectify:objectify-v4_1",
|
||||
"//third_party/java/servlet/servlet_api",
|
||||
],
|
||||
)
|
||||
|
127
java/google/registry/util/BasicHttpSession.java
Normal file
127
java/google/registry/util/BasicHttpSession.java
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import javax.servlet.http.HttpSessionContext;
|
||||
|
||||
/** An {@link HttpSession} that only provides support for getting/setting attributes. */
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BasicHttpSession implements HttpSession {
|
||||
|
||||
private final Map<String, Object> map = new HashMap<>();
|
||||
boolean isValid = true;
|
||||
|
||||
@Override
|
||||
public long getCreationTime() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastAccessedTime() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletContext getServletContext() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(int interval) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxInactiveInterval() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpSessionContext getSessionContext() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(@Nullable String name) {
|
||||
checkValid();
|
||||
return map.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(@Nullable String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<?> getAttributeNames() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getValueNames() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(@Nullable String name, @Nullable Object value) {
|
||||
checkValid();
|
||||
map.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putValue(@Nullable String name, @Nullable Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(@Nullable String name) {
|
||||
checkValid();
|
||||
map.remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeValue(@Nullable String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
isValid = false;
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private void checkValid() {
|
||||
if (!isValid) {
|
||||
throw new IllegalStateException("This session has been invalidated.");
|
||||
}
|
||||
}
|
||||
}
|
59
java/google/registry/util/CacheUtils.java
Normal file
59
java/google/registry/util/CacheUtils.java
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Suppliers.memoizeWithExpiration;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.joda.time.Duration.ZERO;
|
||||
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/** Utility methods related to caching. */
|
||||
public class CacheUtils {
|
||||
|
||||
private static final RegistryEnvironment ENVIRONMENT = RegistryEnvironment.get();
|
||||
|
||||
/**
|
||||
* Memoize a supplier, with a short expiration specified in the environment config.
|
||||
*
|
||||
* <p>Use this for things that might change while code is running. (For example, the various
|
||||
* lists downloaded from the TMCH get updated in datastore and the caches need to be refreshed.)
|
||||
*/
|
||||
public static <T> Supplier<T> memoizeWithShortExpiration(Supplier<T> original) {
|
||||
return memoizeForDuration(original, ENVIRONMENT.config().getSingletonCacheRefreshDuration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Memoize a supplier, with a long expiration specified in the environment config.
|
||||
*
|
||||
* <p>Use this for things that are loaded lazily but then will never change. This allows the test
|
||||
* config to set the expiration time to zero so that different test values can be substituted in,
|
||||
* while allowing the production config to set the expiration to forever.
|
||||
*/
|
||||
public static <T> Supplier<T> memoizeWithLongExpiration(Supplier<T> original) {
|
||||
return memoizeForDuration(original, ENVIRONMENT.config().getSingletonCachePersistDuration());
|
||||
}
|
||||
|
||||
/** Memoize a supplier, with a given expiration. */
|
||||
private static <T> Supplier<T> memoizeForDuration(Supplier<T> original, Duration expiration) {
|
||||
return expiration.isEqual(ZERO)
|
||||
? original // memoizeWithExpiration won't accept 0 as a refresh duration.
|
||||
: memoizeWithExpiration(original, expiration.getMillis(), MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
47
java/google/registry/util/CapturingLogHandler.java
Normal file
47
java/google/registry/util/CapturingLogHandler.java
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** A log handler that captures logs. */
|
||||
public final class CapturingLogHandler extends Handler {
|
||||
|
||||
private final List<LogRecord> records = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void publish(@Nullable LogRecord record) {
|
||||
if (record != null) {
|
||||
records.add(record);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
|
||||
public Iterable<LogRecord> getRecords() {
|
||||
return Iterables.unmodifiableIterable(records);
|
||||
}
|
||||
}
|
471
java/google/registry/util/CidrAddressBlock.java
Normal file
471
java/google/registry/util/CidrAddressBlock.java
Normal file
|
@ -0,0 +1,471 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.collect.AbstractSequentialIterator;
|
||||
import com.google.common.net.InetAddresses;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Class representing an RFC 1519 CIDR IP address block.
|
||||
*
|
||||
* <p>When creating a CidrAddressBlock from an IP string literal
|
||||
* without a specified CIDR netmask (i.e. no trailing "/16" or "/64")
|
||||
* or an InetAddress with an accompanying integer netmask, then the
|
||||
* maximum length netmask for the address famiy of the specified
|
||||
* address is used (i.e. 32 for IPv4, 128 for IPv6). I.e. "1.2.3.4"
|
||||
* is automatically treated as "1.2.3.4/32" and, similarly,
|
||||
* "2001:db8::1" is automatically treated as "2001:db8::1/128".
|
||||
*
|
||||
*/
|
||||
// TODO(b/21870796): Migrate to Guava version when this is open-sourced.
|
||||
public class CidrAddressBlock implements Iterable<InetAddress>, Serializable {
|
||||
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(CidrAddressBlock.class.getName());
|
||||
|
||||
private final InetAddress ip;
|
||||
|
||||
/**
|
||||
* The number of block or mask bits needed to create the address block
|
||||
* (starting from the most-significant side of the address).
|
||||
*/
|
||||
private final int netmask;
|
||||
|
||||
/**
|
||||
* Attempts to parse the given String into a CIDR block.
|
||||
*
|
||||
* <p>If the string is an IP string literal without a specified
|
||||
* CIDR netmask (i.e. no trailing "/16" or "/64") then the maximum
|
||||
* length netmask for the address famiy of the specified address is
|
||||
* used (i.e. 32 for IPv4, 128 for IPv6).
|
||||
*
|
||||
* <p>The specified IP address portion must be properly truncated
|
||||
* (i.e. all the host bits must be zero) or the input is considered
|
||||
* malformed. For example, "1.2.3.0/24" is accepted but "1.2.3.4/24"
|
||||
* is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas
|
||||
* "2001:db8::1/32" is not.
|
||||
*
|
||||
* <p>If inputs might not be properly truncated but would be acceptable
|
||||
* to the application consider constructing a {@code CidrAddressBlock}
|
||||
* via {@code create()}.
|
||||
*
|
||||
* @param s a String of the form "217.68.0.0/16" or "2001:db8::/32".
|
||||
*
|
||||
* @throws IllegalArgumentException if s is malformed or does not
|
||||
* represent a valid CIDR block.
|
||||
*/
|
||||
public CidrAddressBlock(String s) {
|
||||
this(parseInetAddress(s), parseNetmask(s), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse the given String and int into a CIDR block.
|
||||
*
|
||||
* <p>The specified IP address portion must be properly truncated
|
||||
* (i.e. all the host bits must be zero) or the input is considered
|
||||
* malformed. For example, "1.2.3.0/24" is accepted but "1.2.3.4/24"
|
||||
* is not. Similarly, for IPv6, "2001:db8::/32" is accepted whereas
|
||||
* "2001:db8::1/32" is not.
|
||||
*
|
||||
* <p>An IP address without a netmask will automatically have the
|
||||
* maximum applicable netmask for its address family. I.e. "1.2.3.4"
|
||||
* is automatically treated as "1.2.3.4/32", and "2001:db8::1" is
|
||||
* automatically treated as "2001:db8::1/128".
|
||||
*
|
||||
* <p>If inputs might not be properly truncated but would be acceptable
|
||||
* to the application consider constructing a {@code CidrAddressBlock}
|
||||
* via {@code create()}.
|
||||
*
|
||||
* @param ip a String of the form "217.68.0.0" or "2001:db8::".
|
||||
* @param netmask an int between 0 and 32 (for IPv4) or 128 (for IPv6).
|
||||
* This is the number of bits, starting from the big end of the IP,
|
||||
* that will be used for network bits (as opposed to host bits)
|
||||
* in this CIDR block.
|
||||
*
|
||||
* @throws IllegalArgumentException if the params are malformed or do not
|
||||
* represent a valid CIDR block.
|
||||
*/
|
||||
public CidrAddressBlock(String ip, int netmask) {
|
||||
this(InetAddresses.forString(ip), checkNotNegative(netmask), false);
|
||||
}
|
||||
|
||||
public CidrAddressBlock(InetAddress ip) {
|
||||
this(ip, AUTO_NETMASK, false);
|
||||
}
|
||||
|
||||
public CidrAddressBlock(InetAddress ip, int netmask) {
|
||||
this(ip, checkNotNegative(netmask), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to construct a CIDR block from the IP address and netmask,
|
||||
* truncating the IP address as required.
|
||||
*
|
||||
* <p>The specified IP address portion need not be properly truncated
|
||||
* (i.e. all the host bits need not be zero); truncation will be silently
|
||||
* performed. For example, "1.2.3.4/24" is accepted and returns the
|
||||
* same {@code CidrAddressBlock} as "1.2.3.0/24". Similarly, for IPv6,
|
||||
* "2001:db8::1/32" is accepted and returns the same
|
||||
* {@code CidrAddressBlock} as "2001:db8::/32".
|
||||
*
|
||||
* @param ip {@link InetAddress}, possibly requiring truncation.
|
||||
* @param netmask an int between 0 and 32 (for IPv4) or 128 (for IPv6).
|
||||
* This is the number of bits, starting from the big end of the IP,
|
||||
* that will be used for network bits (as opposed to host bits)
|
||||
* when truncating the supplied {@link InetAddress}.
|
||||
*
|
||||
* @throws IllegalArgumentException if the params are malformed or do not
|
||||
* represent a valid CIDR block.
|
||||
* @throws NullPointerException if a parameter is null.
|
||||
*/
|
||||
public static CidrAddressBlock create(InetAddress ip, int netmask) {
|
||||
return new CidrAddressBlock(ip, checkNotNegative(netmask), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to construct a CIDR block from the IP address and netmask
|
||||
* expressed as a String, truncating the IP address as required.
|
||||
*
|
||||
* <p>The specified IP address portion need not be properly truncated
|
||||
* (i.e. all the host bits need not be zero); truncation will be silently
|
||||
* performed. For example, "1.2.3.4/24" is accepted and returns the
|
||||
* same {@code CidrAddressBlock} as "1.2.3.0/24". Similarly, for IPv6,
|
||||
* "2001:db8::1/32" is accepted and returns the same
|
||||
* {@code CidrAddressBlock} as "2001:db8::/32".
|
||||
*
|
||||
* @param s {@code String} representing either a single IP address or
|
||||
* a CIDR netblock, possibly requiring truncation.
|
||||
*
|
||||
* @throws IllegalArgumentException if the params are malformed or do not
|
||||
* represent a valid CIDR block.
|
||||
* @throws NullPointerException if a parameter is null.
|
||||
*/
|
||||
public static CidrAddressBlock create(String s) {
|
||||
return new CidrAddressBlock(parseInetAddress(s), parseNetmask(s), true);
|
||||
}
|
||||
|
||||
private static final int AUTO_NETMASK = -1;
|
||||
|
||||
/**
|
||||
* The universal constructor. All public constructors should lead here.
|
||||
*
|
||||
* @param ip {@link InetAddress}, possibly requiring truncation.
|
||||
* @param netmask the number of prefix bits to include in the netmask.
|
||||
* This is between 0 and 32 (for IPv4) or 128 (for IPv6).
|
||||
* The special value {@code AUTO_NETMASK} indicates that the CIDR block
|
||||
* should cover exactly one IP address.
|
||||
* @param truncate controls the behavior when an address has extra trailing
|
||||
* bits. If true, these bits are silently truncated, otherwise this
|
||||
* triggers an exception.
|
||||
*
|
||||
* @throws IllegalArgumentException if netmask is out of range, or ip has
|
||||
* unexpected trailing bits.
|
||||
* @throws NullPointerException if a parameter is null.
|
||||
*/
|
||||
private CidrAddressBlock(InetAddress ip, int netmask, boolean truncate) {
|
||||
// A single IP address is always truncated, by definition.
|
||||
if (netmask == AUTO_NETMASK) {
|
||||
this.ip = ip;
|
||||
this.netmask = ip.getAddress().length * 8;
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the truncated form of this CIDR block.
|
||||
InetAddress truncatedIp = applyNetmask(ip, netmask);
|
||||
|
||||
// If we're not truncating silently, then check for trailing bits.
|
||||
if (!truncate && !truncatedIp.equals(ip)) {
|
||||
throw new IllegalArgumentException(
|
||||
"CIDR block: " + getCidrString(ip, netmask)
|
||||
+ " is not properly truncated, should have been: "
|
||||
+ getCidrString(truncatedIp, netmask));
|
||||
}
|
||||
|
||||
this.ip = truncatedIp;
|
||||
this.netmask = netmask;
|
||||
}
|
||||
|
||||
private static String getCidrString(InetAddress ip, int netmask) {
|
||||
return ip.getHostAddress() + "/" + netmask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse an {@link InetAddress} prefix from the given String.
|
||||
*
|
||||
* @param s a String of the form "217.68.0.0/16" or "2001:db8::/32".
|
||||
*
|
||||
* @throws IllegalArgumentException if s does not begin with an IP address.
|
||||
*/
|
||||
private static InetAddress parseInetAddress(String s) {
|
||||
int slash = s.indexOf('/');
|
||||
return InetAddresses.forString((slash < 0) ? s : s.substring(0, slash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse a netmask from the given String.
|
||||
*
|
||||
* <p>If the string does not end with a "/xx" suffix, then return AUTO_NETMASK
|
||||
* and let the constructor handle it. Otherwise, we only verify that the
|
||||
* suffix is a well-formed nonnegative integer.
|
||||
*
|
||||
* @param s a String of the form "217.68.0.0/16" or "2001:db8::/32".
|
||||
*
|
||||
* @throws IllegalArgumentException if s is malformed or does not end with a
|
||||
* valid nonnegative integer.
|
||||
*/
|
||||
private static int parseNetmask(String s) {
|
||||
int slash = s.indexOf('/');
|
||||
if (slash < 0) {
|
||||
return AUTO_NETMASK;
|
||||
}
|
||||
try {
|
||||
return checkNotNegative(Integer.parseInt(s.substring(slash + 1)));
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IllegalArgumentException("Invalid netmask: " + s.substring(slash + 1));
|
||||
}
|
||||
}
|
||||
|
||||
private static int checkNotNegative(int netmask) {
|
||||
checkArgument(netmask >= 0, "CIDR netmask '%s' must not be negative.", netmask);
|
||||
return netmask;
|
||||
}
|
||||
|
||||
private static InetAddress applyNetmask(InetAddress ip, int netmask) {
|
||||
byte[] bytes = ip.getAddress();
|
||||
checkArgument(
|
||||
(netmask >= 0) && (netmask <= (bytes.length * 8)),
|
||||
"CIDR netmask '%s' is out of range: 0 <= netmask <= %s.",
|
||||
netmask, (bytes.length * 8));
|
||||
|
||||
// The byte in which the CIDR boundary falls.
|
||||
int cidrByte = (netmask == 0) ? 0 : ((netmask - 1) / 8);
|
||||
// The number of mask bits within this byte.
|
||||
int numBits = netmask - (cidrByte * 8);
|
||||
// The bitmask for this byte.
|
||||
int bitMask = (-1 << (8 - numBits));
|
||||
|
||||
// Truncate the byte in which the CIDR boundary falls.
|
||||
bytes[cidrByte] = (byte) (bytes[cidrByte] & bitMask);
|
||||
|
||||
// All bytes following the cidrByte get zeroed.
|
||||
for (int i = cidrByte + 1; i < bytes.length; ++i) {
|
||||
bytes[i] = 0;
|
||||
}
|
||||
|
||||
try {
|
||||
return InetAddress.getByAddress(bytes);
|
||||
} catch (UnknownHostException uhe) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Error creating InetAddress from byte array '%s'.",
|
||||
Arrays.toString(bytes)),
|
||||
uhe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the standard {@code String} representation of the IP portion
|
||||
* of this CIDR block (a.b.c.d, or a:b:c::d)
|
||||
*
|
||||
* <p>NOTE: This is not reliable for comparison operations. It is
|
||||
* more reliable to normalize strings into {@link InetAddress}s and
|
||||
* then compare.
|
||||
*
|
||||
* <p>Consider:
|
||||
* <ul>
|
||||
* <li>{@code "10.11.12.0"} is equivalent to {@code "10.11.12.000"}
|
||||
* <li>{@code "2001:db8::"} is equivalent to
|
||||
* {@code "2001:0DB8:0000:0000:0000:0000:0000:0000"}
|
||||
* </ul>
|
||||
*/
|
||||
public String getIp() {
|
||||
return ip.getHostAddress();
|
||||
}
|
||||
|
||||
public InetAddress getInetAddress() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of leading bits (prefix size) of the routing prefix.
|
||||
*/
|
||||
public int getNetmask() {
|
||||
return netmask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the supplied {@link InetAddress} is within
|
||||
* this {@code CidrAddressBlock}, {@code false} otherwise.
|
||||
*
|
||||
* <p>This can be used to test if the argument falls within a well-known
|
||||
* network range, a la GoogleIp's isGoogleIp(), isChinaIp(), et alia.
|
||||
*
|
||||
* @param ipAddr {@link InetAddress} to evaluate.
|
||||
* @return {@code true} if {@code ipAddr} is logically within this block,
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
public boolean contains(@Nullable InetAddress ipAddr) {
|
||||
if (ipAddr == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// IPv4 CIDR netblocks can never contain IPv6 addresses, and vice versa.
|
||||
// Calling getClass() is safe because the Inet4Address and Inet6Address
|
||||
// classes are final.
|
||||
if (ipAddr.getClass() != ip.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return ip.equals(applyNetmask(ipAddr, netmask));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
|
||||
// Something has gone very wrong. This CidrAddressBlock should
|
||||
// not have been created with an invalid netmask and a valid
|
||||
// netmask should have been successfully applied to "ipAddr" as long
|
||||
// as it represents an address of the same family as "this.ip".
|
||||
logger.warning(iae.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the supplied {@code CidrAddressBlock} is within
|
||||
* this {@code CidrAddressBlock}, {@code false} otherwise.
|
||||
*
|
||||
* <p>This can be used to test if the argument falls within a well-known
|
||||
* network range, a la GoogleIp's isGoogleIp(), isChinaIp(), et alia.
|
||||
*
|
||||
* @param cidr {@code CidrAddressBlock} to evaluate.
|
||||
* @return {@code true} if {@code cidr} is logically within this block,
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
public boolean contains(@Nullable CidrAddressBlock cidr) {
|
||||
if (cidr == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cidr.netmask < netmask) {
|
||||
// No block can contain a network larger than it
|
||||
// (in CIDR larger blocks have smaller netmasks).
|
||||
return false;
|
||||
}
|
||||
|
||||
return contains(cidr.getInetAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the supplied {@code String} is within
|
||||
* this {@code CidrAddressBlock}, {@code false} otherwise.
|
||||
*
|
||||
* <p>This can be used to test if the argument falls within a well-known
|
||||
* network range, a la GoogleIp's isGoogleIp(), isChinaIp(), et alia.
|
||||
*
|
||||
* @param s {@code String} to evaluate.
|
||||
* @return {@code true} if {@code s} is logically within this block,
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
public boolean contains(@Nullable String s) {
|
||||
if (s == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
return contains(create(s));
|
||||
} catch (IllegalArgumentException iae) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address that is contained in this {@code CidrAddressBlock}
|
||||
* with the most bits set.
|
||||
*
|
||||
* <p>This can be used to calculate the upper bound address of the address
|
||||
* range for this {@code CidrAddressBlock}.
|
||||
*/
|
||||
public InetAddress getAllOnesAddress() {
|
||||
byte[] bytes = ip.getAddress();
|
||||
|
||||
// The byte in which the CIDR boundary falls.
|
||||
int cidrByte = (netmask == 0) ? 0 : ((netmask - 1) / 8);
|
||||
// The number of mask bits within this byte.
|
||||
int numBits = netmask - (cidrByte * 8);
|
||||
// The bitmask for this byte.
|
||||
int bitMask = ~(-1 << (8 - numBits));
|
||||
|
||||
// Set all non-prefix bits where the CIDR boundary falls.
|
||||
bytes[cidrByte] = (byte) (bytes[cidrByte] | bitMask);
|
||||
|
||||
// All bytes following the cidrByte get set to all ones.
|
||||
for (int i = cidrByte + 1; i < bytes.length; ++i) {
|
||||
bytes[i] = (byte) 0xff;
|
||||
}
|
||||
|
||||
try {
|
||||
return InetAddress.getByAddress(bytes);
|
||||
} catch (UnknownHostException uhe) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Error creating InetAddress from byte array '%s'.",
|
||||
Arrays.toString(bytes)),
|
||||
uhe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<InetAddress> iterator() {
|
||||
return new AbstractSequentialIterator<InetAddress>(ip) {
|
||||
@Override
|
||||
protected InetAddress computeNext(InetAddress previous) {
|
||||
if (InetAddresses.isMaximum(previous)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InetAddress next = InetAddresses.increment(previous);
|
||||
return (contains(next)) ? next : null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return InetAddresses.coerceToInteger(ip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object o) {
|
||||
if (!(o instanceof CidrAddressBlock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CidrAddressBlock cidr = (CidrAddressBlock) o;
|
||||
return ip.equals(cidr.ip) && (netmask == cidr.netmask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCidrString(ip, netmask);
|
||||
}
|
||||
}
|
27
java/google/registry/util/Clock.java
Normal file
27
java/google/registry/util/Clock.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/** A clock that tells the current time in milliseconds or nanoseconds. */
|
||||
@ThreadSafe
|
||||
public interface Clock {
|
||||
|
||||
/** Returns current time in UTC timezone. */
|
||||
DateTime nowUtc();
|
||||
}
|
153
java/google/registry/util/CollectionUtils.java
Normal file
153
java/google/registry/util/CollectionUtils.java
Normal file
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.collect.Iterables.isEmpty;
|
||||
import static com.google.common.collect.Iterables.partition;
|
||||
|
||||
import com.google.common.collect.HashMultiset;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multisets;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Utility methods related to collections. */
|
||||
public class CollectionUtils {
|
||||
|
||||
/** Checks if an iterable is null or empty. */
|
||||
public static boolean isNullOrEmpty(@Nullable Iterable<?> potentiallyNull) {
|
||||
return potentiallyNull == null || isEmpty(potentiallyNull);
|
||||
}
|
||||
|
||||
/** Checks if a map is null or empty. */
|
||||
public static boolean isNullOrEmpty(@Nullable Map<?, ?> potentiallyNull) {
|
||||
return potentiallyNull == null || potentiallyNull.isEmpty();
|
||||
}
|
||||
|
||||
/** Turns a null set into an empty set. JAXB leaves lots of null sets lying around. */
|
||||
public static <T> Set<T> nullToEmpty(@Nullable Set<T> potentiallyNull) {
|
||||
return firstNonNull(potentiallyNull, ImmutableSet.<T>of());
|
||||
}
|
||||
|
||||
/** Turns a null list into an empty list. */
|
||||
public static <T> List<T> nullToEmpty(@Nullable List<T> potentiallyNull) {
|
||||
return firstNonNull(potentiallyNull, ImmutableList.<T>of());
|
||||
}
|
||||
|
||||
/** Turns a null map into an empty map. */
|
||||
public static <T, U> Map<T, U> nullToEmpty(@Nullable Map<T, U> potentiallyNull) {
|
||||
return firstNonNull(potentiallyNull, ImmutableMap.<T, U>of());
|
||||
}
|
||||
|
||||
/** Turns a null multimap into an empty multimap. */
|
||||
public static <T, U> Multimap<T, U> nullToEmpty(@Nullable Multimap<T, U> potentiallyNull) {
|
||||
return firstNonNull(potentiallyNull, ImmutableMultimap.<T, U>of());
|
||||
}
|
||||
|
||||
/** Turns a null sorted map into an empty sorted map.. */
|
||||
public static <T, U> SortedMap<T, U> nullToEmpty(@Nullable SortedMap<T, U> potentiallyNull) {
|
||||
return firstNonNull(potentiallyNull, ImmutableSortedMap.<T, U>of());
|
||||
}
|
||||
|
||||
/** Defensive copy helper for {@link Set}. */
|
||||
public static <V> ImmutableSet<V> nullSafeImmutableCopy(Set<V> data) {
|
||||
return data == null ? null : ImmutableSet.copyOf(data);
|
||||
}
|
||||
|
||||
/** Defensive copy helper for {@link List}. */
|
||||
public static <V> ImmutableList<V> nullSafeImmutableCopy(List<V> data) {
|
||||
return data == null ? null : ImmutableList.copyOf(data);
|
||||
}
|
||||
|
||||
/** Defensive copy helper for {@link Set}. */
|
||||
public static <V> ImmutableSet<V> nullToEmptyImmutableCopy(Set<V> data) {
|
||||
return data == null ? ImmutableSet.<V>of() : ImmutableSet.copyOf(data);
|
||||
}
|
||||
|
||||
/** Defensive copy helper for {@link Set}. */
|
||||
public static <V extends Comparable<V>>
|
||||
ImmutableSortedSet<V> nullToEmptyImmutableSortedCopy(Set<V> data) {
|
||||
return data == null ? ImmutableSortedSet.<V>of() : ImmutableSortedSet.copyOf(data);
|
||||
}
|
||||
|
||||
/** Defensive copy helper for {@link SortedMap}. */
|
||||
public static <K, V> ImmutableSortedMap<K, V> nullToEmptyImmutableCopy(SortedMap<K, V> data) {
|
||||
return data == null ? ImmutableSortedMap.<K, V>of() : ImmutableSortedMap.copyOfSorted(data);
|
||||
}
|
||||
|
||||
/** Defensive copy helper for {@link List}. */
|
||||
public static <V> ImmutableList<V> nullToEmptyImmutableCopy(List<V> data) {
|
||||
return data == null ? ImmutableList.<V>of() : ImmutableList.copyOf(data);
|
||||
}
|
||||
|
||||
/** Defensive copy helper for {@link Map}. */
|
||||
public static <K, V> ImmutableMap<K, V> nullToEmptyImmutableCopy(Map<K, V> data) {
|
||||
return data == null ? ImmutableMap.<K, V>of() : ImmutableMap.copyOf(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns an empty collection into a null collection.
|
||||
* <p>
|
||||
* This is unwise in the general case (nulls are bad; empties are good) but occasionally needed to
|
||||
* cause JAXB not to emit a field, or to avoid saving something to datastore. The method name
|
||||
* includes "force" to indicate that you should think twice before using it.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T, C extends Collection<T>> C forceEmptyToNull(@Nullable C potentiallyEmpty) {
|
||||
return potentiallyEmpty == null || potentiallyEmpty.isEmpty() ? null : potentiallyEmpty;
|
||||
}
|
||||
|
||||
/** Copy an {@link ImmutableSet} and add members. */
|
||||
@SafeVarargs
|
||||
public static <T> ImmutableSet<T> union(Set<T> set, T... newMembers) {
|
||||
return Sets.union(set, ImmutableSet.copyOf(newMembers)).immutableCopy();
|
||||
}
|
||||
|
||||
/** Copy an {@link ImmutableSet} and remove members. */
|
||||
@SafeVarargs
|
||||
public static <T> ImmutableSet<T> difference(Set<T> set, T... toRemove) {
|
||||
return Sets.difference(set, ImmutableSet.copyOf(toRemove)).immutableCopy();
|
||||
}
|
||||
|
||||
/** Returns any duplicates in an iterable. */
|
||||
public static <T> Set<T> findDuplicates(Iterable<T> iterable) {
|
||||
return Multisets.difference(
|
||||
HashMultiset.create(iterable),
|
||||
HashMultiset.create(ImmutableSet.copyOf(iterable))).elementSet();
|
||||
}
|
||||
|
||||
/** Partitions a Map into a Collection of Maps, each of max size n. */
|
||||
public static <K, V> ImmutableList<ImmutableMap<K, V>> partitionMap(Map<K, V> map, int size) {
|
||||
ImmutableList.Builder<ImmutableMap<K, V>> shards = new ImmutableList.Builder<>();
|
||||
for (Iterable<Map.Entry<K, V>> entriesShard : partition(map.entrySet(), size)) {
|
||||
shards.add(ImmutableMap.copyOf(entriesShard));
|
||||
}
|
||||
return shards.build();
|
||||
}
|
||||
}
|
102
java/google/registry/util/Concurrent.java
Normal file
102
java/google/registry/util/Concurrent.java
Normal file
|
@ -0,0 +1,102 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.appengine.api.ThreadManager.currentRequestThreadFactory;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
import static java.util.concurrent.Executors.newFixedThreadPool;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/** Utilities for multithreaded operations in App Engine requests. */
|
||||
public final class Concurrent {
|
||||
|
||||
/** Maximum number of threads per pool. The actual GAE per-request limit is 50. */
|
||||
private static final int MAX_THREADS = 10;
|
||||
|
||||
/**
|
||||
* Runs transform with the default number of threads.
|
||||
*
|
||||
* @see #transform(Collection, int, Function)
|
||||
*/
|
||||
public static <A, B> ImmutableList<B> transform(Collection<A> items, final Function<A, B> funk) {
|
||||
return transform(items, max(1, min(items.size(), MAX_THREADS)), funk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes {@code items} in parallel using {@code funk}, with the specified number of threads.
|
||||
*
|
||||
* <p><b>Note:</b> Spawned threads will inherit the same namespace.
|
||||
*
|
||||
* @throws UncheckedExecutionException to wrap the exception thrown by {@code funk}. This will
|
||||
* only contain the exception information for the first exception thrown.
|
||||
* @return transformed {@code items} in the same order.
|
||||
*/
|
||||
public static <A, B> ImmutableList<B> transform(
|
||||
Collection<A> items,
|
||||
int threadCount,
|
||||
final Function<A, B> funk) {
|
||||
checkNotNull(funk);
|
||||
checkNotNull(items);
|
||||
ThreadFactory threadFactory = currentRequestThreadFactory();
|
||||
if (threadFactory == null) {
|
||||
// Fall back to non-concurrent transform if we can't get an App Engine thread factory (most
|
||||
// likely caused by hitting this code from a command-line tool). Default Java system threads
|
||||
// are not compatible with code that needs to interact with App Engine (such as Objectify),
|
||||
// which we often have in funk when calling Concurrent.transform().
|
||||
// For more info see: http://stackoverflow.com/questions/15976406
|
||||
return FluentIterable.from(items).transform(funk).toList();
|
||||
}
|
||||
ExecutorService executor = newFixedThreadPool(threadCount, threadFactory);
|
||||
try {
|
||||
List<Future<B>> futures = new ArrayList<>();
|
||||
for (final A item : items) {
|
||||
futures.add(executor.submit(new Callable<B>() {
|
||||
@Override
|
||||
public B call() {
|
||||
return funk.apply(item);
|
||||
}}));
|
||||
}
|
||||
ImmutableList.Builder<B> results = new ImmutableList.Builder<>();
|
||||
for (Future<B> future : futures) {
|
||||
try {
|
||||
results.add(Uninterruptibles.getUninterruptibly(future));
|
||||
} catch (ExecutionException e) {
|
||||
throw new UncheckedExecutionException(e.getCause());
|
||||
}
|
||||
}
|
||||
return results.build();
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private Concurrent() {}
|
||||
}
|
35
java/google/registry/util/DatastoreServiceUtils.java
Normal file
35
java/google/registry/util/DatastoreServiceUtils.java
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
/** Utility methods for working with the AppEngine datastore service. */
|
||||
public class DatastoreServiceUtils {
|
||||
|
||||
/** Helper function that extracts the kind from a regular datastore entity key. */
|
||||
public static final Function<Key, String> KEY_TO_KIND_FUNCTION = new Function<Key, String>() {
|
||||
@Override
|
||||
public String apply(Key key) {
|
||||
return key.getKind();
|
||||
}};
|
||||
|
||||
/** Returns the name or id of a key, which may be a string or a long. */
|
||||
public static Object getNameOrId(Key key) {
|
||||
return Optional.<Object>fromNullable(key.getName()).or(key.getId());
|
||||
}
|
||||
}
|
81
java/google/registry/util/DateTimeUtils.java
Normal file
81
java/google/registry/util/DateTimeUtils.java
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Ordering;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
/** Utilities methods and constants related to Joda {@link DateTime} objects. */
|
||||
public class DateTimeUtils {
|
||||
|
||||
/** The start of the epoch, in a convenient constant. */
|
||||
public static final DateTime START_OF_TIME = new DateTime(0, DateTimeZone.UTC);
|
||||
|
||||
/**
|
||||
* A date in the far future that we can treat as infinity.
|
||||
* <p>
|
||||
* This value is (2^63-1)/1000 rounded down. AppEngine stores dates as 64 bit microseconds, but
|
||||
* Java uses milliseconds, so this is the largest representable date that will survive
|
||||
* a round-trip through the datastore.
|
||||
*/
|
||||
public static final DateTime END_OF_TIME = new DateTime(Long.MAX_VALUE / 1000, DateTimeZone.UTC);
|
||||
|
||||
/** Returns the earliest of a number of given {@link DateTime} instances. */
|
||||
public static DateTime earliestOf(DateTime first, DateTime... rest) {
|
||||
return earliestOf(Lists.asList(first, rest));
|
||||
}
|
||||
|
||||
/** Returns the earliest element in a {@link DateTime} iterable. */
|
||||
public static DateTime earliestOf(Iterable<DateTime> dates) {
|
||||
checkArgument(!Iterables.isEmpty(dates));
|
||||
return Ordering.<DateTime>natural().min(dates);
|
||||
}
|
||||
|
||||
/** Returns the latest of a number of given {@link DateTime} instances. */
|
||||
public static DateTime latestOf(DateTime first, DateTime... rest) {
|
||||
return latestOf(Lists.asList(first, rest));
|
||||
}
|
||||
|
||||
/** Returns the latest element in a {@link DateTime} iterable. */
|
||||
public static DateTime latestOf(Iterable<DateTime> dates) {
|
||||
checkArgument(!Iterables.isEmpty(dates));
|
||||
return Ordering.<DateTime>natural().max(dates);
|
||||
}
|
||||
|
||||
/** Returns whether the first {@link DateTime} is equal to or earlier than the second. */
|
||||
public static boolean isBeforeOrAt(DateTime timeToCheck, DateTime timeToCompareTo) {
|
||||
return !timeToCheck.isAfter(timeToCompareTo);
|
||||
}
|
||||
|
||||
/** Returns whether the first {@link DateTime} is equal to or later than the second. */
|
||||
public static boolean isAtOrAfter(DateTime timeToCheck, DateTime timeToCompareTo) {
|
||||
return !timeToCheck.isBefore(timeToCompareTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds years to a date, in the {@code Duration} sense of semantic years. Use this instead of
|
||||
* {@link DateTime#plusYears} to ensure that we never end up on February 29.
|
||||
*/
|
||||
public static DateTime leapSafeAddYears(DateTime now, int years) {
|
||||
checkArgument(years >= 0);
|
||||
return years == 0 ? now : now.plusYears(1).plusYears(years - 1);
|
||||
}
|
||||
}
|
173
java/google/registry/util/DiffUtils.java
Normal file
173
java/google/registry/util/DiffUtils.java
Normal file
|
@ -0,0 +1,173 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Predicates.notNull;
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.primitives.Primitives;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Helper class for diff utilities. */
|
||||
public final class DiffUtils {
|
||||
|
||||
/**
|
||||
* A helper class to store the two sides of a diff. If both sides are Sets then they will be
|
||||
* diffed, otherwise the two objects are toStringed in Collection format "[a, b]".
|
||||
*/
|
||||
private static class DiffPair {
|
||||
@Nullable
|
||||
final Object a;
|
||||
|
||||
@Nullable
|
||||
final Object b;
|
||||
|
||||
DiffPair(@Nullable Object a, @Nullable Object b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// Note that we use newArrayList here instead of ImmutableList because a and b can be null.
|
||||
return newArrayList(a, b).toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** Pretty-prints a deep diff between two maps. */
|
||||
public static String prettyPrintDeepDiff(Map<?, ?> a, Map<?, ?> b) {
|
||||
return prettyPrintDiffedMap(deepDiff(a, b), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-prints a deep diff between two maps. Path is prefixed to each output line of the diff.
|
||||
*/
|
||||
public static String prettyPrintDeepDiff(Map<?, ?> a, Map<?, ?> b, @Nullable String path) {
|
||||
return prettyPrintDiffedMap(deepDiff(a, b), path);
|
||||
}
|
||||
|
||||
/** Compare two maps and return a map containing, at each key where they differed, both values. */
|
||||
public static ImmutableMap<?, ?> deepDiff(Map<?, ?> a, Map<?, ?> b) {
|
||||
ImmutableMap.Builder<Object, Object> diff = new ImmutableMap.Builder<>();
|
||||
for (Object key : Sets.union(a.keySet(), b.keySet())) {
|
||||
Object aValue = a.get(key);
|
||||
Object bValue = b.get(key);
|
||||
if (!Objects.equals(aValue, bValue)) {
|
||||
if (aValue instanceof String && bValue instanceof String
|
||||
&& a.toString().contains("\n") && b.toString().contains("\n")) {
|
||||
aValue = stringToMap((String) aValue);
|
||||
bValue = stringToMap((String) bValue);
|
||||
} else if (aValue instanceof Set && bValue instanceof Set) {
|
||||
// Leave Sets alone; prettyPrintDiffedMap has special handling for Sets.
|
||||
} else if (aValue instanceof Iterable && bValue instanceof Iterable) {
|
||||
aValue = iterableToSortedMap((Iterable<?>) aValue);
|
||||
bValue = iterableToSortedMap((Iterable<?>) bValue);
|
||||
}
|
||||
diff.put(key, (aValue instanceof Map && bValue instanceof Map)
|
||||
? deepDiff((Map<?, ?>) aValue, (Map<?, ?>) bValue)
|
||||
: new DiffPair(aValue, bValue));
|
||||
}
|
||||
}
|
||||
return diff.build();
|
||||
}
|
||||
|
||||
private static Map<Integer, ?> iterableToSortedMap(Iterable<?> iterable) {
|
||||
// We use a sorted map here so that the iteration across the keySet is consistent.
|
||||
ImmutableSortedMap.Builder<Integer, Object> builder =
|
||||
new ImmutableSortedMap.Builder<>(Ordering.natural());
|
||||
int i = 0;
|
||||
for (Object item : Iterables.filter(iterable, notNull())) {
|
||||
builder.put(i++, item);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Map<String, ?> stringToMap(String string) {
|
||||
ImmutableMap.Builder<String, Object> builder = new ImmutableMap.Builder<>();
|
||||
int i = 0;
|
||||
for (String item : Splitter.on('\n').split(string)) {
|
||||
builder.put("Line " + i++, item);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Recursively pretty prints the contents of a diffed map generated by {@link #deepDiff}. */
|
||||
public static String prettyPrintDiffedMap(Map<?, ?> map, @Nullable String path) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
String newPath = (path == null ? "" : path + ".") + entry.getKey();
|
||||
String output;
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof Map) {
|
||||
output = prettyPrintDiffedMap((Map<?, ?>) entry.getValue(), newPath);
|
||||
} else if (value instanceof DiffPair
|
||||
&& ((DiffPair) value).a instanceof Set
|
||||
&& ((DiffPair) value).b instanceof Set) {
|
||||
DiffPair pair = ((DiffPair) value);
|
||||
String prettyLineDiff = prettyPrintSetDiff((Set<?>) pair.a, (Set<?>) pair.b) + "\n";
|
||||
output = newPath + ((prettyLineDiff.startsWith("\n")) ? " ->" : " -> ") + prettyLineDiff;
|
||||
} else {
|
||||
output = newPath + " -> " + value + "\n";
|
||||
}
|
||||
builder.append(output);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string displaying the differences between the old values in a set and the new ones.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static String prettyPrintSetDiff(Set<?> a, Set<?> b) {
|
||||
Set<?> removed = Sets.difference(a, b);
|
||||
Set<?> added = Sets.difference(b, a);
|
||||
if (removed.isEmpty() && added.isEmpty()) {
|
||||
return "NO DIFFERENCES";
|
||||
}
|
||||
return Joiner.on("\n ").skipNulls().join("",
|
||||
!added.isEmpty() ? ("ADDED:" + formatSetContents(added)) : null,
|
||||
!removed.isEmpty() ? ("REMOVED:" + formatSetContents(removed)) : null,
|
||||
"FINAL CONTENTS:" + formatSetContents(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted listing of Set contents, using a single line format if all elements are
|
||||
* wrappers of primitive types or Strings, and a multiline (one object per line) format if they
|
||||
* are not.
|
||||
*/
|
||||
private static String formatSetContents(Set<?> set) {
|
||||
for (Object obj : set) {
|
||||
if (!Primitives.isWrapperType(obj.getClass()) && !(obj instanceof String)) {
|
||||
return "\n " + Joiner.on(",\n ").join(set);
|
||||
}
|
||||
}
|
||||
return " " + set;
|
||||
}
|
||||
|
||||
private DiffUtils() {}
|
||||
}
|
67
java/google/registry/util/DomainNameUtils.java
Normal file
67
java/google/registry/util/DomainNameUtils.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
|
||||
/** Utility methods related to domain names. */
|
||||
public final class DomainNameUtils {
|
||||
|
||||
/** Prefix for unicode domain name parts. */
|
||||
public static final String ACE_PREFIX = "xn--";
|
||||
public static final String ACE_PREFIX_REGEX = "^xn--";
|
||||
|
||||
/** Checks whether "name" is a strict subdomain of "potentialParent". */
|
||||
public static boolean isUnder(InternetDomainName name, InternetDomainName potentialParent) {
|
||||
int numNameParts = name.parts().size();
|
||||
int numParentParts = potentialParent.parts().size();
|
||||
return numNameParts > numParentParts
|
||||
&& name.parts().subList(numNameParts - numParentParts, numNameParts)
|
||||
.equals(potentialParent.parts());
|
||||
}
|
||||
|
||||
/** Canonicalizes a domain name by lowercasing and converting unicode to punycode. */
|
||||
public static String canonicalizeDomainName(String label) {
|
||||
return Idn.toASCII(Ascii.toLowerCase(label));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonicalized TLD part of a valid domain name (just an SLD, no subdomains) by
|
||||
* stripping off the leftmost part.
|
||||
*
|
||||
* <p>This function is compatible with multi-part tlds, e.g. {@code co.uk}. This function will
|
||||
* also work on domains for which the registry is not authoritative. If you are certain that the
|
||||
* input will be under a TLD this registry controls, then it is preferable to use
|
||||
* {@link com.google.domain.registry.model.registry.Registries#findTldForName(InternetDomainName)
|
||||
* Registries#findTldForName}, which will work on hostnames in addition to domains.
|
||||
*
|
||||
* @param fullyQualifiedDomainName must be a punycode SLD (not a host or unicode)
|
||||
* @throws IllegalArgumentException if there is no TLD
|
||||
*/
|
||||
public static String getTldFromDomainName(String fullyQualifiedDomainName) {
|
||||
checkArgument(
|
||||
!Strings.isNullOrEmpty(fullyQualifiedDomainName),
|
||||
"fullyQualifiedDomainName cannot be null or empty");
|
||||
InternetDomainName domainName = InternetDomainName.from(fullyQualifiedDomainName);
|
||||
checkArgument(domainName.hasParent(), "fullyQualifiedDomainName does not have a TLD");
|
||||
return domainName.parent().toString();
|
||||
}
|
||||
|
||||
private DomainNameUtils() {}
|
||||
}
|
32
java/google/registry/util/FixedClock.java
Normal file
32
java/google/registry/util/FixedClock.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
/** A simple clock that always returns a fixed time. */
|
||||
public class FixedClock implements Clock {
|
||||
private final DateTime nowUtc;
|
||||
|
||||
public FixedClock(long millisSinceEpoch) {
|
||||
this.nowUtc = new DateTime(millisSinceEpoch, DateTimeZone.UTC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DateTime nowUtc() {
|
||||
return nowUtc;
|
||||
}
|
||||
}
|
114
java/google/registry/util/FormattingLogger.java
Normal file
114
java/google/registry/util/FormattingLogger.java
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/** Logging wrapper. */
|
||||
public class FormattingLogger {
|
||||
|
||||
private final Logger logger;
|
||||
|
||||
/** Returns an instance using the caller's class name for the underlying logger's name. */
|
||||
public static FormattingLogger getLoggerForCallerClass() {
|
||||
return new FormattingLogger(new Exception().getStackTrace()[1].getClassName());
|
||||
}
|
||||
|
||||
private FormattingLogger(String name) {
|
||||
this.logger = Logger.getLogger(name);
|
||||
}
|
||||
|
||||
private void log(Level level, Throwable cause, String msg) {
|
||||
StackTraceElement callerFrame = FluentIterable
|
||||
.from(asList(new Exception().getStackTrace()))
|
||||
.firstMatch(new Predicate<StackTraceElement>() {
|
||||
@Override
|
||||
public boolean apply(StackTraceElement frame) {
|
||||
return !frame.getClassName().equals(FormattingLogger.class.getName());
|
||||
}}).get();
|
||||
if (cause == null) {
|
||||
logger.logp(level, callerFrame.getClassName(), callerFrame.getMethodName(), msg);
|
||||
} else {
|
||||
logger.logp(level, callerFrame.getClassName(), callerFrame.getMethodName(), msg, cause);
|
||||
}
|
||||
}
|
||||
|
||||
public void finefmt(String fmt, Object... args) {
|
||||
log(Level.FINE, null, String.format(fmt, args));
|
||||
}
|
||||
|
||||
public void info(String msg) {
|
||||
log(Level.INFO, null, msg);
|
||||
}
|
||||
|
||||
public void info(Throwable cause, String msg) {
|
||||
log(Level.INFO, cause, msg);
|
||||
}
|
||||
|
||||
public void infofmt(String fmt, Object... args) {
|
||||
log(Level.INFO, null, String.format(fmt, args));
|
||||
}
|
||||
|
||||
public void infofmt(Throwable cause, String fmt, Object... args) {
|
||||
log(Level.INFO, cause, String.format(fmt, args));
|
||||
}
|
||||
|
||||
public void warning(String msg) {
|
||||
log(Level.WARNING, null, msg);
|
||||
}
|
||||
|
||||
public void warning(Throwable cause, String msg) {
|
||||
log(Level.WARNING, cause, msg);
|
||||
}
|
||||
|
||||
public void warningfmt(String fmt, Object... args) {
|
||||
log(Level.WARNING, null, String.format(fmt, args));
|
||||
}
|
||||
|
||||
public void warningfmt(Throwable cause, String fmt, Object... args) {
|
||||
log(Level.WARNING, cause, String.format(fmt, args));
|
||||
}
|
||||
|
||||
public void severe(String msg) {
|
||||
log(Level.SEVERE, null, msg);
|
||||
}
|
||||
|
||||
public void severe(Throwable cause, String msg) {
|
||||
log(Level.SEVERE, cause, msg);
|
||||
}
|
||||
|
||||
public void severefmt(String fmt, Object... args) {
|
||||
log(Level.SEVERE, null, String.format(fmt, args));
|
||||
}
|
||||
|
||||
public void severefmt(Throwable cause, String fmt, Object... args) {
|
||||
log(Level.SEVERE, cause, String.format(fmt, args));
|
||||
}
|
||||
|
||||
public void addHandler(Handler handler) {
|
||||
logger.addHandler(handler);
|
||||
}
|
||||
|
||||
public void removeHandler(Handler handler) {
|
||||
logger.removeHandler(handler);
|
||||
}
|
||||
}
|
230
java/google/registry/util/HexDumper.java
Normal file
230
java/google/registry/util/HexDumper.java
Normal file
|
@ -0,0 +1,230 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
|
||||
import javax.annotation.Nonnegative;
|
||||
import javax.annotation.WillNotClose;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* Hex Dump Utility.
|
||||
*
|
||||
* <p>This class takes binary data and prints it out in a way that humans can read. It's just like
|
||||
* the hex dump programs you remembered as a kid. There a column on the left for the address (or
|
||||
* offset) in decimal, the middle columns are each digit in hexadecimal, and the column on the
|
||||
* right is the ASCII representation where non-printable characters are represented by {@code '.'}.
|
||||
*
|
||||
* <p>It's easiest to generate a simple {@link String} by calling {@link #dumpHex(byte[])}, or you
|
||||
* can stream data with {@link #HexDumper(Writer, int, int)}.
|
||||
*
|
||||
* <p>Example output:
|
||||
* <pre> {@code
|
||||
* [222 bytes total]
|
||||
* 00000000 90 0d 03 00 08 03 35 58 61 46 fd f3 f3 73 01 88 ......5XaF...s..
|
||||
* 00000016 cd 04 00 03 08 00 37 05 02 52 09 3f 8d 30 1c 45 ......7..R.?.0.E
|
||||
* 00000032 72 69 63 20 45 63 68 69 64 6e 61 20 28 74 65 73 ric Echidna (tes
|
||||
* 00000048 74 20 6b 65 79 29 20 3c 65 72 69 63 40 62 6f 75 t key) <eric@bou
|
||||
* 00000064 6e 63 79 63 61 73 74 6c 65 2e 6f 72 67 3e 00 0a ncycastle.org>..
|
||||
* 00000080 09 10 35 58 61 46 fd f3 f3 73 4b 5b 03 fe 2e 53 ..5XaF...sK[...S
|
||||
* 00000096 04 28 ab cb 35 3b e2 1b 63 91 65 3a 86 b9 fb 47 .(..5;..c.e:...G
|
||||
* 00000112 d5 4c 6a 21 50 f5 2e 39 76 aa d5 86 d7 96 3b 9a .Lj!P..9v.....;.
|
||||
* 00000128 1a c3 6d c0 50 7f c6 25 9a 04 de 0f 1f 20 ae 70 ..m.P..%..... .p
|
||||
* 00000144 f9 77 c4 8b bf ec 3c 2f 59 58 b8 47 81 6a 59 25 .w....</YX.G.jY%
|
||||
* 00000160 82 b0 ba e2 a9 43 94 aa fc 92 2b b3 76 77 f5 ba .....C....+.vw..
|
||||
* 00000176 5b 59 9a de 22 1c 79 06 88 d2 ba 97 51 e3 11 2e [Y..".y.....Q...
|
||||
* 00000192 5b c0 c6 8c 34 4d a7 28 77 bf 11 27 e7 6c 8e 1c [...4M.(w..'.l..
|
||||
* 00000208 b4 a6 66 18 8e 69 3c 18 b7 97 d5 34 9a bb ..f..i<....4..
|
||||
* }</pre>
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public final class HexDumper extends OutputStream {
|
||||
|
||||
@Nonnegative
|
||||
public static final int DEFAULT_PER_LINE = 16;
|
||||
|
||||
@Nonnegative
|
||||
public static final int DEFAULT_PER_GROUP = 4;
|
||||
|
||||
private Writer upstream;
|
||||
|
||||
@Nonnegative
|
||||
private final int perLine;
|
||||
|
||||
@Nonnegative
|
||||
private final int perGroup;
|
||||
|
||||
private long totalBytes;
|
||||
|
||||
@Nonnegative
|
||||
private int lineCount;
|
||||
|
||||
private StringBuilder line;
|
||||
|
||||
private final char[] asciis;
|
||||
|
||||
/**
|
||||
* Calls {@link #dumpHex(byte[], int, int)} with {@code perLine} set to
|
||||
* {@value #DEFAULT_PER_LINE} and {@code perGroup} set to {@value #DEFAULT_PER_GROUP}.
|
||||
*/
|
||||
public static String dumpHex(byte[] data) {
|
||||
return dumpHex(data, DEFAULT_PER_LINE, DEFAULT_PER_GROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience static method for generating a hex dump as a {@link String}.
|
||||
*
|
||||
* <p>This method adds an additional line to the beginning with the total number of bytes.
|
||||
*
|
||||
* @see #HexDumper(Writer, int, int)
|
||||
*/
|
||||
public static String dumpHex(byte[] data, @Nonnegative int perLine, @Nonnegative int perGroup) {
|
||||
checkNotNull(data, "data");
|
||||
StringWriter writer = new StringWriter();
|
||||
writer.write(String.format("[%d bytes total]\n", data.length));
|
||||
try (HexDumper hexDump = new HexDumper(writer, perLine, perGroup)) {
|
||||
hexDump.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls {@link #HexDumper(Writer, int, int)} with {@code perLine} set to
|
||||
* {@value #DEFAULT_PER_LINE} and {@code perGroup} set to {@value #DEFAULT_PER_GROUP}.
|
||||
*/
|
||||
public HexDumper(@WillNotClose Writer writer) {
|
||||
this(writer, DEFAULT_PER_LINE, DEFAULT_PER_GROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new streaming {@link HexDumper} object.
|
||||
*
|
||||
* <p>The output is line-buffered so a single write call is made to {@code out} for each line.
|
||||
* This is done to avoid system call overhead {@code out} is a resource, and reduces the chance
|
||||
* of lines being broken by other threads writing to {@code out} (or its underlying resource) at
|
||||
* the same time.
|
||||
*
|
||||
* <p>This object will <i>not</i> close {@code out}. You must close <i>both</i> this object and
|
||||
* {@code out}, and this object must be closed <i>first</i>.
|
||||
*
|
||||
* @param out is the stream to which the hex dump text is written. It is <i>not</i> closed.
|
||||
* @param perLine determines how many hex characters to show on each line.
|
||||
* @param perGroup how many columns of hex digits should be grouped together. If this value is
|
||||
* {@code > 0}, an extra space will be inserted after each Nth column for readability.
|
||||
* Grouping can be disabled by setting this to {@code 0}.
|
||||
* @see #dumpHex(byte[])
|
||||
*/
|
||||
public HexDumper(@WillNotClose Writer out, @Nonnegative int perLine, @Nonnegative int perGroup) {
|
||||
checkArgument(0 < perLine, "0 < perLine <= INT32_MAX");
|
||||
checkArgument(0 <= perGroup && perGroup < perLine, "0 <= perGroup < perLine");
|
||||
this.upstream = checkNotNull(out, "out");
|
||||
this.totalBytes = 0L;
|
||||
this.perLine = perLine;
|
||||
this.perGroup = perGroup;
|
||||
this.asciis = new char[perLine];
|
||||
this.line = newLine();
|
||||
}
|
||||
|
||||
/** Initializes member variables at the beginning of a new line. */
|
||||
private StringBuilder newLine() {
|
||||
lineCount = 0;
|
||||
return new StringBuilder(String.format("%08d ", totalBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single byte to the current line buffer, flushing if end of line.
|
||||
*
|
||||
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
|
||||
* @throws IllegalStateException if this object has been {@link #close() closed}.
|
||||
*/
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
String flush = null;
|
||||
line.append(String.format("%02x ", (byte) b));
|
||||
asciis[lineCount] = b >= 32 && b <= 126 ? (char) b : '.';
|
||||
++lineCount;
|
||||
++totalBytes;
|
||||
if (lineCount == perLine) {
|
||||
line.append(' ');
|
||||
line.append(asciis);
|
||||
line.append('\n');
|
||||
flush = line.toString();
|
||||
line = newLine();
|
||||
} else {
|
||||
if (perGroup > 0 && lineCount % perGroup == 0) {
|
||||
line.append(' ');
|
||||
}
|
||||
}
|
||||
// Writing upstream is deferred until the end in order to *somewhat* maintain a correct
|
||||
// internal state in the event that an exception is thrown. This also avoids the need for
|
||||
// a try statement which usually makes code run slower.
|
||||
if (flush != null) {
|
||||
upstream.write(flush);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes partial line buffer (if any) to upstream {@link Writer}.
|
||||
*
|
||||
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
|
||||
* @throws IllegalStateException if this object has been {@link #close() closed}.
|
||||
*/
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (line.length() > 0) {
|
||||
upstream.write(line.toString());
|
||||
line = new StringBuilder();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes out the final line (if incomplete) and invalidates this object.
|
||||
*
|
||||
* <p>This object must be closed <i>before</i> you close the upstream writer. Please note that
|
||||
* this method <i>does not</i> close upstream writer for you.
|
||||
*
|
||||
* <p>If you attempt to write to this object after calling this method,
|
||||
* {@link IllegalStateException} will be thrown. However, it's safe to call close multiple times,
|
||||
* as subsequent calls will be treated as a no-op.
|
||||
*
|
||||
* @throws IOException upon failure to write to upstream {@link Writer#write(String) writer}.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (lineCount > 0) {
|
||||
while (lineCount < perLine) {
|
||||
asciis[lineCount] = ' ';
|
||||
line.append(" ");
|
||||
++lineCount;
|
||||
if (perGroup > 0 && lineCount % perGroup == 0 && lineCount != perLine) {
|
||||
line.append(' ');
|
||||
}
|
||||
}
|
||||
line.append(' ');
|
||||
line.append(asciis);
|
||||
line.append('\n');
|
||||
flush();
|
||||
}
|
||||
}
|
||||
}
|
78
java/google/registry/util/Idn.java
Normal file
78
java/google/registry/util/Idn.java
Normal file
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
|
||||
import com.ibm.icu.text.IDNA;
|
||||
import com.ibm.icu.text.IDNA.Info;
|
||||
|
||||
/**
|
||||
* A partial API-compatible replacement for {@link java.net.IDN} that replaces <a
|
||||
* href="http://www.ietf.org/rfc/rfc3490.txt">IDNA2003</a> processing with <a
|
||||
* href="http://unicode.org/reports/tr46/">UTS46 transitional processing</a>, with differences as
|
||||
* described in the <a href="http://www.unicode.org/reports/tr46/#IDNAComparison">UTS46
|
||||
* documentation</a>/
|
||||
*
|
||||
* <p>This class provides methods to convert internationalized domain names (IDNs) between Unicode
|
||||
* and ASCII-only <a href="http://www.ietf.org/rfc/rfc3492.txt">Punycode</a> form. It implements the
|
||||
* parts of the API from {@link java.net.IDN} that we care about, but will return different results
|
||||
* as defined by the differences between IDNA2003 and transitional UTS46.
|
||||
*/
|
||||
public final class Idn {
|
||||
|
||||
/** Cached UTS46 with the flags we want. */
|
||||
private static final IDNA UTS46_INSTANCE = IDNA.getUTS46Instance(IDNA.CHECK_BIDI);
|
||||
|
||||
/**
|
||||
* Translates a string from Unicode to ASCII Compatible Encoding (ACE), as defined by the ToASCII
|
||||
* operation of <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>.
|
||||
*
|
||||
* <p>This method always uses <a href="http://unicode.org/reports/tr46/">UTS46 transitional
|
||||
* processing</a>.
|
||||
*
|
||||
* @param name a domain name, which may include multiple labels separated by dots
|
||||
* @throws IllegalArgumentException if the input string doesn't conform to RFC 3490 specification
|
||||
* @see java.net.IDN#toASCII(String)
|
||||
*/
|
||||
public static String toASCII(String name) {
|
||||
Info info = new Info();
|
||||
StringBuilder result = new StringBuilder();
|
||||
UTS46_INSTANCE.nameToASCII(name, result, info);
|
||||
if (info.hasErrors()) {
|
||||
throw new IllegalArgumentException("Errors: " + Joiner.on(',').join(info.getErrors()));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string from Unicode to ASCII Compatible Encoding (ACE), as defined by the ToASCII
|
||||
* operation of <a href="http://www.ietf.org/rfc/rfc3490.txt">RFC 3490</a>.
|
||||
*
|
||||
* <p>This method always uses <a href="http://unicode.org/reports/tr46/">UTS46 transitional
|
||||
* processing</a>.
|
||||
*
|
||||
* <p>ToUnicode never fails. In case of any error, the input string is returned unmodified.
|
||||
*
|
||||
* @param name a domain name, which may include multiple labels separated by dots
|
||||
* @see java.net.IDN#toUnicode(String)
|
||||
*/
|
||||
public static String toUnicode(String name) {
|
||||
Info info = new Info();
|
||||
StringBuilder result = new StringBuilder();
|
||||
UTS46_INSTANCE.nameToUnicode(name, result, info);
|
||||
return info.hasErrors() ? name : result.toString();
|
||||
}
|
||||
}
|
151
java/google/registry/util/ImprovedInputStream.java
Normal file
151
java/google/registry/util/ImprovedInputStream.java
Normal file
|
@ -0,0 +1,151 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.annotation.OverridingMethodsMustInvokeSuper;
|
||||
import javax.annotation.WillCloseWhenClosed;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* {@link InputStream} wrapper that offers some additional magic.
|
||||
*
|
||||
* <ul>
|
||||
* <li>Byte counting
|
||||
* <li>Log byte count on close
|
||||
* <li>Check expected byte count when closed (Optional)
|
||||
* <li>Close original {@link InputStream} when closed (Optional)
|
||||
* <li>Overridable {@link #onClose()} method
|
||||
* <li>Throws {@link NullPointerException} if read after {@link #close()}
|
||||
* </ul>
|
||||
*
|
||||
* @see ImprovedOutputStream
|
||||
* @see com.google.common.io.CountingInputStream
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public class ImprovedInputStream extends FilterInputStream {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private long count;
|
||||
private long mark = -1;
|
||||
private final long expected;
|
||||
private final boolean shouldClose;
|
||||
|
||||
public ImprovedInputStream(@WillCloseWhenClosed InputStream out) {
|
||||
this(out, true, -1);
|
||||
}
|
||||
|
||||
public ImprovedInputStream(InputStream in, boolean shouldClose, long expected) {
|
||||
super(checkNotNull(in, "in"));
|
||||
checkArgument(expected >= -1, "expected >= 0 or -1");
|
||||
this.shouldClose = shouldClose;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
@Override
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
public int read() throws IOException {
|
||||
int result = in.read();
|
||||
if (result != -1) {
|
||||
count++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int read(byte[] b) throws IOException {
|
||||
return this.read(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int result = in.read(b, off, len);
|
||||
if (result != -1) {
|
||||
count += result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
long result = in.skip(n);
|
||||
count += result;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
in.mark(readlimit);
|
||||
mark = count;
|
||||
// it's okay to mark even if mark isn't supported, as reset won't work
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
if (!in.markSupported()) {
|
||||
throw new IOException("Mark not supported");
|
||||
}
|
||||
if (mark == -1) {
|
||||
throw new IOException("Mark not set");
|
||||
}
|
||||
|
||||
in.reset();
|
||||
count = mark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs byte count, checks byte count (optional), closes (optional), and self-sabotages.
|
||||
*
|
||||
* <p>This method may not be overridden, use {@link #onClose()} instead.
|
||||
*
|
||||
* @see InputStream#close()
|
||||
*/
|
||||
@Override
|
||||
@NonFinalForTesting
|
||||
public void close() throws IOException {
|
||||
if (in == null) {
|
||||
return;
|
||||
}
|
||||
logger.infofmt("%s closed with %,d bytes read", getClass().getSimpleName(), count);
|
||||
if (expected != -1 && count != expected) {
|
||||
throw new IOException(String.format("%s expected %,d bytes but read %,d bytes",
|
||||
getClass().getCanonicalName(), expected, count));
|
||||
}
|
||||
onClose();
|
||||
if (shouldClose) {
|
||||
in.close();
|
||||
}
|
||||
in = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable method that's called by {@link #close()}.
|
||||
*
|
||||
* <p>This method does nothing by default.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void onClose() throws IOException {
|
||||
// Does nothing by default.
|
||||
}
|
||||
}
|
130
java/google/registry/util/ImprovedOutputStream.java
Normal file
130
java/google/registry/util/ImprovedOutputStream.java
Normal file
|
@ -0,0 +1,130 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.OverridingMethodsMustInvokeSuper;
|
||||
import javax.annotation.WillCloseWhenClosed;
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
/**
|
||||
* {@link OutputStream} wrapper that offers some additional magic.
|
||||
*
|
||||
* <ul>
|
||||
* <li>Byte counting
|
||||
* <li>Always {@link #flush()} on {@link #close()}
|
||||
* <li>Check expected byte count when closed (Optional)
|
||||
* <li>Close original {@link OutputStream} when closed (Optional)
|
||||
* <li>Overridable {@link #onClose()} method
|
||||
* <li>Throws {@link NullPointerException} if written after {@link #close()}
|
||||
* </ul>
|
||||
*
|
||||
* @see ImprovedInputStream
|
||||
* @see com.google.common.io.CountingOutputStream
|
||||
*/
|
||||
@NotThreadSafe
|
||||
public class ImprovedOutputStream extends FilterOutputStream {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private long count;
|
||||
private final long expected;
|
||||
private final boolean shouldClose;
|
||||
|
||||
public ImprovedOutputStream(@WillCloseWhenClosed OutputStream out) {
|
||||
this(out, true, -1);
|
||||
}
|
||||
|
||||
public ImprovedOutputStream(OutputStream out, boolean shouldClose, long expected) {
|
||||
super(checkNotNull(out, "out"));
|
||||
checkArgument(expected >= -1, "expected >= 0 or -1");
|
||||
this.shouldClose = shouldClose;
|
||||
this.expected = expected;
|
||||
}
|
||||
|
||||
/** Returns the number of bytes that have been written to this stream thus far. */
|
||||
public long getBytesWritten() {
|
||||
return count;
|
||||
}
|
||||
|
||||
/** @see java.io.FilterOutputStream#write(int) */
|
||||
@Override
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
public void write(int b) throws IOException {
|
||||
out.write(b);
|
||||
++count;
|
||||
}
|
||||
|
||||
/** @see #write(byte[], int, int) */
|
||||
@Override
|
||||
public final void write(byte[] b) throws IOException {
|
||||
this.write(b, 0, b.length);
|
||||
}
|
||||
|
||||
/** @see java.io.FilterOutputStream#write(byte[], int, int) */
|
||||
@Override
|
||||
@OverridingMethodsMustInvokeSuper
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
out.write(b, off, len);
|
||||
count += len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes, logs byte count, checks byte count (optional), closes (optional), and self-sabotages.
|
||||
*
|
||||
* <p>This method may not be overridden, use {@link #onClose()} instead.
|
||||
*
|
||||
* @see java.io.OutputStream#close()
|
||||
*/
|
||||
@Override
|
||||
@NonFinalForTesting
|
||||
public void close() throws IOException {
|
||||
if (out == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
flush();
|
||||
} catch (IOException e) {
|
||||
logger.warningfmt(e, "%s.flush() failed", getClass().getSimpleName());
|
||||
}
|
||||
logger.infofmt("%s closed with %,d bytes written", getClass().getSimpleName(), count);
|
||||
if (expected != -1 && count != expected) {
|
||||
throw new IOException(String.format(
|
||||
"%s expected %,d bytes but got %,d bytes", getClass().getSimpleName(), expected, count));
|
||||
}
|
||||
onClose();
|
||||
if (shouldClose) {
|
||||
out.close();
|
||||
}
|
||||
out = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable method that's called by {@link #close()}.
|
||||
*
|
||||
* <p>This method does nothing by default.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void onClose() throws IOException {
|
||||
// Does nothing by default.
|
||||
}
|
||||
}
|
30
java/google/registry/util/ListNamingUtils.java
Normal file
30
java/google/registry/util/ListNamingUtils.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* A utility class for conversion of input file paths into names for entities in Datastore.
|
||||
*/
|
||||
public final class ListNamingUtils {
|
||||
|
||||
/** Turns a file path into a name suitable for use as the name of a premium or reserved list. */
|
||||
public static String convertFilePathToName(Path file) {
|
||||
return Files.getNameWithoutExtension(file.getFileName().toString());
|
||||
}
|
||||
}
|
135
java/google/registry/util/NetworkUtils.java
Normal file
135
java/google/registry/util/NetworkUtils.java
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.domain.registry.util.CollectionUtils.union;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
|
||||
/** Utilities for networking. */
|
||||
public final class NetworkUtils {
|
||||
|
||||
private static final int PICK_ATTEMPTS = 16;
|
||||
private static final int RANDOM_PORT_BASE = 32768;
|
||||
private static final int RANDOM_PORT_RANGE = 60000 - RANDOM_PORT_BASE + 1;
|
||||
|
||||
@GuardedBy("random")
|
||||
private static final Random random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Returns random unused local port that can be used for TCP listening server.
|
||||
*
|
||||
* @throws IOException if failed to find free port after {@value #PICK_ATTEMPTS} attempts
|
||||
*/
|
||||
public static int pickUnusedPort() throws IOException {
|
||||
// In an ideal world, we would just listen on port 0 and use whatever random port the kernel
|
||||
// assigns us. But our CI testing system reports there are rare circumstances in which this
|
||||
// doesn't work.
|
||||
Iterator<Integer> ports = union(generateRandomPorts(PICK_ATTEMPTS), 0).iterator();
|
||||
while (true) {
|
||||
try (ServerSocket serverSocket = new ServerSocket(ports.next())) {
|
||||
return serverSocket.getLocalPort();
|
||||
} catch (IOException e) {
|
||||
if (!ports.hasNext()) {
|
||||
throw new IOException("Failed to acquire random port", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully-qualified domain name of the local host in all lower case.
|
||||
*
|
||||
* @throws RuntimeException to wrap {@link UnknownHostException} if the local host could not be
|
||||
* resolved into an address
|
||||
*/
|
||||
public static String getCanonicalHostName() {
|
||||
try {
|
||||
return getExternalAddressOfLocalSystem().getCanonicalHostName().toLowerCase();
|
||||
} catch (UnknownHostException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the externally-facing IPv4 network address of the local host.
|
||||
*
|
||||
* <p>This function implements a workaround for an
|
||||
* <a href="http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4665037">issue</a> in
|
||||
* {@link InetAddress#getLocalHost}.
|
||||
*
|
||||
* <p><b>Note:</b> This code was pilfered from {@link "com.google.net.base.LocalHost"} which was
|
||||
* never made open source.
|
||||
*
|
||||
* @throws UnknownHostException if the local host could not be resolved into an address
|
||||
*/
|
||||
private static InetAddress getExternalAddressOfLocalSystem() throws UnknownHostException {
|
||||
InetAddress localhost = InetAddress.getLocalHost();
|
||||
// If we have a loopback address, look for an address using the network cards.
|
||||
if (localhost.isLoopbackAddress()) {
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
if (interfaces == null) {
|
||||
return localhost;
|
||||
}
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface networkInterface = interfaces.nextElement();
|
||||
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
|
||||
while (addresses.hasMoreElements()) {
|
||||
InetAddress address = addresses.nextElement();
|
||||
if (!(address.isLoopbackAddress()
|
||||
|| address.isLinkLocalAddress()
|
||||
|| address instanceof Inet6Address)) {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
// Fall-through.
|
||||
}
|
||||
}
|
||||
return localhost;
|
||||
}
|
||||
|
||||
private static ImmutableSet<Integer> generateRandomPorts(int count) {
|
||||
checkArgument(count > 0);
|
||||
Set<Integer> result = new HashSet<>();
|
||||
synchronized (random) {
|
||||
while (result.size() < count) {
|
||||
result.add(RANDOM_PORT_BASE + random.nextInt(RANDOM_PORT_RANGE));
|
||||
}
|
||||
}
|
||||
return ImmutableSet.copyOf(result);
|
||||
}
|
||||
|
||||
private NetworkUtils() {}
|
||||
}
|
40
java/google/registry/util/NonFinalForTesting.java
Normal file
40
java/google/registry/util/NonFinalForTesting.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation that discourages future maintainers from marking a field {@code final}.
|
||||
*
|
||||
* <p>This annotation serves purely as documention to indicate that even though a {@code private}
|
||||
* field may <em>appear</em> safe to change to {@code final}, it will actually be reflectively
|
||||
* modified by a unit test, and therefore should not be {@code final}.
|
||||
*
|
||||
* <p>When this annotation is used on methods, it means that you should not override the method
|
||||
* and it's only non-{@code final} so it can be mocked.
|
||||
*
|
||||
* @see com.google.domain.registry.testing.InjectRule
|
||||
*/
|
||||
@Documented
|
||||
@Retention(SOURCE)
|
||||
@Target({FIELD, METHOD})
|
||||
public @interface NonFinalForTesting {}
|
29
java/google/registry/util/ObjectifyUtils.java
Normal file
29
java/google/registry/util/ObjectifyUtils.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
/** Utilities for working with Objectify. */
|
||||
public class ObjectifyUtils {
|
||||
|
||||
public static final Function<Object, Key<?>> OBJECTS_TO_KEYS = new Function<Object, Key<?>>() {
|
||||
@Override
|
||||
public Key<?> apply(Object obj) {
|
||||
return Key.create(obj);
|
||||
}};
|
||||
}
|
69
java/google/registry/util/PathargMatcher.java
Normal file
69
java/google/registry/util/PathargMatcher.java
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** Helper for extracting pathargs from the servlet request pathinfo with colon notation. */
|
||||
public final class PathargMatcher {
|
||||
|
||||
private static final Pattern PATHARG_MATCHER = Pattern.compile(":(?<name>[^/]+)");
|
||||
|
||||
/**
|
||||
* Returns regex pattern for parsing an HTTP path using Java 7 named groups.
|
||||
*
|
||||
* <p>The named groups should use colon notation. For example:<pre> {@code
|
||||
*
|
||||
* Pattern pattern = PathargMatcher.forPath("/task/:tld/:registrar");
|
||||
* Matcher matcher = pattern.matcher("/task/soy/TheRegistrar");
|
||||
* assertTrue(matcher.matches());
|
||||
* assertEquals("soy", matcher.group("soy"));
|
||||
* assertEquals("TheRegistrar", matcher.group("registrar"));}</pre>
|
||||
*
|
||||
* <h3>Best Practices</h3>
|
||||
*
|
||||
* <p>Pathargs is a good choice for parameters that:
|
||||
*
|
||||
* <ol>
|
||||
* <li>Are mandatory
|
||||
* <li>Never contain slashes
|
||||
* <li>Part of the resource name
|
||||
* <li>About 1-10 characters in length
|
||||
* <li>Matches the pattern {@code [-._a-zA-Z0-9]+} (not enforced)
|
||||
* </ol>
|
||||
*
|
||||
* <p>Otherwise you may want to consider using normal parameters.
|
||||
*
|
||||
* <h3>Trailing Slash</h3>
|
||||
*
|
||||
* <p>This class does not allow you to make the trailing slash in the path optional. You must
|
||||
* either forbid it (recommended) or require it (not recommended). If you wish to make the
|
||||
* trailing slash optional, you should configure your frontend webserver to 30x redirect to the
|
||||
* canonical path.
|
||||
*
|
||||
* <p>There's no consensus in the web development community about whether or not the canonical
|
||||
* path should include or omit the trailing slash. The Django community prefers to keep the
|
||||
* trailing slash; whereas the Ruby on Rails community has traditionally omitted it.
|
||||
*
|
||||
* @see java.util.regex.Matcher
|
||||
*/
|
||||
public static Pattern forPath(String pathSpec) {
|
||||
return Pattern.compile(convertPathSpecToRegex(pathSpec));
|
||||
}
|
||||
|
||||
private static String convertPathSpecToRegex(String pathSpec) {
|
||||
return PATHARG_MATCHER.matcher(pathSpec).replaceAll("(?<${name}>[^/]+)");
|
||||
}
|
||||
}
|
27
java/google/registry/util/PipelineUtils.java
Normal file
27
java/google/registry/util/PipelineUtils.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
/** Utilities for working with pipeline jobs. */
|
||||
public class PipelineUtils {
|
||||
|
||||
private static final String URL_PREFIX = "/_ah/pipeline/status.html?root=";
|
||||
|
||||
private PipelineUtils() {}
|
||||
|
||||
public static String createJobPath(String jobId) {
|
||||
return URL_PREFIX + jobId;
|
||||
}
|
||||
}
|
511
java/google/registry/util/PosixTarHeader.java
Normal file
511
java/google/registry/util/PosixTarHeader.java
Normal file
|
@ -0,0 +1,511 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.joda.time.DateTimeConstants.MILLIS_PER_SECOND;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
* POSIX Tar Header.
|
||||
*
|
||||
* <p>This class represents the 512-byte header that precedes each file within a tar file archive.
|
||||
* It's in the POSIX ustar format which is equivalent to using the following GNU tar flags:
|
||||
* {@code tar --format=ustar}. It's called ustar because you're a star!
|
||||
*
|
||||
* <p><b>Warning:</b> This class is not a complete tar implementation. It also offers no
|
||||
* abstractions beyond the header format and has only been tested against very simple archives
|
||||
* created in the ustar format and the default format for gnu and bsd tar. If your goal is to be
|
||||
* able to read generic tar files, you should use a more mature tar implementation like
|
||||
* <a href="http://commons.apache.org/proper/commons-compress/">Apache Commons Compress</a>.
|
||||
*
|
||||
* <p>This class is only really useful in situations where the following statements are true:
|
||||
*
|
||||
* <ol>
|
||||
* <li>You want to <i>create</i> tar archives.
|
||||
* <li>You don't need to be able to read tar files from external sources.
|
||||
* <li>You don't want additional dependencies.
|
||||
* <li>You don't need fancy features like symbolic links.
|
||||
* <li>You want something that's a step above writing out the bytes by hand.
|
||||
* </ol>
|
||||
*
|
||||
* <p>To create a tar archive using this class, you must do the following: For each file in the
|
||||
* archive, output a header and the file contents ({@code null}-padded to the nearest 512-byte
|
||||
* boundary). Then output another 1024 {@code null} bytes to indicate end of archive.
|
||||
*
|
||||
* <p>The ustar tar header contains the following fields:
|
||||
*
|
||||
* <dl>
|
||||
* <dt>name<dd><i>[Offset: 0, Length: 100]</i><br>
|
||||
* C String which we'll assume is UTF-8 (Offset: 0)
|
||||
* <dt>mode<dd><i>[Offset: 100, Length: 8]</i><br>
|
||||
* ASCII 7-digit zero-padded octal file mode and {@code null} byte.
|
||||
* <dt>uid<dd><i>[Offset: 108, Length: 8]</i><br>
|
||||
* ASCII 7-digit zero-padded octal UNIX user ID and {@code null} byte.
|
||||
* <dt>gid<dd><i>[Offset: 116, Length: 8]</i><br>
|
||||
* ASCII 7-digit zero-padded octal UNIX group ID and {@code null} byte.
|
||||
* <dt>size<dd><i>[Offset: 124, Length: 12]</i><br>
|
||||
* ASCII 11-digit zero-padded octal file size and {@code null} byte.
|
||||
* <dt>mtime<dd><i>[Offset: 136, Length: 12]</i><br>
|
||||
* ASCII octal UNIX timestamp modified time and {@code null} byte.
|
||||
* <dt>chksum<dd><i>[Offset: 148, Length: 8]</i><br>
|
||||
* ASCII octal sum of all header bytes where chksum are 0's.
|
||||
* <dt>typeflag<dd><i>[Offset: 156]</i><br>
|
||||
* Always {@code '0'} (zero character) for regular type of file.
|
||||
* <dt>linkname<dd><i>[Offset: 157, Length: 100]</i><br>
|
||||
* All {@code null} bytes because we don't support symbolic links.
|
||||
* <dt>magic<dd><i>[Offset: 257, Length: 6]</i><br>
|
||||
* Always the C string "ustar".
|
||||
* <dt>version<dd><i>[Offset: 263, Length: 2]</i><br>
|
||||
* Always "00" without a {@code null} or blank on GNU systems.
|
||||
* <dt>uname<dd><i>[Offset: 265, Length: 32]</i><br>
|
||||
* The C string UNIX user name corresponding to {@code uid}.
|
||||
* <dt>gname<dd><i>[Offset: 297, Length: 32]</i><br>
|
||||
* The C string UNIX group name corresponding to {@code gid}.
|
||||
* <dt>devmajor<dd><i>[Offset: 329, Length: 8]</i><br>
|
||||
* Not supported; set to zero.
|
||||
* <dt>devminor<dd><i>[Offset: 337, Length: 8]</i><br>
|
||||
* Not supported; set to zero.
|
||||
* <dt>prefix<dd><i>[Offset: 345, Length: 155]</i><br>
|
||||
* Not supported; set to {@code null}.
|
||||
* </dl>
|
||||
*
|
||||
* @see <a href="http://www.gnu.org/software/tar/manual/html_node/Standard.html">Tar Standard</a>
|
||||
*/
|
||||
@Immutable
|
||||
public final class PosixTarHeader {
|
||||
|
||||
/** Type of file stored in the archive. Only normal files and directories are supported. */
|
||||
public enum Type {
|
||||
/** A regular file. This can't be a symbolic link or anything interesting. */
|
||||
REGULAR,
|
||||
|
||||
/** A directory AKA folder. */
|
||||
DIRECTORY,
|
||||
|
||||
/** This indicates we read a file from an archive with an unsupported type. */
|
||||
UNSUPPORTED;
|
||||
}
|
||||
|
||||
private static final int HEADER_LENGTH = 512;
|
||||
|
||||
private final byte[] header;
|
||||
|
||||
/**
|
||||
* Create a new header from the bytes of an existing tar file header (Safe).
|
||||
*
|
||||
* <p>This routine validates the checksum to ensure the data header is correct and supported.
|
||||
*
|
||||
* @param header the existing and assumed correct header. Value is defensively copied.
|
||||
* @throws IllegalArgumentException if header isn't ustar or has a bad checksum.
|
||||
* @throws NullPointerException if {@code header} is {@code null}.
|
||||
*/
|
||||
public static PosixTarHeader from(byte[] header) {
|
||||
checkNotNull(header, "header");
|
||||
checkArgument(header.length == HEADER_LENGTH,
|
||||
"POSIX tar header length should be %s but was %s", HEADER_LENGTH, header.length);
|
||||
PosixTarHeader res = new PosixTarHeader(header.clone());
|
||||
checkArgument(res.getMagic().equals("ustar"),
|
||||
"Not a POSIX tar ustar header.");
|
||||
String version = res.getVersion();
|
||||
checkArgument(version.isEmpty() || version.equals("00"),
|
||||
"Only POSIX tar ustar version 00 and the GNU variant supported.");
|
||||
checkArgument(res.getChksum() == checksum(header),
|
||||
"POSIX tar header chksum invalid.");
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Constructs a new instance (Unsafe). */
|
||||
PosixTarHeader(byte[] header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
/** Returns 512-byte tar header (safe copy). */
|
||||
public byte[] getBytes() {
|
||||
return header.clone();
|
||||
}
|
||||
|
||||
/** Returns the filename. */
|
||||
public String getName() {
|
||||
return extractField(0, 100);
|
||||
}
|
||||
|
||||
/** Returns the octal UNIX mode aka permissions. */
|
||||
public int getMode() {
|
||||
return Integer.parseInt(extractField(100, 8).trim(), 8);
|
||||
}
|
||||
|
||||
/** Returns the UNIX owner/user id. */
|
||||
public int getUid() {
|
||||
return Integer.parseInt(extractField(108, 8).trim(), 8);
|
||||
}
|
||||
|
||||
/** Returns the UNIX group id. */
|
||||
public int getGid() {
|
||||
return Integer.parseInt(extractField(116, 8).trim(), 8);
|
||||
}
|
||||
|
||||
/** Returns the file size in bytes. */
|
||||
public int getSize() {
|
||||
return Integer.parseInt(extractField(124, 12).trim(), 8);
|
||||
}
|
||||
|
||||
/** Returns the modified time as a UTC {@link DateTime} object. */
|
||||
public DateTime getMtime() {
|
||||
return new DateTime(Long.parseLong(extractField(136, 12).trim(), 8) * MILLIS_PER_SECOND, UTC);
|
||||
}
|
||||
|
||||
/** Returns the checksum value stored in the . */
|
||||
public int getChksum() {
|
||||
return Integer.parseInt(extractField(148, 8).trim(), 8);
|
||||
}
|
||||
|
||||
/** Returns the {@link Type} of file. */
|
||||
public Type getType() {
|
||||
switch (header[156]) {
|
||||
case '\0':
|
||||
case '0':
|
||||
return Type.REGULAR;
|
||||
case '5':
|
||||
return Type.DIRECTORY;
|
||||
default:
|
||||
return Type.UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UNIX symbolic link name.
|
||||
*
|
||||
* <p>This feature is unsupported but the getter is included for completeness.
|
||||
*/
|
||||
public String getLinkName() {
|
||||
return extractField(157, 100);
|
||||
}
|
||||
|
||||
/** Returns the {@code magic} field. Only {@code "ustar"} is supported. */
|
||||
public String getMagic() {
|
||||
return extractField(257, 6).trim();
|
||||
}
|
||||
|
||||
/** Returns the {@code magic} field. Only {@code "00"} is supported. */
|
||||
public String getVersion() {
|
||||
return extractField(263, 2).trim();
|
||||
}
|
||||
|
||||
/** Returns the UNIX user name (or owner) of the file. */
|
||||
public String getUname() {
|
||||
return extractField(265, 32).trim();
|
||||
}
|
||||
|
||||
/** Returns the UNIX group name associated with the file. */
|
||||
public String getGname() {
|
||||
return extractField(297, 32).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code devmajor} field.
|
||||
*
|
||||
* <p>This feature is unsupported but the getter is included for completeness.
|
||||
*/
|
||||
public String getDevMajor() {
|
||||
return extractField(329, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code devminor} field.
|
||||
*
|
||||
* <p>This feature is unsupported but the getter is included for completeness.
|
||||
*/
|
||||
public String getDevMinor() {
|
||||
return extractField(337, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code prefix} field.
|
||||
*
|
||||
* <p>This feature is unsupported but the getter is included for completeness.
|
||||
*/
|
||||
public String getPrefix() {
|
||||
return extractField(345, 155);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a C string field. This routine is lenient when it comes to the terminating
|
||||
* {@code null} byte. If it's not present, it'll be assumed that the string length is the
|
||||
* size of the field. The encoding is always assumed to be UTF-8.
|
||||
*/
|
||||
private String extractField(int offset, int max) {
|
||||
return new String(header, offset, extractFieldLength(offset, max), UTF_8);
|
||||
}
|
||||
|
||||
/** Returns length of C string in field, or {@code max} if there's no {@code null} byte. */
|
||||
private int extractFieldLength(int offset, int max) {
|
||||
for (int n = 0; n < max; ++n) {
|
||||
if (header[offset + n] == '\0') {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/** @see Arrays#hashCode(byte[]) */
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(header);
|
||||
}
|
||||
|
||||
/** @see Arrays#equals(byte[], byte[]) */
|
||||
@Override
|
||||
public boolean equals(@Nullable Object rhs) {
|
||||
return rhs == this
|
||||
|| rhs != null
|
||||
&& getClass() == rhs.getClass()
|
||||
&& Arrays.equals(header, ((PosixTarHeader) rhs).header);
|
||||
}
|
||||
|
||||
/** @see Arrays#toString(byte[]) */
|
||||
@Override
|
||||
public String toString() {
|
||||
return Arrays.toString(header);
|
||||
}
|
||||
|
||||
/** Simple checksum algorithm specified by tar. */
|
||||
static int checksum(byte[] bytes) {
|
||||
int sum = 0;
|
||||
for (int n = 0; n < 148; ++n) {
|
||||
sum += bytes[n];
|
||||
}
|
||||
sum += ' ' * 8; // We pretend the chksum field stores spaces.
|
||||
for (int n = 148 + 8; n < bytes.length; ++n) {
|
||||
sum += bytes[n];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link PosixTarHeader}.
|
||||
*
|
||||
* <p>The following fields are required:<ul>
|
||||
* <li>{@link #setName(String)}
|
||||
* <li>{@link #setSize(long)}</ul>
|
||||
*
|
||||
* <p>{@link #build()} may be called multiple times. With the exception of the required fields
|
||||
* listed above, fields will retain the values. This is useful if you want to construct many
|
||||
* file headers with the same value for certain certain fields (e.g. uid, gid, uname, gname)
|
||||
* but don't want to have to call their setters repeatedly.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private static final int DEFAULT_MODE = 0640;
|
||||
private static final int DEFAULT_UID = 0;
|
||||
private static final int DEFAULT_GID = 0;
|
||||
private static final String DEFAULT_UNAME = "root";
|
||||
private static final String DEFAULT_GNAME = "wheel";
|
||||
private static final Type DEFAULT_TYPE = Type.REGULAR;
|
||||
|
||||
private final byte[] header = new byte[HEADER_LENGTH];
|
||||
private boolean hasName = false;
|
||||
private boolean hasSize = false;
|
||||
|
||||
public Builder() {
|
||||
setMode(DEFAULT_MODE);
|
||||
setUid(DEFAULT_UID);
|
||||
setGid(DEFAULT_GID);
|
||||
setMtime(new DateTime(UTC));
|
||||
setType(DEFAULT_TYPE);
|
||||
setMagic();
|
||||
setVersion();
|
||||
setUname(DEFAULT_UNAME);
|
||||
setGname(DEFAULT_GNAME);
|
||||
setField("devmajor", 329, 8, "0000000"); // I have no clue what this is.
|
||||
setField("devminor", 337, 8, "0000000"); // I have no clue what this is.
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file name. (Required)
|
||||
*
|
||||
* @param name must be {@code <100} characters in length.
|
||||
*/
|
||||
public Builder setName(String name) {
|
||||
checkArgument(!isNullOrEmpty(name), "name");
|
||||
setField("name", 0, 100, name);
|
||||
hasName = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UNIX file mode aka permissions. By default this is {@value #DEFAULT_MODE}.
|
||||
*
|
||||
* @param mode <u>This value is octal</u>. Just in case you were wondering, {@code 416} is the
|
||||
* decimal representation of {@code 0640}. If that number doesn't look familiar to you,
|
||||
* search Google for "chmod". The value must be {@code >=0} and {@code <8^7}.
|
||||
*/
|
||||
public Builder setMode(int mode) {
|
||||
checkArgument(0 <= mode && mode <= 07777777,
|
||||
"Tar mode out of range: %s", mode);
|
||||
setField("mode", 100, 8, String.format("%07o", mode));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the owner's UNIX user ID. By default this is {@value #DEFAULT_UID}.
|
||||
*
|
||||
* @param uid must be {@code >=0} and {@code <8^7}.
|
||||
* @see #setUname(String)
|
||||
*/
|
||||
public Builder setUid(int uid) {
|
||||
checkArgument(0 <= uid && uid <= 07777777,
|
||||
"Tar uid out of range: %s", uid);
|
||||
setField("uid", 108, 8, String.format("%07o", uid));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UNIX group ID. By default this is {@value #DEFAULT_GID}.
|
||||
*
|
||||
* @param gid must be {@code >=0} and {@code <8^7}.
|
||||
* @see #setGname(String)
|
||||
*/
|
||||
public Builder setGid(int gid) {
|
||||
checkArgument(0 <= gid && gid <= 07777777,
|
||||
"Tar gid out of range: %s", gid);
|
||||
setField("gid", 116, 8, String.format("%07o", gid));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file size. (Required)
|
||||
*
|
||||
* <p>This value must be known in advance. There's no such thing as a streaming tar archive.
|
||||
*
|
||||
* @param size must be {@code >=0} and {@code <8^11} which places an eight gigabyte limit.
|
||||
*/
|
||||
public Builder setSize(long size) {
|
||||
checkArgument(0 <= size && size <= 077777777777L,
|
||||
"Tar size out of range: %s", size);
|
||||
setField("size", 124, 12, String.format("%011o", size));
|
||||
hasSize = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the modified time of the file. By default, this is the time the builder object was
|
||||
* constructed.
|
||||
*
|
||||
* <p>The modified time is always stored as a UNIX timestamp which is seconds since the UNIX
|
||||
* epoch in UTC time. Because {@link DateTime} has millisecond precision, it gets rounded down
|
||||
* (floor) to the second.
|
||||
*
|
||||
* @throws NullPointerException
|
||||
*/
|
||||
public Builder setMtime(DateTime mtime) {
|
||||
checkNotNull(mtime, "mtime");
|
||||
setField("mtime", 136, 12, String.format("%011o", mtime.getMillis() / MILLIS_PER_SECOND));
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setChksum() {
|
||||
setField("chksum", 148, 8, String.format("%06o", checksum(header)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file {@link Type}. By default this is {@link Type#REGULAR}.
|
||||
*/
|
||||
public Builder setType(Type type) {
|
||||
switch (type) {
|
||||
case REGULAR:
|
||||
header[156] = '0';
|
||||
break;
|
||||
case DIRECTORY:
|
||||
header[156] = '5';
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UNIX owner of the file. By default this is {@value #DEFAULT_UNAME}.
|
||||
*
|
||||
* @param uname must be {@code <32} characters in length.
|
||||
* @see #setUid(int)
|
||||
* @see #setGname(String)
|
||||
*/
|
||||
public Builder setUname(String uname) {
|
||||
checkArgument(!isNullOrEmpty(uname), "uname");
|
||||
setField("uname", 265, 32, uname);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UNIX group of the file. By default this is {@value #DEFAULT_GNAME}.
|
||||
*
|
||||
* @param gname must be {@code <32} characters in length.
|
||||
* @see #setGid(int)
|
||||
* @see #setUname(String)
|
||||
*/
|
||||
public Builder setGname(String gname) {
|
||||
checkArgument(!isNullOrEmpty(gname), "gname");
|
||||
setField("gname", 297, 32, gname);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setMagic() {
|
||||
setField("magic", 257, 6, "ustar");
|
||||
}
|
||||
|
||||
private void setVersion() {
|
||||
header[263] = '0';
|
||||
header[264] = '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new immutable {@link PosixTarHeader} instance.
|
||||
*
|
||||
* <p>It's safe to save a reference to the builder instance and call this method multiple times
|
||||
* because the header data is copied into the resulting object.
|
||||
*
|
||||
* @throws IllegalStateException if you forgot to call required setters.
|
||||
*/
|
||||
public PosixTarHeader build() {
|
||||
checkState(hasName, "name not set");
|
||||
checkState(hasSize, "size not set");
|
||||
hasName = false;
|
||||
hasSize = false;
|
||||
setChksum(); // Calculate the checksum last.
|
||||
return new PosixTarHeader(header.clone());
|
||||
}
|
||||
|
||||
private void setField(String fieldName, int offset, int max, String data) {
|
||||
byte[] bytes = (data + "\0").getBytes(UTF_8);
|
||||
checkArgument(bytes.length <= max,
|
||||
"%s field exceeds max length of %s: %s", fieldName, max - 1, data);
|
||||
System.arraycopy(bytes, 0, header, offset, bytes.length);
|
||||
Arrays.fill(header, offset + bytes.length, offset + max, (byte) 0);
|
||||
}
|
||||
}
|
||||
}
|
48
java/google/registry/util/PreconditionsUtils.java
Normal file
48
java/google/registry/util/PreconditionsUtils.java
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Utility methods related to preconditions checking. */
|
||||
public class PreconditionsUtils {
|
||||
|
||||
/**
|
||||
* Checks whether the provided reference is null, throws IAE if it is, and returns it if not.
|
||||
*
|
||||
* <p>This method and its overloads are to substitute for checkNotNull() in cases where it's
|
||||
* preferable to throw an IAE instead of an NPE, such as where we want an IAE to indicate that
|
||||
* it's just a bad argument/parameter and reserve NPEs for bugs and unexpected null values.
|
||||
*/
|
||||
public static <T> T checkArgumentNotNull(T reference) {
|
||||
checkArgument(reference != null);
|
||||
return reference;
|
||||
}
|
||||
|
||||
/** Checks whether the provided reference is null, throws IAE if it is, and returns it if not. */
|
||||
public static <T> T checkArgumentNotNull(T reference, @Nullable Object errorMessage) {
|
||||
checkArgument(reference != null, errorMessage);
|
||||
return reference;
|
||||
}
|
||||
|
||||
/** Checks whether the provided reference is null, throws IAE if it is, and returns it if not. */
|
||||
public static <T> T checkArgumentNotNull(
|
||||
T reference, @Nullable String errorMessageTemplate, @Nullable Object... errorMessageArgs) {
|
||||
checkArgument(reference != null, errorMessageTemplate, errorMessageArgs);
|
||||
return reference;
|
||||
}
|
||||
}
|
45
java/google/registry/util/PredicateUtils.java
Normal file
45
java/google/registry/util/PredicateUtils.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
|
||||
/** Utility class containing {@link Predicate} methods. */
|
||||
public class PredicateUtils {
|
||||
|
||||
/**
|
||||
* A predicate for a given class X that checks if tested classes are supertypes of X.
|
||||
*
|
||||
* <p>We need our own predicate because Guava's class predicates are backwards.
|
||||
* @see "https://github.com/google/guava/issues/1444"
|
||||
*/
|
||||
private static class SupertypeOfPredicate implements Predicate<Class<?>> {
|
||||
|
||||
private final Class<?> subClass;
|
||||
|
||||
SupertypeOfPredicate(Class<?> subClass) {
|
||||
this.subClass = subClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Class<?> superClass) {
|
||||
return superClass.isAssignableFrom(subClass);
|
||||
}
|
||||
}
|
||||
|
||||
public static Predicate<Class<?>> supertypeOf(Class<?> subClass) {
|
||||
return new SupertypeOfPredicate(subClass);
|
||||
}
|
||||
}
|
34
java/google/registry/util/RegistrarUtils.java
Normal file
34
java/google/registry/util/RegistrarUtils.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.CharMatcher.javaLetterOrDigit;
|
||||
|
||||
/** Utilities for working with {@code Registrar} objects. */
|
||||
public class RegistrarUtils {
|
||||
/** Strip out anything that isn't a letter or digit, and lowercase. */
|
||||
public static String normalizeRegistrarName(String name) {
|
||||
return javaLetterOrDigit().retainFrom(name).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a normalized registrar clientId by taking the input and making it lowercase and
|
||||
* removing all characters that aren't alphanumeric or hyphens. The normalized id should be unique
|
||||
* in Datastore, and is suitable for use in email addresses.
|
||||
*/
|
||||
public static String normalizeClientId(String clientId) {
|
||||
return clientId.toLowerCase().replaceAll("[^a-z0-9\\-]", "");
|
||||
}
|
||||
}
|
52
java/google/registry/util/ResourceUtils.java
Normal file
52
java/google/registry/util/ResourceUtils.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.io.Resources.asByteSource;
|
||||
import static com.google.common.io.Resources.getResource;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.io.ByteSource;
|
||||
import com.google.common.io.Resources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
/** Utility methods related to reading java resources. */
|
||||
public final class ResourceUtils {
|
||||
|
||||
/** Loads a file as a string, assuming UTF-8 encoding. */
|
||||
public static String readResourceUtf8(String filename) {
|
||||
return resourceToString(getResource(filename));
|
||||
}
|
||||
|
||||
/** Loads a file (specified relative to the contextClass) as a string, assuming UTF-8 encoding. */
|
||||
public static String readResourceUtf8(Class<?> contextClass, String filename) {
|
||||
return resourceToString(getResource(contextClass, filename));
|
||||
}
|
||||
|
||||
private static String resourceToString(URL url) {
|
||||
try {
|
||||
return Resources.toString(url, UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Failed to load resource: " + url, e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Loads a file (specified relative to the contextClass) as a ByteSource. */
|
||||
public static ByteSource readResourceBytes(Class<?> contextClass, String filename) {
|
||||
return asByteSource(getResource(contextClass, filename));
|
||||
}
|
||||
}
|
108
java/google/registry/util/Retrier.java
Normal file
108
java/google/registry/util/Retrier.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.appengine.api.search.checkers.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Throwables.propagate;
|
||||
import static com.google.common.collect.Iterables.any;
|
||||
import static com.google.common.math.IntMath.pow;
|
||||
import static com.google.domain.registry.util.PredicateUtils.supertypeOf;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.config.ConfigModule.Config;
|
||||
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Wrapper that does retry with exponential backoff. */
|
||||
public class Retrier implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1167386907195735483L;
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private final Sleeper sleeper;
|
||||
private final int attempts;
|
||||
|
||||
@Inject
|
||||
public Retrier(Sleeper sleeper, @Config("transientFailureRetries") int transientFailureRetries) {
|
||||
this.sleeper = sleeper;
|
||||
checkArgument(transientFailureRetries > 0, "Number of attempts must be positive");
|
||||
this.attempts = transientFailureRetries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries a unit of work in the face of transient errors.
|
||||
*
|
||||
* <p>Retrying is done a fixed number of times, with exponential backoff, if the exception that is
|
||||
* thrown is deemed retryable by the predicate. If the error is not considered retryable, or if
|
||||
* the thread is interrupted, or if the allowable number of attempts has been exhausted, the
|
||||
* original exception is propagated through to the caller.
|
||||
*
|
||||
* @return <V> the value returned by the {@link Callable}.
|
||||
*/
|
||||
public final <V> V callWithRetry(Callable<V> callable, Predicate<Throwable> isRetryable) {
|
||||
int failures = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return callable.call();
|
||||
} catch (Throwable e) {
|
||||
if (++failures == attempts || !isRetryable.apply(e)) {
|
||||
propagate(e);
|
||||
}
|
||||
logger.info(e, "Retrying transient error, attempt " + failures);
|
||||
try {
|
||||
// Wait 100ms on the first attempt, doubling on each subsequent attempt.
|
||||
sleeper.sleep(Duration.millis(pow(2, failures) * 100));
|
||||
} catch (InterruptedException e2) {
|
||||
// Since we're not rethrowing InterruptedException, set the interrupt state on the thread
|
||||
// so the next blocking operation will know to abort the thread.
|
||||
Thread.currentThread().interrupt();
|
||||
propagate(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retries a unit of work in the face of transient errors.
|
||||
*
|
||||
* <p>Retrying is done a fixed number of times, with exponential backoff, if the exception that is
|
||||
* thrown is on a whitelist of retryable errors. If the error is not on the whitelist, or if the
|
||||
* thread is interrupted, or if the allowable number of attempts has been exhausted, the original
|
||||
* exception is propagated through to the caller.
|
||||
*
|
||||
* @return <V> the value returned by the {@link Callable}.
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final <V> V callWithRetry(
|
||||
Callable<V> callable,
|
||||
Class<? extends RuntimeException> retryableError,
|
||||
Class<? extends RuntimeException>... moreRetryableErrors) {
|
||||
final Set<Class<?>> retryables =
|
||||
new ImmutableSet.Builder<Class<?>>().add(retryableError).add(moreRetryableErrors).build();
|
||||
return callWithRetry(callable, new Predicate<Throwable>() {
|
||||
@Override
|
||||
public boolean apply(Throwable e) {
|
||||
return any(retryables, supertypeOf(e.getClass()));
|
||||
}});
|
||||
}
|
||||
}
|
37
java/google/registry/util/SendEmailService.java
Normal file
37
java/google/registry/util/SendEmailService.java
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.Transport;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
||||
/** Wrapper around javax.mail's Transport.send that can be mocked for testing purposes. */
|
||||
public class SendEmailService {
|
||||
|
||||
/** Returns a new MimeMessage using default App Engine transport settings. */
|
||||
public Message createMessage() {
|
||||
return new MimeMessage(Session.getDefaultInstance(new Properties(), null));
|
||||
}
|
||||
|
||||
/** Sends a message using default App Engine transport. */
|
||||
public void sendMessage(Message msg) throws MessagingException {
|
||||
Transport.send(msg);
|
||||
}
|
||||
}
|
98
java/google/registry/util/SendEmailUtils.java
Normal file
98
java/google/registry/util/SendEmailUtils.java
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.collect.Iterables.toArray;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Predicates;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.config.RegistryConfig;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.mail.Message;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
|
||||
/**
|
||||
* Utility class for sending emails from the app.
|
||||
*/
|
||||
public class SendEmailUtils {
|
||||
|
||||
private static final RegistryConfig CONFIG = RegistryEnvironment.get().config();
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
@NonFinalForTesting
|
||||
private static SendEmailService emailService = new SendEmailService();
|
||||
|
||||
/**
|
||||
* Sends an email from Domain Registry to the specified recipient. Returns true iff sending was
|
||||
* successful.
|
||||
*/
|
||||
public static boolean sendEmail(String address, String subject, String body) {
|
||||
return sendEmail(ImmutableList.of(address), subject, body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email from Domain Registry to the specified recipients. Returns true iff sending was
|
||||
* successful.
|
||||
*/
|
||||
public static boolean sendEmail(Iterable<String> addresses, final String subject, String body) {
|
||||
try {
|
||||
Message msg = emailService.createMessage();
|
||||
msg.setFrom(new InternetAddress(
|
||||
CONFIG.getGoogleAppsSendFromEmailAddress(),
|
||||
CONFIG.getGoogleAppsAdminEmailDisplayName()));
|
||||
List<InternetAddress> emails = FluentIterable
|
||||
.from(addresses)
|
||||
.transform(new Function<String, InternetAddress>() {
|
||||
@Override
|
||||
public InternetAddress apply(String emailAddress) {
|
||||
try {
|
||||
return new InternetAddress(emailAddress);
|
||||
} catch (AddressException e) {
|
||||
logger.severefmt(
|
||||
e,
|
||||
"Could not send email to %s with subject '%s'.",
|
||||
emailAddress,
|
||||
subject);
|
||||
// Returning null excludes this address from the list of recipients on the email.
|
||||
return null;
|
||||
}
|
||||
}})
|
||||
.filter(Predicates.notNull())
|
||||
.toList();
|
||||
if (emails.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
msg.addRecipients(Message.RecipientType.TO, toArray(emails, InternetAddress.class));
|
||||
msg.setSubject(subject);
|
||||
msg.setText(body);
|
||||
emailService.sendMessage(msg);
|
||||
} catch (Throwable t) {
|
||||
logger.severefmt(
|
||||
t,
|
||||
"Could not email to addresses %s with subject '%s'.",
|
||||
Joiner.on(", ").join(addresses),
|
||||
subject);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
70
java/google/registry/util/SerializeUtils.java
Normal file
70
java/google/registry/util/SerializeUtils.java
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Utilities for easy serialization with informative error messages. */
|
||||
public final class SerializeUtils {
|
||||
|
||||
/**
|
||||
* Turns an object into a byte array.
|
||||
*
|
||||
* @return serialized object or {@code null} if {@code value} is {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public static byte[] serialize(@Nullable Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream objectBytes = new ByteArrayOutputStream();
|
||||
try (ObjectOutputStream oos = new ObjectOutputStream(objectBytes)) {
|
||||
oos.writeObject(value);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException("Unable to serialize: " + value, e);
|
||||
}
|
||||
return objectBytes.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a byte array into an object.
|
||||
*
|
||||
* @return deserialized object or {@code null} if {@code objectBytes} is {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> T deserialize(Class<T> type, @Nullable byte[] objectBytes) {
|
||||
checkNotNull(type);
|
||||
if (objectBytes == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return type.cast(new ObjectInputStream(new ByteArrayInputStream(objectBytes)).readObject());
|
||||
} catch (ClassNotFoundException | IOException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unable to deserialize: objectBytes=" + base16().encode(objectBytes), e);
|
||||
}
|
||||
}
|
||||
|
||||
private SerializeUtils() {}
|
||||
}
|
46
java/google/registry/util/Sleeper.java
Normal file
46
java/google/registry/util/Sleeper.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import org.joda.time.ReadableDuration;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* An object which accepts requests to put the current thread to sleep.
|
||||
*
|
||||
* @see SystemSleeper
|
||||
* @see com.google.domain.registry.testing.FakeSleeper
|
||||
*/
|
||||
@ThreadSafe
|
||||
public interface Sleeper {
|
||||
|
||||
/**
|
||||
* Puts the current thread to sleep.
|
||||
*
|
||||
* @throws InterruptedException if this thread was interrupted
|
||||
*/
|
||||
void sleep(ReadableDuration duration) throws InterruptedException;
|
||||
|
||||
/**
|
||||
* Puts the current thread to sleep, ignoring interrupts.
|
||||
*
|
||||
* <p>If {@link InterruptedException} was caught, then {@code Thread.currentThread().interrupt()}
|
||||
* will be called at the end of the {@code duration}.
|
||||
*
|
||||
* @see com.google.common.util.concurrent.Uninterruptibles#sleepUninterruptibly
|
||||
*/
|
||||
void sleepUninterruptibly(ReadableDuration duration);
|
||||
}
|
105
java/google/registry/util/SqlTemplate.java
Normal file
105
java/google/registry/util/SqlTemplate.java
Normal file
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/** SQL template variable substitution. */
|
||||
@Immutable
|
||||
public final class SqlTemplate {
|
||||
|
||||
private static final Pattern KEY_PATTERN = Pattern.compile("[A-Z][_A-Z0-9]*");
|
||||
|
||||
private static final Pattern SEARCH_PATTERN =
|
||||
Pattern.compile("(['\"]?)%(" + KEY_PATTERN + ")%(['\"]?)");
|
||||
|
||||
private static final CharMatcher LEGAL_SUBSTITUTIONS =
|
||||
CharMatcher.javaLetterOrDigit().or(CharMatcher.anyOf("-_.,: "));
|
||||
|
||||
/** Returns a new immutable SQL template builder object, for query parameter substitution. */
|
||||
public static SqlTemplate create(String template) {
|
||||
return new SqlTemplate(template, ImmutableMap.<String, String>of());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a key/value that should be substituted an individual variable in the template.
|
||||
*
|
||||
* <p>Your template variables should appear as follows: {@code WHERE foo = '%BAR%'} and you
|
||||
* would call {@code .put("BAR", "some value"} to safely substitute it with a value. Only
|
||||
* whitelisted characters (as defined by {@link #LEGAL_SUBSTITUTIONS}) are allowed in values.
|
||||
*
|
||||
* @param key uppercase string that can have digits and underscores
|
||||
* @param value substitution value, comprised of whitelisted characters
|
||||
* @throws IllegalArgumentException if key or value has bad chars or duplicate keys were added
|
||||
*/
|
||||
public SqlTemplate put(String key, String value) {
|
||||
checkArgument(KEY_PATTERN.matcher(key).matches(), "Bad substitution key: %s", key);
|
||||
checkArgument(LEGAL_SUBSTITUTIONS.matchesAllOf(value), "Illegal characters in %s", value);
|
||||
return new SqlTemplate(template, new ImmutableMap.Builder<String, String>()
|
||||
.putAll(substitutions)
|
||||
.put(key, value)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the freshly substituted SQL code.
|
||||
*
|
||||
* @throws IllegalArgumentException if any substitution variable is not found in the template,
|
||||
* or if there are any variable-like strings (%something%) left after substitution.
|
||||
*/
|
||||
public String build() {
|
||||
StringBuffer result = new StringBuffer(template.length());
|
||||
Set<String> found = new HashSet<>();
|
||||
Matcher matcher = SEARCH_PATTERN.matcher(template);
|
||||
while (matcher.find()) {
|
||||
String wholeMatch = matcher.group(0);
|
||||
String leftQuote = matcher.group(1);
|
||||
String key = matcher.group(2);
|
||||
String rightQuote = matcher.group(3);
|
||||
String value = substitutions.get(key);
|
||||
checkArgumentNotNull(value, "%%s% found in template but no substitution specified", key);
|
||||
checkArgument(leftQuote.equals(rightQuote), "Quote mismatch: %s", wholeMatch);
|
||||
matcher.appendReplacement(result, String.format("%s%s%s", leftQuote, value, rightQuote));
|
||||
found.add(key);
|
||||
}
|
||||
matcher.appendTail(result);
|
||||
Set<String> remaining = difference(substitutions.keySet(), found);
|
||||
checkArgument(remaining.isEmpty(),
|
||||
"Not found in template: %s", Joiner.on(", ").join(remaining));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private final String template;
|
||||
private final ImmutableMap<String, String> substitutions;
|
||||
|
||||
private SqlTemplate(String template, ImmutableMap<String, String> substitutions) {
|
||||
this.template = checkNotNull(template);
|
||||
this.substitutions = checkNotNull(substitutions);
|
||||
}
|
||||
}
|
46
java/google/registry/util/SystemClock.java
Normal file
46
java/google/registry/util/SystemClock.java
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/** Clock implementation that proxies to the real system clock. */
|
||||
@ThreadSafe
|
||||
public class SystemClock implements Clock {
|
||||
|
||||
/** Returns the current time. */
|
||||
@Override
|
||||
public DateTime nowUtc() {
|
||||
return DateTime.now(UTC);
|
||||
}
|
||||
|
||||
/** Dagger module for {@link SystemClock}. */
|
||||
@Module
|
||||
public static final class SystemClockModule {
|
||||
private static final Clock clock = new SystemClock();
|
||||
|
||||
@Provides
|
||||
static Clock provideClock() {
|
||||
return clock;
|
||||
}
|
||||
}
|
||||
}
|
63
java/google/registry/util/SystemSleeper.java
Normal file
63
java/google/registry/util/SystemSleeper.java
Normal file
|
@ -0,0 +1,63 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.util.concurrent.Uninterruptibles;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import org.joda.time.ReadableDuration;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Implementation of {@link Sleeper} for production use. */
|
||||
@ThreadSafe
|
||||
public final class SystemSleeper implements Sleeper, Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2003215961965322843L;
|
||||
|
||||
@Inject
|
||||
public SystemSleeper() {}
|
||||
|
||||
@Override
|
||||
public void sleep(ReadableDuration duration) throws InterruptedException {
|
||||
checkArgument(duration.getMillis() >= 0);
|
||||
Thread.sleep(duration.getMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sleepUninterruptibly(ReadableDuration duration) {
|
||||
checkArgument(duration.getMillis() >= 0);
|
||||
Uninterruptibles.sleepUninterruptibly(duration.getMillis(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/** Dagger module for {@link SystemSleeper}. */
|
||||
@Module
|
||||
public static final class SystemSleeperModule {
|
||||
private static final Sleeper sleeper = new SystemSleeper();
|
||||
|
||||
@Provides
|
||||
static Sleeper provideSleeper() {
|
||||
return sleeper;
|
||||
}
|
||||
}
|
||||
}
|
81
java/google/registry/util/TaskEnqueuer.java
Normal file
81
java/google/registry/util/TaskEnqueuer.java
Normal file
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.appengine.api.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.appengine.api.taskqueue.TransientFailureException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/** Utilities for dealing with App Engine task queues. */
|
||||
public class TaskEnqueuer implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7893211200220508362L;
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private final Retrier retrier;
|
||||
|
||||
@Inject
|
||||
public TaskEnqueuer(Retrier retrier) {
|
||||
this.retrier = retrier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a task to a App Engine task queue in a reliable manner.
|
||||
*
|
||||
* <p>This is the same as {@link Queue#add(TaskOptions)} except it'll automatically retry with
|
||||
* exponential backoff if {@link TransientFailureException} is thrown.
|
||||
*
|
||||
* @throws TransientFailureException if retrying failed for the maximum period of time, or an
|
||||
* {@link InterruptedException} told us to stop trying
|
||||
* @return successfully enqueued task
|
||||
*/
|
||||
public TaskHandle enqueue(Queue queue, TaskOptions task) {
|
||||
return enqueue(queue, asList(task)).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds tasks to an App Engine task queue in a reliable manner.
|
||||
*
|
||||
* <p>This is the same as {@link Queue#add(Iterable)} except it'll automatically retry with
|
||||
* exponential backoff if {@link TransientFailureException} is thrown.
|
||||
*
|
||||
* @throws TransientFailureException if retrying failed for the maximum period of time, or an
|
||||
* {@link InterruptedException} told us to stop trying
|
||||
* @return successfully enqueued tasks
|
||||
*/
|
||||
public List<TaskHandle> enqueue(final Queue queue, final Iterable<TaskOptions> tasks) {
|
||||
return retrier.callWithRetry(
|
||||
new Callable<List<TaskHandle>>() {
|
||||
@Override
|
||||
public List<TaskHandle> call() {
|
||||
for (TaskOptions task : tasks) {
|
||||
logger.infofmt(
|
||||
"Enqueuing queue='%s' endpoint='%s'", queue.getQueueName(), task.getUrl());
|
||||
}
|
||||
return queue.add(tasks);
|
||||
}},
|
||||
TransientFailureException.class);
|
||||
}
|
||||
}
|
69
java/google/registry/util/TeeOutputStream.java
Normal file
69
java/google/registry/util/TeeOutputStream.java
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.WillNotClose;
|
||||
|
||||
/**
|
||||
* {@link OutputStream} delegate that writes simultaneously to multiple other output streams.
|
||||
*/
|
||||
public final class TeeOutputStream extends OutputStream {
|
||||
|
||||
private final ImmutableList<? extends OutputStream> outputs;
|
||||
private boolean isClosed;
|
||||
|
||||
public TeeOutputStream(@WillNotClose Iterable<? extends OutputStream> outputs) {
|
||||
this.outputs = ImmutableList.copyOf(outputs);
|
||||
checkArgument(!this.outputs.isEmpty(), "must provide at least one output stream");
|
||||
}
|
||||
|
||||
/** @see java.io.OutputStream#write(int) */
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
checkState(!isClosed, "outputstream closed");
|
||||
for (OutputStream out : outputs) {
|
||||
out.write(b);
|
||||
}
|
||||
}
|
||||
|
||||
/** @see #write(byte[], int, int) */
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
this.write(b, 0, b.length);
|
||||
}
|
||||
|
||||
/** @see java.io.OutputStream#write(byte[], int, int) */
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
checkState(!isClosed, "outputstream closed");
|
||||
for (OutputStream out : outputs) {
|
||||
out.write(b, off, len);
|
||||
}
|
||||
}
|
||||
|
||||
/** Closes the stream. Any calls to a {@code write()} method after this will throw. */
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
isClosed = true;
|
||||
}
|
||||
}
|
105
java/google/registry/util/TypeUtils.java
Normal file
105
java/google/registry/util/TypeUtils.java
Normal file
|
@ -0,0 +1,105 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.domain.registry.util.CollectionUtils.difference;
|
||||
import static java.lang.reflect.Modifier.isFinal;
|
||||
import static java.lang.reflect.Modifier.isStatic;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
/** Utilities methods related to reflection. */
|
||||
public class TypeUtils {
|
||||
|
||||
/** A {@TypeToken} that removes an ugly cast in the common cases of getting a known type. */
|
||||
public static class TypeInstantiator<T> extends TypeToken<T> {
|
||||
protected TypeInstantiator(Class<?> declaringClass) {
|
||||
super(declaringClass);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<T> getExactType() {
|
||||
return (Class<T>) getRawType();
|
||||
}
|
||||
|
||||
public T instantiate() {
|
||||
return TypeUtils.instantiate(getExactType());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T instantiate(Class<?> clazz) {
|
||||
checkArgument(Modifier.isPublic(clazz.getModifiers()),
|
||||
"AppEngine's custom security manager won't let us reflectively access non-public types");
|
||||
try {
|
||||
return (T) clazz.newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates enum "values" in a typesafe enum pattern into a string->field map.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> ImmutableMap<String, T> getTypesafeEnumMapping(Class<T> clazz) {
|
||||
ImmutableMap.Builder<String, T> builder = new ImmutableMap.Builder<>();
|
||||
for (Field field : clazz.getFields()) {
|
||||
if (isFinal(field.getModifiers())
|
||||
&& isStatic(field.getModifiers())
|
||||
&& clazz.isAssignableFrom(field.getType())) {
|
||||
try {
|
||||
T enumField = (T) field.get(null);
|
||||
builder.put(field.getName(), enumField);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
throw new RuntimeException(String.format(
|
||||
"Could not retrieve static final field mapping for %s", clazz.getName()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Returns a predicate that tests whether classes are annotated with the given annotation. */
|
||||
public static final Predicate<Class<?>> hasAnnotation(
|
||||
final Class<? extends Annotation> annotation) {
|
||||
return new Predicate<Class<?>>() {
|
||||
@Override
|
||||
public boolean apply(Class<?> clazz) {
|
||||
return clazz.isAnnotationPresent(annotation);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void checkNoInheritanceRelationships(ImmutableSet<Class<?>> resourceClasses) {
|
||||
for (Class<?> resourceClass : resourceClasses) {
|
||||
for (Class<?> potentialSuperclass : difference(resourceClasses, resourceClass)) {
|
||||
checkArgument(
|
||||
!potentialSuperclass.isAssignableFrom(resourceClass),
|
||||
"Cannot specify resource classes with inheritance relationship: %s extends %s",
|
||||
resourceClass,
|
||||
potentialSuperclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
java/google/registry/util/UrlFetchException.java
Normal file
60
java/google/registry/util/UrlFetchException.java
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPHeader;
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
|
||||
/**
|
||||
* Exception for when App Engine HTTP requests return a bad response.
|
||||
*
|
||||
* <p>This class displays lots of helpful troubleshooting information.
|
||||
*/
|
||||
public class UrlFetchException extends RuntimeException {
|
||||
|
||||
private final HTTPRequest req;
|
||||
private final HTTPResponse rsp;
|
||||
|
||||
public UrlFetchException(String message, HTTPRequest req, HTTPResponse rsp) {
|
||||
super(message);
|
||||
this.req = checkNotNull(req, "req");
|
||||
this.rsp = checkNotNull(rsp, "rsp");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder res = new StringBuilder(2048 + rsp.getContent().length).append(String.format(
|
||||
"%s: %s (HTTP Status %d)\nX-Fetch-URL: %s\nX-Final-URL: %s\n",
|
||||
getClass().getSimpleName(),
|
||||
getMessage(),
|
||||
rsp.getResponseCode(),
|
||||
req.getURL().toString(),
|
||||
rsp.getFinalUrl()));
|
||||
for (HTTPHeader header : rsp.getHeadersUncombined()) {
|
||||
res.append(header.getName());
|
||||
res.append(": ");
|
||||
res.append(header.getValue());
|
||||
res.append('\n');
|
||||
}
|
||||
res.append(">>>\n");
|
||||
res.append(new String(rsp.getContent(), UTF_8));
|
||||
res.append("\n<<<");
|
||||
return res.toString();
|
||||
}
|
||||
}
|
108
java/google/registry/util/UrlFetchUtils.java
Normal file
108
java/google/registry/util/UrlFetchUtils.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.io.BaseEncoding.base32;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static com.google.common.net.HttpHeaders.AUTHORIZATION;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_DISPOSITION;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_LENGTH;
|
||||
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
|
||||
import static java.lang.String.format;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.appengine.api.urlfetch.HTTPHeader;
|
||||
import com.google.appengine.api.urlfetch.HTTPRequest;
|
||||
import com.google.appengine.api.urlfetch.HTTPResponse;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.net.MediaType;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.ProviderException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/** Helper methods for the App Engine URL fetch service. */
|
||||
public final class UrlFetchUtils {
|
||||
|
||||
@NonFinalForTesting
|
||||
private static SecureRandom secureRandom = initSecureRandom();
|
||||
|
||||
/** Returns value of first header matching {@code name}. */
|
||||
public static Optional<String> getHeaderFirst(HTTPResponse rsp, String name) {
|
||||
return getHeaderFirstInternal(rsp.getHeadersUncombined(), name);
|
||||
}
|
||||
|
||||
/** Returns value of first header matching {@code name}. */
|
||||
public static Optional<String> getHeaderFirst(HTTPRequest req, String name) {
|
||||
return getHeaderFirstInternal(req.getHeaders(), name);
|
||||
}
|
||||
|
||||
private static Optional<String> getHeaderFirstInternal(Iterable<HTTPHeader> hdrs, String name) {
|
||||
name = name.toLowerCase();
|
||||
for (HTTPHeader header : hdrs) {
|
||||
if (header.getName().toLowerCase().equals(name)) {
|
||||
return Optional.of(header.getValue());
|
||||
}
|
||||
}
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets payload on request as a {@code multipart/form-data} request.
|
||||
*
|
||||
* <p>This is equivalent to running the command: {@code curl -F fieldName=@payload.txt URL}
|
||||
*
|
||||
* @see "http://www.ietf.org/rfc/rfc2388.txt"
|
||||
*/
|
||||
public static <T> void setPayloadMultipart(
|
||||
HTTPRequest request, String name, String filename, MediaType contentType, T data) {
|
||||
String boundary = createMultipartBoundary();
|
||||
StringBuilder multipart = new StringBuilder();
|
||||
multipart.append(format("--%s\r\n", boundary));
|
||||
multipart.append(format("%s: form-data; name=\"%s\"; filename=\"%s\"\r\n",
|
||||
CONTENT_DISPOSITION, name, filename));
|
||||
multipart.append(format("%s: %s\r\n", CONTENT_TYPE, contentType.toString()));
|
||||
multipart.append("\r\n");
|
||||
multipart.append(data);
|
||||
multipart.append("\r\n");
|
||||
multipart.append(format("--%s--", boundary));
|
||||
byte[] payload = multipart.toString().getBytes(UTF_8);
|
||||
request.addHeader(new HTTPHeader(CONTENT_TYPE, "multipart/form-data; boundary=" + boundary));
|
||||
request.addHeader(new HTTPHeader(CONTENT_LENGTH, Integer.toString(payload.length)));
|
||||
request.setPayload(payload);
|
||||
}
|
||||
|
||||
private static String createMultipartBoundary() {
|
||||
byte[] rand = new byte[5]; // Avoid base32 padding since `5 * 8 % log2(32) == 0`
|
||||
secureRandom.nextBytes(rand);
|
||||
return "------------------------------" + base32().encode(rand);
|
||||
}
|
||||
|
||||
private static SecureRandom initSecureRandom() {
|
||||
try {
|
||||
return SecureRandom.getInstance("NativePRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new ProviderException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the HTTP Basic Authentication header on an {@link HTTPRequest}. */
|
||||
public static void setAuthorizationHeader(HTTPRequest req, Optional<String> login) {
|
||||
if (login.isPresent()) {
|
||||
String token = base64().encode(login.get().getBytes(UTF_8));
|
||||
req.addHeader(new HTTPHeader(AUTHORIZATION, "Basic " + token));
|
||||
}
|
||||
}
|
||||
}
|
183
java/google/registry/util/X509Utils.java
Normal file
183
java/google/registry/util/X509Utils.java
Normal file
|
@ -0,0 +1,183 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Throwables.propagateIfInstanceOf;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CRLException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.CertificateRevokedException;
|
||||
import java.security.cert.Extension;
|
||||
import java.security.cert.X509CRL;
|
||||
import java.security.cert.X509CRLEntry;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Date;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import javax.annotation.Tainted;
|
||||
|
||||
/** X.509 Public Key Infrastructure (PKI) helper functions. */
|
||||
public final class X509Utils {
|
||||
|
||||
/**
|
||||
* Parse the encoded certificate and return a base64 encoded string (without padding) of the
|
||||
* SHA-256 digest of the certificate.
|
||||
* <p>
|
||||
* Note that this must match the method used by the GFE to generate the client certificate hash so
|
||||
* that the two will match when we check against the whitelist.
|
||||
*/
|
||||
public static String getCertificateHash(X509Certificate cert) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
messageDigest.update(cert.getEncoded());
|
||||
return base64().omitPadding().encode(messageDigest.digest());
|
||||
} catch (CertificateException | NoSuchAlgorithmException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an ASCII-armored public X.509 certificate.
|
||||
*
|
||||
* @throws CertificateParsingException on parsing errors.
|
||||
*/
|
||||
public static X509Certificate loadCertificate(InputStream input)
|
||||
throws CertificateParsingException {
|
||||
try {
|
||||
return Iterables.getOnlyElement(FluentIterable
|
||||
.from(CertificateFactory.getInstance("X.509").generateCertificates(input))
|
||||
.filter(X509Certificate.class));
|
||||
} catch (CertificateException e) { // CertificateParsingException by specification.
|
||||
propagateIfInstanceOf(e, CertificateParsingException.class);
|
||||
throw new CertificateParsingException(e);
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new CertificateParsingException("No X509Certificate found.");
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new CertificateParsingException("Multiple X509Certificate found.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an ASCII-armored public X.509 certificate.
|
||||
*
|
||||
* @throws CertificateParsingException on parsing errors
|
||||
*/
|
||||
public static X509Certificate loadCertificate(String asciiCrt)
|
||||
throws CertificateParsingException {
|
||||
return loadCertificate(new ByteArrayInputStream(asciiCrt.getBytes(US_ASCII)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an ASCII-armored public X.509 certificate.
|
||||
*
|
||||
* @throws CertificateParsingException on parsing errors
|
||||
* @throws IOException on file system errors
|
||||
*/
|
||||
public static X509Certificate loadCertificate(Path certPath)
|
||||
throws CertificateParsingException, IOException {
|
||||
return loadCertificate(Files.newInputStream(certPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an ASCII-armored X.509 certificate revocation list (CRL).
|
||||
*
|
||||
* @throws CRLException on parsing errors.
|
||||
*/
|
||||
public static X509CRL loadCrl(String asciiCrl) throws GeneralSecurityException {
|
||||
ByteArrayInputStream input = new ByteArrayInputStream(asciiCrl.getBytes(US_ASCII));
|
||||
try {
|
||||
return Iterables.getOnlyElement(FluentIterable
|
||||
.from(CertificateFactory.getInstance("X.509").generateCRLs(input))
|
||||
.filter(X509CRL.class));
|
||||
} catch (NoSuchElementException e) {
|
||||
throw new CRLException("No X509CRL found.");
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new CRLException("Multiple X509CRL found.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that {@code cert} is signed by the {@code ca} and not revoked.
|
||||
*
|
||||
* <p>Support for certificate chains has not been implemented.
|
||||
*
|
||||
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
|
||||
* parsing errors, encoding errors, if the CRL is expired, or if the CRL is older than the
|
||||
* one currently in memory.
|
||||
*/
|
||||
public static void verifyCertificate(
|
||||
X509Certificate rootCert, X509CRL crl, @Tainted X509Certificate cert, Date now)
|
||||
throws GeneralSecurityException {
|
||||
cert.checkValidity(checkNotNull(now, "now"));
|
||||
try {
|
||||
cert.verify(rootCert.getPublicKey());
|
||||
} catch (CertificateException e) {
|
||||
propagateIfInstanceOf(e, CertificateException.class);
|
||||
throw new CertificateEncodingException(e); // Coercion by specification.
|
||||
}
|
||||
if (crl.isRevoked(cert)) {
|
||||
X509CRLEntry entry = crl.getRevokedCertificate(cert);
|
||||
throw new CertificateRevokedException(
|
||||
checkNotNull(entry.getRevocationDate(), "revocationDate"),
|
||||
checkNotNull(entry.getRevocationReason(), "revocationReason"),
|
||||
firstNonNull(entry.getCertificateIssuer(), crl.getIssuerX500Principal()),
|
||||
ImmutableMap.<String, Extension>of());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an X.509 CRL you downloaded can safely replace your current CRL.
|
||||
*
|
||||
* <p>This routine makes sure {@code newCrl} is signed by {@code rootCert} and that its timestamps
|
||||
* are correct with respect to {@code now}.
|
||||
*
|
||||
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
*/
|
||||
public static void verifyCrl(
|
||||
X509Certificate rootCert, X509CRL oldCrl, @Tainted X509CRL newCrl, Date now)
|
||||
throws GeneralSecurityException {
|
||||
if (newCrl.getThisUpdate().before(oldCrl.getThisUpdate())) {
|
||||
throw new CRLException(String.format(
|
||||
"New CRL is more out of date than our current CRL. %s < %s\n%s",
|
||||
newCrl.getThisUpdate(), oldCrl.getThisUpdate(), newCrl));
|
||||
}
|
||||
if (newCrl.getNextUpdate().before(now)) {
|
||||
throw new CRLException("CRL has expired.\n" + newCrl);
|
||||
}
|
||||
newCrl.verify(rootCert.getPublicKey());
|
||||
}
|
||||
|
||||
private X509Utils() {}
|
||||
}
|
29
java/google/registry/util/XmlEnumUtils.java
Normal file
29
java/google/registry/util/XmlEnumUtils.java
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 com.google.domain.registry.util;
|
||||
|
||||
import javax.xml.bind.annotation.XmlEnumValue;
|
||||
|
||||
/** Utility methods related to xml enums. */
|
||||
public class XmlEnumUtils {
|
||||
/** Read the {@link XmlEnumValue} string off of an enum. */
|
||||
public static String enumToXml(Enum<?> input) {
|
||||
try {
|
||||
return input.getClass().getField(input.name()).getAnnotation(XmlEnumValue.class).value();
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
16
java/google/registry/util/package-info.java
Normal file
16
java/google/registry/util/package-info.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2016 The Domain Registry Authors. All Rights Reserved.
|
||||
//
|
||||
// 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.
|
||||
|
||||
@javax.annotation.ParametersAreNonnullByDefault
|
||||
package com.google.domain.registry.util;
|
Loading…
Add table
Add a link
Reference in a new issue