http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java b/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java new file mode 100644 index 0000000..b9de2f1 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenArtifact.java @@ -0,0 +1,222 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.maven; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.collections.MutableList; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.text.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MavenArtifact { + + private static final Logger log = LoggerFactory.getLogger(MavenArtifact.class); + + protected final @Nonnull String groupId; + protected final @Nonnull String artifactId; + protected final @Nonnull String packaging; + protected final @Nullable String classifier; + protected final @Nonnull String version; + + /** a custom marker inserted after the artifactId and before the version, offset by an additional "-"; + * defaults to null (nothing) + * <p> + * uses: when a shaded JAR is built, sometimes the word shaded is inserted before the version + * (and the "with-dependencies" classifier overwritten) */ + protected @Nullable String customFileNameAfterArtifactMarker; + + /** a custom marker inserted after the version and before the extension, offset by an additional "-" if non-empty; + * defaults to {@link #getClassifier()} if null, but can replace the classifier + * <p> + * uses: removing classifier by specifying "", or adding a notional classifier such as "dist" */ + protected @Nullable String classifierFileNameMarker; + + public MavenArtifact(String groupId, String artifactId, String packaging, String classifier, String version) { + super(); + this.groupId = groupId; + this.artifactId = artifactId; + this.packaging = packaging; + this.classifier = classifier; + this.version = version; + } + + public MavenArtifact(String groupId, String artifactId, String packaging, String version) { + super(); + this.groupId = groupId; + this.artifactId = artifactId; + this.packaging = packaging; + this.classifier = null; + this.version = version; + } + + public static MavenArtifact fromCoordinate(String coordinate) { + String[] parts = checkNotNull(coordinate, "coordinate").split(":"); + if (parts.length==4) + return new MavenArtifact(parts[0], parts[1], parts[2], parts[3]); + if (parts.length==5) + return new MavenArtifact(parts[0], parts[1], parts[2], parts[3], parts[4]); + throw new IllegalArgumentException("Invalid maven coordinate '"+coordinate+"'"); + } + + public String getGroupId() { + return groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public String getVersion() { + return version; + } + + @Nullable public String getClassifier() { + return classifier; + } + + public String getPackaging() { + return packaging; + } + + public boolean isSnapshot() { + return getVersion().toUpperCase().contains("SNAPSHOT"); + } + + /** @see #customFileNameAfterArtifactMarker */ + public String getCustomFileNameAfterArtifactMarker() { + return customFileNameAfterArtifactMarker; + } + + /** @see #customFileNameAfterArtifactMarker */ + public void setCustomFileNameAfterArtifactMarker(String customFileNameMarker) { + this.customFileNameAfterArtifactMarker = customFileNameMarker; + } + + /** @ee {@link #classifierFileNameMarker} */ + public String getClassifierFileNameMarker() { + return classifierFileNameMarker!=null ? classifierFileNameMarker : getClassifier(); + } + + /** @ee {@link #classifierFileNameMarker} */ + public void setClassifierFileNameMarker(String classifierFileNameMarker) { + this.classifierFileNameMarker = classifierFileNameMarker; + } + + /** returns a "groupId:artifactId:version:(classifier:)packaging" string + * which maven refers to as the co-ordinate */ + public String getCoordinate() { + return Strings.join(MutableList.<String>of().append(groupId, artifactId, packaging). + appendIfNotNull(classifier).append(version), ":"); + } + + public String getFilename() { + return artifactId+"-"+ + (Strings.isNonEmpty(getCustomFileNameAfterArtifactMarker()) ? getCustomFileNameAfterArtifactMarker()+"-" : "")+ + version+ + (Strings.isNonEmpty(getClassifierFileNameMarker()) ? "-"+getClassifierFileNameMarker() : "")+ + (Strings.isNonEmpty(getExtension()) ? "."+getExtension() : ""); + } + + /** returns an extension, defaulting to {@link #packaging} if one cannot be inferred */ + @Nullable public String getExtension() { + if ("jar".equalsIgnoreCase(packaging) || "bundle".equalsIgnoreCase(packaging)) + return "jar"; + if ("war".equalsIgnoreCase(packaging)) + return "war"; + log.debug("Unrecognised packaging for autodetecting extension, defaulting to {} for: {}", packaging, this); + return packaging; + } + + @Override + public String toString() { + return JavaClassNames.simpleClassName(this)+"["+getCoordinate()+"]"; + } + + @Override + public int hashCode() { + // autogenerated code + + final int prime = 31; + int result = 1; + result = prime * result + ((artifactId == null) ? 0 : artifactId.hashCode()); + result = prime * result + ((classifier == null) ? 0 : classifier.hashCode()); + result = prime * result + ((classifierFileNameMarker == null) ? 0 : classifierFileNameMarker.hashCode()); + result = prime * result + ((customFileNameAfterArtifactMarker == null) ? 0 : customFileNameAfterArtifactMarker.hashCode()); + result = prime * result + ((groupId == null) ? 0 : groupId.hashCode()); + result = prime * result + ((packaging == null) ? 0 : packaging.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + // autogenerated code + + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + MavenArtifact other = (MavenArtifact) obj; + if (artifactId == null) { + if (other.artifactId != null) + return false; + } else if (!artifactId.equals(other.artifactId)) + return false; + if (classifier == null) { + if (other.classifier != null) + return false; + } else if (!classifier.equals(other.classifier)) + return false; + if (classifierFileNameMarker == null) { + if (other.classifierFileNameMarker != null) + return false; + } else if (!classifierFileNameMarker.equals(other.classifierFileNameMarker)) + return false; + if (customFileNameAfterArtifactMarker == null) { + if (other.customFileNameAfterArtifactMarker != null) + return false; + } else if (!customFileNameAfterArtifactMarker.equals(other.customFileNameAfterArtifactMarker)) + return false; + if (groupId == null) { + if (other.groupId != null) + return false; + } else if (!groupId.equals(other.groupId)) + return false; + if (packaging == null) { + if (other.packaging != null) + return false; + } else if (!packaging.equals(other.packaging)) + return false; + if (version == null) { + if (other.version != null) + return false; + } else if (!version.equals(other.version)) + return false; + return true; + } + + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenRetriever.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenRetriever.java b/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenRetriever.java new file mode 100644 index 0000000..d8cf89c --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/maven/MavenRetriever.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.maven; + +import java.io.File; + +import org.apache.brooklyn.util.net.Urls; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.base.Function; + +/** returns the URL for accessing the artifact, assuming sonatype for snapshots and maven.org for releases by default, + * with some methods checking local file system, and allowing the generators for each to be specified */ +public class MavenRetriever { + + public static final Function<MavenArtifact,String> CONDITIONAL_SNAPSHOT_URL_GENERATOR = new Function<MavenArtifact, String>() { + public String apply(MavenArtifact artifact) { + if (artifact.groupId.startsWith("org.apache")) { + return APACHE_SNAPSHOT_URL_GENERATOR.apply(artifact); + } else { + return SONATYPE_SNAPSHOT_URL_GENERATOR.apply(artifact); + } + } + }; + + public static final Function<MavenArtifact,String> SONATYPE_SNAPSHOT_URL_GENERATOR = + nexusSnapshotUrlGenerator("https://oss.sonatype.org"); + + public static final Function<MavenArtifact,String> APACHE_SNAPSHOT_URL_GENERATOR = + nexusSnapshotUrlGenerator("https://repository.apache.org"); + + public static Function<MavenArtifact,String> nexusSnapshotUrlGenerator(final String baseUrl) { + return new Function<MavenArtifact, String>() { + public String apply(MavenArtifact artifact) { + return baseUrl+"/service/local/artifact/maven/redirect?" + + "r=snapshots&" + + "v="+Urls.encode(artifact.version)+"&" + + "g="+Urls.encode(artifact.groupId)+"&" + + "a="+Urls.encode(artifact.artifactId)+"&" + + (artifact.classifier!=null ? "c="+Urls.encode(artifact.classifier)+"&" : "")+ + "e="+Urls.encode(artifact.packaging); + } + }; + } + + public static final Function<MavenArtifact,String> MAVEN_CENTRAL_URL_GENERATOR = new Function<MavenArtifact, String>() { + public String apply(MavenArtifact artifact) { + return "http://search.maven.org/remotecontent?filepath="+ + Urls.encode(Strings.replaceAllNonRegex(artifact.groupId, ".", "/")+"/"+ + artifact.artifactId+"/"+artifact.version+"/"+ + artifact.getFilename()); + } + }; + + public static final Function<MavenArtifact,String> LOCAL_REPO_PATH_GENERATOR = new Function<MavenArtifact, String>() { + public String apply(MavenArtifact artifact) { + return System.getProperty("user.home")+"/.m2/repository/"+ + Strings.replaceAllNonRegex(artifact.groupId, ".", "/")+"/"+ + artifact.artifactId+"/"+artifact.version+"/"+ + artifact.getFilename(); + } + }; + + protected Function<MavenArtifact,String> snapshotUrlGenerator = CONDITIONAL_SNAPSHOT_URL_GENERATOR; + protected Function<MavenArtifact,String> releaseUrlGenerator = MAVEN_CENTRAL_URL_GENERATOR; + + public void setSnapshotUrlGenerator(Function<MavenArtifact, String> snapshotUrlGenerator) { + this.snapshotUrlGenerator = snapshotUrlGenerator; + } + + public void setReleaseUrlGenerator(Function<MavenArtifact, String> releaseUrlGenerator) { + this.releaseUrlGenerator = releaseUrlGenerator; + } + + public String getHostedUrl(MavenArtifact artifact) { + if (artifact.isSnapshot()) return snapshotUrlGenerator.apply(artifact); + else return releaseUrlGenerator.apply(artifact); + } + + public String getLocalPath(MavenArtifact artifact) { + return LOCAL_REPO_PATH_GENERATOR.apply(artifact); + } + + public boolean isInstalledLocally(MavenArtifact artifact) { + return new File(getLocalPath(artifact)).exists(); + } + + /** returns a URL for accessing the given artifact, preferring a local file if available, + * else generating a hosted URL (but not checking) */ + public String getLocalUrl(MavenArtifact artifact) { + if (isInstalledLocally(artifact)) return new File(getLocalPath(artifact)).toURI().toString(); + if (artifact.isSnapshot()) return snapshotUrlGenerator.apply(artifact); + else return releaseUrlGenerator.apply(artifact); + } + + /** returns a URL for accessing the artifact from the local machine (ie preferring a local repo), + * using the default remote sits (sonatype for snapshots and maven.org for releases) */ + public static String localUrl(MavenArtifact artifact) { + return new MavenRetriever().getLocalUrl(artifact); + } + + /** returns a URL for accessing the artifact from any machine (ie not allowing a local repo), + * using the default remote sits (sonatype for snapshots and maven.org for releases) */ + public static String hostedUrl(MavenArtifact artifact) { + return new MavenRetriever().getHostedUrl(artifact); + } + + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/net/Cidr.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/Cidr.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/Cidr.java new file mode 100644 index 0000000..d7b564a --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/Cidr.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.net; + + +import java.io.Serializable; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.List; + +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.math.BitList; +import org.apache.brooklyn.util.math.BitUtils; +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.collect.ImmutableList; + +/** represents a CIDR (classless inter-domain routing) token, i.e. 10.0.0.0/8 or 192.168.4.0/24 */ +public class Cidr implements Serializable { + + private static final long serialVersionUID = -4605909101590811958L; + + /** 0.0.0.0/0 -- matches all addresses */ + public static final Cidr UNIVERSAL = new Cidr(); + + public static final Cidr _10 = new Cidr(10); + public static final Cidr _172_16 = new Cidr("172.16.0.0/12"); + public static final Cidr _192_168 = new Cidr(192, 168); + public static final Cidr CLASS_A = _10; + public static final Cidr CLASS_B = _172_16; + public static final Cidr CLASS_C = _192_168; + + public static final List<Cidr> PRIVATE_NETWORKS_RFC_1918 = ImmutableList.<Cidr>of(_192_168, _172_16, _10); + + public static final Cidr _169_254 = new Cidr("169.254.0.0/16"); + public static final Cidr LINK_LOCAL = _169_254; + + public static final Cidr _127 = new Cidr("127.0.0.0/8"); + public static final Cidr LOOPBACK = _127; + + public static final List<Cidr> NON_PUBLIC_CIDRS = ImmutableList.<Cidr>builder().addAll(PRIVATE_NETWORKS_RFC_1918).add(LINK_LOCAL).add(LOOPBACK).build(); + + + final int[] subnetBytes = new int[] { 0, 0, 0, 0 }; + final int length; + + + public Cidr(String cidr) { + if (Strings.isBlank(cidr)) + // useful e.g. if user leaves it blank in gui + cidr = "0.0.0.0/0"; + int slash = cidr.indexOf('/'); + if (slash==-1) throw new IllegalArgumentException("CIDR should be of form 192.168.0.0/16 (missing slash); input="+cidr); + String subnet = cidr.substring(0, slash); + String lengthS = cidr.substring(slash+1); + this.length = Integer.parseInt(lengthS); + String[] bytes = subnet.split("\\."); + int i=0; + for (; i<this.length/8; i++) + subnetBytes[i] = Integer.parseInt(bytes[i]); + for (; i<(this.length+7)/8; i++) + // for fractional parts: reverse significance, trim, reverse back + subnetBytes[i] = + BitUtils.unsigned( BitUtils.reverseBitSignificanceInByte( + BitList.newInstanceFromBytes(BitUtils.reverseBitSignificanceInByte(Integer.parseInt(bytes[i]))). + resized(this.length % 8).intValue() )); + } + + /** returns true iff this CIDR is well-formed and canonical, + * i.e. 4 dot-separated bytes followed by a slash and a length, + * where length is <= 32, and the preceding 4 bytes don't include any 1 bits beyond the indicated length; + * e.g. 10.0.0.0/8 -- but not 10.0.0.1/8 or 10.../8 + * (although the latter ones are accepted by the constructor and converted to the canonical CIDR) */ + public static boolean isCanonical(String cidr) { + try { + return cidr.equals(new Cidr(cidr).toString()); + } catch (Throwable e) { + Exceptions.propagateIfFatal(e); + return false; + } + } + + /** allows creation as Cidr(192, 168) for 192.168.0.0/16; + * zero bits or ints included are significant, i.e. Cidr(10, 0) gives 10.0.0.0/16 */ + public Cidr(int ...unsignedBytes) { + length = unsignedBytes.length*8; + System.arraycopy(unsignedBytes, 0, subnetBytes, 0, unsignedBytes.length); + } + + public Cidr(int[] subnetBytes, int length) { + this.length = length; + if (subnetBytes.length>4) + throw new IllegalArgumentException("Cannot create CIDR beyond 4 bytes: "+Arrays.toString(subnetBytes)); + if (length>32) + throw new IllegalArgumentException("Cannot create CIDR beyond 4 bytes: length "+length); + // reverse the bits to remove zeroed bits, then reverse back + byte[] significantSubnetBytes = BitList.newInstance(BitUtils.reverseBitSignificanceInBytes(subnetBytes)).resized(length).asBytes(); + for (int i=0; i<significantSubnetBytes.length; i++) + this.subnetBytes[i] = BitUtils.unsigned(BitUtils.reverseBitSignificance(significantSubnetBytes[i])); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + length; + result = prime * result + Arrays.hashCode(subnetBytes); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Cidr other = (Cidr) obj; + if (length != other.length) + return false; + if (!Arrays.equals(subnetBytes, other.subnetBytes)) + return false; + return true; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + int i=0; + while (i<(length+7)/8) { + if (sb.length()>0) sb.append("."); + sb.append(""+subnetBytes[i]); + i++; + } + while (i<4) { + if (sb.length()>0) sb.append("."); + sb.append("0"); + i++; + } + sb.append("/"); + sb.append(""+length); + return sb.toString(); + } + + public int[] getBytes() { + return Arrays.copyOf(subnetBytes, 4); + } + + public int getLength() { + return length; + } + + public Cidr subnet(int ...extraUnsignedBytes) { + if ((length%8)!=0) throw new IllegalStateException("subnet can only be used for byte boundary subnetted CIDR's; not "+this); + int[] newBytes = getBytes(); + int newLen = this.length + extraUnsignedBytes.length*8; + if (newLen>32) throw new IllegalStateException("further subnet for "+Arrays.toString(extraUnsignedBytes)+" not possible on CIDR "+this); + for (int i=0; i<extraUnsignedBytes.length; i++) { + newBytes[this.length/8 + i] = extraUnsignedBytes[i]; + } + return new Cidr(newBytes, newLen); + } + + /** returns the netmask for this cidr; e.g. for a /24 cidr returns 255.255.255.0 */ + public InetAddress netmask() { + final byte[] netmaskBytes = new byte[] { 0, 0, 0, 0 }; + int lengthLeft = length; + int i=0; + while (lengthLeft>0) { + if (lengthLeft>=8) { + netmaskBytes[i] = (byte)255; + } else { + netmaskBytes[i] = (byte) ( (1 << lengthLeft) - 1 ); + } + lengthLeft -= 8; + i++; + } + return Networking.getInetAddressWithFixedName(netmaskBytes); + } + + /** taking the addresses in the CIDR in order, returns the one in the offset^th position + * (starting with the CIDR itself, even if final bits are 0) */ + public InetAddress addressAtOffset(int offset) { + int[] ints = getBytes(); + ints[3] += offset; + { + int i=3; + while (ints[i]>=256) { + ints[i-1] += (ints[i] / 256); + ints[i] %= 256; + i--; + } + } + byte[] bytes = new byte[] { 0, 0, 0, 0 }; + for (int i=0; i<4; i++) + bytes[i] = (byte) ints[i]; + return Networking.getInetAddressWithFixedName(bytes); + } + + /** returns length of the prefix in common between the two cidrs */ + public int commonPrefixLength(Cidr other) { + return asBitList().commonPrefixLength(other.asBitList()); + } + + public Cidr commonPrefix(Cidr other) { + return new Cidr(other.getBytes(), commonPrefixLength(other)); + } + + /** returns list of bits for the significant (length) bits of this CIDR */ + public BitList asBitList() { + return BitList.newInstance(BitUtils.reverseBitSignificanceInBytes(getBytes())).resized(getLength()); + } + + public boolean contains(Cidr target) { + return commonPrefixLength(target) == getLength(); + } + + // FIXME remove from here, promote NetworkUtils + /** @deprecated use {@link Networking#getInetAddressWithFixedName(byte[])} */ + @Deprecated + public static InetAddress getInetAddressWithFixedName(byte[] ip) { + return Networking.getInetAddressWithFixedName(ip); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/net/HasNetworkAddresses.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/HasNetworkAddresses.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/HasNetworkAddresses.java new file mode 100644 index 0000000..397570f --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/HasNetworkAddresses.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.net; + +import java.util.Set; + +import javax.annotation.Nullable; + +import com.google.common.annotations.Beta; + +@Beta +public interface HasNetworkAddresses { + + /** + * <h4>note</h4> hostname is something that is set in the operating system. + * This value may or may not be set in DNS. + * + * @return hostname of the node, or null if unknown + */ + @Nullable + String getHostname(); + + /** + * All public IP addresses, potentially including shared ips. + */ + Set<String> getPublicAddresses(); + + /** + * All private IP addresses. + */ + Set<String> getPrivateAddresses(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/net/NetworkMultiAddressUtils.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/NetworkMultiAddressUtils.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/NetworkMultiAddressUtils.java new file mode 100644 index 0000000..2babf1a --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/NetworkMultiAddressUtils.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.net; + +import java.util.Collection; + +/** + * Given several strings, determines which have the longest, and shorted, initial matching prefix. + * Particularly useful as a poor-man's way to determine which IP's are likely to be the same subnet. + */ +public class NetworkMultiAddressUtils { + + // TODO should convert to byte arrays then binary, and look at binary digit match length ! + +// public static Collection<String> sortByInitialSimilarity(final String pattern, Collection<String> targets) { +// List<String> result = new ArrayList<String>(targets); +// Collections.sort(result, new Comparator<String>() { +// @Override +// public int compare(String o1, String o2) { +// int i1 = 0; int i2 = 0; +// for (int i=0; i<pattern.length(); i++) { +// if (o1.length()<i || o2.length()<i) break; +// if (o1.substring(0, i).equals(anObject)) +// } +// } +// }); +// return result; +// } + + public static String getClosest(final String pattern, Collection<String> targets) { + int score = -1; + String best = null; + for (String target: targets) { + int thisScore = matchLength(pattern, target); + if (thisScore > score) { + score = thisScore; + best = target; + } + } + return best; + } + + public static String getFurthest(final String pattern, Collection<String> targets) { + int score = 65535; + String best = null; + for (String target: targets) { + int thisScore = matchLength(pattern, target); + if (thisScore < score) { + score = thisScore; + best = target; + } + } + return best; + } + + private static int matchLength(String pattern, String target) { + int i=0; + while (i<pattern.length() && i<target.length() && pattern.charAt(i)==target.charAt(i)) + i++; + return i; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java new file mode 100644 index 0000000..695e3f8 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java @@ -0,0 +1,547 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.net; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; +import com.google.common.base.Throwables; +import com.google.common.net.HostAndPort; +import com.google.common.primitives.UnsignedBytes; + +import static com.google.common.base.Preconditions.checkArgument; + +public class Networking { + + private static final Logger log = LoggerFactory.getLogger(Networking.class); + + public static final int MIN_PORT_NUMBER = 1; + public static final int MAX_PORT_NUMBER = 65535; + + // based on http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address + // but updated to allow leading zeroes + public static final String VALID_IP_ADDRESS_REGEX = "^((0*[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(0*[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"; + + public static final Pattern VALID_IP_ADDRESS_PATTERN; + static { + VALID_IP_ADDRESS_PATTERN = Pattern.compile(VALID_IP_ADDRESS_REGEX); + } + + public static final List<Cidr> PRIVATE_NETWORKS = Cidr.PRIVATE_NETWORKS_RFC_1918; + + public static InetAddress ANY_NIC = getInetAddressWithFixedName(0, 0, 0, 0); + public static InetAddress LOOPBACK = getInetAddressWithFixedName(127, 0, 0, 1); + + public static boolean isPortAvailable(int port) { + return isPortAvailable(ANY_NIC, port); + } + public static boolean isPortAvailable(InetAddress localAddress, int port) { + if (port < MIN_PORT_NUMBER || port > MAX_PORT_NUMBER) { + throw new IllegalArgumentException("Invalid start port: " + port); + } + + Stopwatch watch = Stopwatch.createStarted(); + try { + //despite http://stackoverflow.com/questions/434718/sockets-discover-port-availability-using-java + //(recommending the following) it isn't 100% reliable (e.g. nginx will happily coexist with ss+ds) + // + //Svet - SO_REUSEADDR (enabled below) will allow one socket to listen on 0.0.0.0:X and another on + //192.168.0.1:X which explains the above comment (nginx sets SO_REUSEADDR as well). Moreover there + //is no TIME_WAIT for listening sockets without any connections so why enable it at all. + ServerSocket ss = null; + DatagramSocket ds = null; + try { + // Check TCP port + ss = new ServerSocket(); + ss.setSoTimeout(250); + ss.setReuseAddress(true); + ss.bind(new InetSocketAddress(localAddress, port)); + + // Check UDP port + ds = new DatagramSocket(null); + ds.setSoTimeout(250); + ds.setReuseAddress(true); + ds.bind(new InetSocketAddress(localAddress, port)); + } catch (IOException e) { + if (log.isTraceEnabled()) log.trace("Failed binding to " + localAddress + " : " + port, e); + return false; + } finally { + closeQuietly(ds); + closeQuietly(ss); + } + + if (localAddress==null || ANY_NIC.equals(localAddress)) { + // sometimes 0.0.0.0 can be bound to even if 127.0.0.1 has the port as in use; + // check all interfaces if 0.0.0.0 was requested + Enumeration<NetworkInterface> nis = null; + try { + nis = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + throw Exceptions.propagate(e); + } + // When using a specific interface saw failures not caused by port already bound: + // * java.net.SocketException: No such device + // * java.net.BindException: Cannot assign requested address + // * probably many more + // Check if the address is still valid before marking the port as not available. + boolean foundAvailableInterface = false; + while (nis.hasMoreElements()) { + NetworkInterface ni = nis.nextElement(); + Enumeration<InetAddress> as = ni.getInetAddresses(); + while (as.hasMoreElements()) { + InetAddress a = as.nextElement(); + if (!isPortAvailable(a, port)) { + if (isAddressValid(a)) { + if (log.isTraceEnabled()) log.trace("Port {} : {} @ {} is taken and the address is valid", new Object[] {a, port, nis}); + return false; + } + } else { + foundAvailableInterface = true; + } + } + } + if (!foundAvailableInterface) { + //Aborting with an error, even nextAvailablePort won't be able to find a free port. + throw new RuntimeException("Unable to bind on any network interface, even when letting the OS pick a port. Possible causes include file handle exhaustion, port exhaustion. Failed on request for " + localAddress + ":" + port + "."); + } + } + + return true; + } finally { + // Until timeout was added, was taking 1min5secs for /fe80:0:0:0:1cc5:1ff:fe81:a61d%8 : 8081 + // Svet - Probably caused by the now gone new Socket().connect() call, SO_TIMEOUT doesn't + // influence bind(). Doesn't hurt having it though. + long elapsed = watch.elapsed(TimeUnit.SECONDS); + boolean isDelayed = (elapsed >= 1); + boolean isDelayedByMuch = (elapsed >= 30); + if (isDelayed || log.isTraceEnabled()) { + String msg = "Took {} to determine if port was available for {} : {}"; + Object[] args = new Object[] {Time.makeTimeString(watch.elapsed(TimeUnit.MILLISECONDS), true), localAddress, port}; + if (isDelayedByMuch) { + log.warn(msg, args); + } else if (isDelayed) { + log.debug(msg, args); + } else { + log.trace(msg, args); + } + } + } + } + + /** + * Bind to the specified IP, but let the OS pick a port. + * If the operation fails we know it's not because of + * non-available port, the interface could be down. + * + * If there's port exhaustion on a single interface we won't catch it + * and declare the port is free. Doesn't matter really because the + * subsequent bind of the caller will fail anyway and nextAvailablePort + * wouldn't be able to find a free one either. + */ + private static boolean isAddressValid(InetAddress addr) { + ServerSocket ss; + try { + ss = new ServerSocket(); + ss.setSoTimeout(250); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + try { + ss.bind(new InetSocketAddress(addr, 0)); + return true; + } catch (IOException e) { + if (log.isTraceEnabled()) log.trace("Binding on {} failed, interface could be down, being reconfigured, file handle exhaustion, port exhaustion, etc.", addr); + return false; + } finally { + closeQuietly(ss); + } + } + + /** returns the first port available on the local machine >= the port supplied */ + public static int nextAvailablePort(int port) { + checkArgument(port >= MIN_PORT_NUMBER && port <= MAX_PORT_NUMBER, "requested port %s is outside the valid range of %s to %s", port, MIN_PORT_NUMBER, MAX_PORT_NUMBER); + int originalPort = port; + while (!isPortAvailable(port) && port < MAX_PORT_NUMBER) port++; + if (port >= MAX_PORT_NUMBER) + throw new RuntimeException("unable to find a free port at or above " + originalPort); + return port; + } + + public static boolean isPortValid(Integer port) { + return (port!=null && port>=Networking.MIN_PORT_NUMBER && port<=Networking.MAX_PORT_NUMBER); + } + public static int checkPortValid(Integer port, String errorMessage) { + if (!isPortValid(port)) { + throw new IllegalArgumentException("Invalid port value "+port+": "+errorMessage); + } + return port; + } + + public static void checkPortsValid(Map<?, ?> ports) { + for (Map.Entry<?,?> entry : ports.entrySet()) { + Object val = entry.getValue(); + if (val == null){ + throw new IllegalArgumentException("port for "+entry.getKey()+" is null"); + } else if (!(val instanceof Integer)) { + throw new IllegalArgumentException("port "+val+" for "+entry.getKey()+" is not an integer ("+val.getClass()+")"); + } + checkPortValid((Integer)val, ""+entry.getKey()); + } + } + + /** + * Check if this is a private address, not exposed on the public Internet. + * + * For IPV4 addresses this is an RFC1918 subnet (site local) address ({@code 10.0.0.0/8}, + * {@code 172.16.0.0/12} and {@code 192.168.0.0/16}), a link-local address + * ({@code 169.254.0.0/16}) or a loopback address ({@code 127.0.0.1/0}). + * <p> + * For IPV6 addresses this is the RFC3514 link local block ({@code fe80::/10}) + * and site local block ({@code feco::/10}) or the loopback block + * ({@code ::1/128}). + * + * @return true if the address is private + */ + public static boolean isPrivateSubnet(InetAddress address) { + return address.isSiteLocalAddress() || address.isLoopbackAddress() || address.isLinkLocalAddress(); + } + + /** Check whether this address is definitely not going to be usable on any other machine; + * i.e. if it is a loopback address or a link-local (169.254) + */ + public static boolean isLocalOnly(InetAddress address) { + return address.isLoopbackAddress() || address.isLinkLocalAddress(); + } + + /** As {@link #isLocalOnly(InetAddress)} but taking a string; + * does not require the string to be resolvable, and generally treats non-resolvable hostnames as NOT local-only + * (although they are treated as private by {@link #isPrivateSubnet(String)}), + * although certain well-known hostnames are recognised as local-only + * <p> + * note however {@link InetAddress#getByName(String)} can ignore settings in /etc/hosts, on OS X at least, + * and give different values than the system */ + public static boolean isLocalOnly(String hostnameOrIp) { + Preconditions.checkNotNull(hostnameOrIp, "hostnameOrIp"); + if ("127.0.0.1".equals(hostnameOrIp)) return true; + if ("localhost".equals(hostnameOrIp)) return true; + if ("localhost.localdomain".equals(hostnameOrIp)) return true; + try { + InetAddress ia = getInetAddressWithFixedName(hostnameOrIp); + return isLocalOnly(ia); + } catch (Exception e) { + log.debug("Networking cannot resolve "+hostnameOrIp+": assuming it is not a local-only address, but it is a private address"); + return false; + } + } + + /** As {@link #isPrivateSubnet(InetAddress)} but taking a string; sepcifically local-only address ARE treated as private. + * does not require the string to be resolvable, and things which aren't resolvable are treated as private + * unless they are known to be local-only */ + public static boolean isPrivateSubnet(String hostnameOrIp) { + Preconditions.checkNotNull(hostnameOrIp, "hostnameOrIp"); + try { + InetAddress ia = getInetAddressWithFixedName(hostnameOrIp); + return isPrivateSubnet(ia); + } catch (Exception e) { + log.debug("Networking cannot resolve "+hostnameOrIp+": assuming it IS a private address"); + return true; + } + } + + private static boolean triedUnresolvableHostname = false; + private static String cachedAddressOfUnresolvableHostname = null; + + /** returns null in a sane DNS environment, but if DNS provides a bogus address for made-up hostnames, this returns that address */ + public synchronized static String getAddressOfUnresolvableHostname() { + if (triedUnresolvableHostname) return cachedAddressOfUnresolvableHostname; + String h = "noexistent-machine-"+Identifiers.makeRandomBase64Id(8); + try { + cachedAddressOfUnresolvableHostname = InetAddress.getByName(h).getHostAddress(); + log.info("Networking detected "+cachedAddressOfUnresolvableHostname+" being returned by DNS for bogus hostnames ("+h+")"); + } catch (Exception e) { + log.debug("Networking detected failure on DNS resolution of unknown hostname ("+h+" throws "+e+")"); + cachedAddressOfUnresolvableHostname = null; + } + triedUnresolvableHostname = true; + return cachedAddressOfUnresolvableHostname; + } + + /** resolves the given hostname to an IP address, returning null if unresolvable or + * if the resolution is bogus (eg 169.* subnet or a "catch-all" IP resolution supplied by some miscreant DNS services) */ + public static InetAddress resolve(String hostname) { + try { + InetAddress a = InetAddress.getByName(hostname); + if (a==null) return null; + String ha = a.getHostAddress(); + if (log.isDebugEnabled()) log.debug("Networking resolved "+hostname+" as "+a); + if (ha.equals(getAddressOfUnresolvableHostname())) return null; + if (ha.startsWith("169.")) return null; + return a; + } catch (Exception e) { + if (log.isDebugEnabled()) log.debug("Networking failed to resolve "+hostname+", threw "+e); + return null; + } + } + + /** + * Gets an InetAddress using the given IP, and using that IP as the hostname (i.e. avoids any hostname resolution). + * <p> + * This is very useful if using the InetAddress for updating config files on remote machines, because then it will + * not be pickup a hostname from the local /etc/hosts file, which might not be known on the remote machine. + */ + public static InetAddress getInetAddressWithFixedName(byte[] ip) { + try { + StringBuilder name = new StringBuilder(); + for (byte part : ip) { + if (name.length() > 0) name.append("."); + name.append(part); + } + return InetAddress.getByAddress(name.toString(), ip); + } catch (UnknownHostException e) { + throw Throwables.propagate(e); + } + } + + public static InetAddress getInetAddressWithFixedName(int ip1, int ip2, int ip3, int ip4) { + return getInetAddressWithFixedName(asByteArray(ip1, ip2, ip3, ip4)); + } + + public static InetAddress getInetAddressWithFixedName(int ip1, int ip2, int ip3, int ip4, int ip5, int ip6) { + return getInetAddressWithFixedName(asByteArray(ip1, ip2, ip3, ip4, ip5, ip6)); + } + + /** creates a byte array given a var-arg number of (or bytes or longs); + * checks that all values are valid as _unsigned_ bytes (i.e. in [0,255] ) */ + public static byte[] asByteArray(long ...bytes) { + byte[] result = new byte[bytes.length]; + for (int i=0; i<bytes.length; i++) + result[i] = UnsignedBytes.checkedCast(bytes[i]); + return result; + } + + /** checks whether given string matches a valid numeric IP (v4) address, e.g. 127.0.0.1, + * but not localhost or 1.2.3.256 */ + public static boolean isValidIp4(String input) { + return VALID_IP_ADDRESS_PATTERN.matcher(input).matches(); + } + + /** + * Gets an InetAddress using the given hostname or IP. If it is an IPv4 address, then this is equivalent + * to {@link getInetAddressWithFixedName(byte[])}. If it is a hostname, then this hostname will be used + * in the returned InetAddress. + */ + public static InetAddress getInetAddressWithFixedName(String hostnameOrIp) { + try { + if (isValidIp4(hostnameOrIp)) { + byte[] ip = new byte[4]; + String[] parts = hostnameOrIp.split("\\."); + assert parts.length == 4 : "val="+hostnameOrIp+"; split="+Arrays.toString(parts)+"; length="+parts.length; + for (int i = 0; i < parts.length; i++) { + ip[i] = (byte)Integer.parseInt(parts[i]); + } + return InetAddress.getByAddress(hostnameOrIp, ip); + } else { + return InetAddress.getByName(hostnameOrIp); + } + } catch (UnknownHostException e) { + throw Throwables.propagate(e); + } + } + + /** returns local IP address, or 127.0.0.1 if it cannot be parsed */ + public static InetAddress getLocalHost() { + try { + return InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + InetAddress result = null; + result = getInetAddressWithFixedName("127.0.0.1"); + log.warn("Localhost is not resolvable; using "+result); + return result; + } + } + + /** returns all local addresses */ + public static Map<String,InetAddress> getLocalAddresses() { + Map<String, InetAddress> result = new LinkedHashMap<String, InetAddress>(); + Enumeration<NetworkInterface> ne; + try { + ne = NetworkInterface.getNetworkInterfaces(); + } catch (SocketException e) { + log.warn("Local network interfaces are not resolvable: "+e); + ne = null; + } + while (ne != null && ne.hasMoreElements()) { + NetworkInterface nic = ne.nextElement(); + Enumeration<InetAddress> inets = nic.getInetAddresses(); + while (inets.hasMoreElements()) { + InetAddress inet = inets.nextElement(); + result.put(inet.getHostAddress(), inet); + } + } + if (result.isEmpty()) { + log.warn("No local network addresses found; assuming 127.0.0.1"); + InetAddress loop = Cidr.LOOPBACK.addressAtOffset(0); + result.put(loop.getHostAddress(), loop); + } + return result; + } + + /** returns a CIDR object for the given string, e.g. "10.0.0.0/8" */ + public static Cidr cidr(String cidr) { + return new Cidr(cidr); + } + + /** returns any well-known private network (e.g. 10.0.0.0/8 or 192.168.0.0/16) + * which the given IP is in, or the /32 of local address if none */ + public static Cidr getPrivateNetwork(String ip) { + Cidr me = new Cidr(ip+"/32"); + for (Cidr c: PRIVATE_NETWORKS) + if (c.contains(me)) + return c; + return me; + } + + public static Cidr getPrivateNetwork(InetAddress address) { + return getPrivateNetwork(address.getHostAddress()); + } + + /** returns whether the IP is _not_ in any private subnet */ + public static boolean isPublicIp(String ipAddress) { + Cidr me = new Cidr(ipAddress+"/32"); + for (Cidr c: Cidr.NON_PUBLIC_CIDRS) + if (c.contains(me)) return false; + return true; + } + + /** returns true if the supplied string matches any known IP (v4 or v6) for this machine, + * or if it can be resolved to any such address */ + public static boolean isLocalhost(String remoteAddress) { + Map<String, InetAddress> addresses = getLocalAddresses(); + if (addresses.containsKey(remoteAddress)) return true; + + if ("127.0.0.1".equals(remoteAddress)) return true; + + String modifiedIpV6Address = remoteAddress; + // IPv6 localhost "ip" strings may vary; + // comes back as 0:0:0:0:0:0:0:1%1 for me. + // following deals with the cases which seem likely. + // (svet suggests using InetAddress parsing but I -- Alex -- am not sure if that's going to have it's own bugs) + if (modifiedIpV6Address.contains("%")) { + // trim any description %dex + modifiedIpV6Address = modifiedIpV6Address.substring(0, modifiedIpV6Address.indexOf("%")); + } + if ("0:0:0:0:0:0:0:1".equals(modifiedIpV6Address)) return true; + if ("::1".equals(modifiedIpV6Address)) return true; + if (addresses.containsKey(remoteAddress) || addresses.containsKey(modifiedIpV6Address)) + return true; + + try { + InetAddress remote = InetAddress.getByName(remoteAddress); + if (addresses.values().contains(remote)) + return true; + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + log.debug("Error resolving address "+remoteAddress+" when checking if it is local (assuming not: "+e, e); + } + + return false; + } + + public static boolean isReachable(HostAndPort endpoint) { + try { + Socket s = new Socket(endpoint.getHostText(), endpoint.getPort()); + closeQuietly(s); + return true; + } catch (Exception e) { + if (log.isTraceEnabled()) log.trace("Error reaching "+endpoint+" during reachability check (return false)", e); + return false; + } + } + + public static void closeQuietly(Socket s) { + if (s != null) { + try { + s.close(); + } catch (IOException e) { + /* should not be thrown */ + } + } + } + + public static void closeQuietly(ServerSocket s) { + if (s != null) { + try { + s.close(); + } catch (IOException e) { + /* should not be thrown */ + } + } + } + + public static void closeQuietly(DatagramSocket s) { + if (s != null) { + s.close(); + } + } + + + // TODO go through nic's, looking for public, private, etc, on localhost + + /** + * force use of TLSv1, fixing: + * http://stackoverflow.com/questions/9828414/receiving-sslhandshakeexception-handshake-failure-despite-my-client-ignoring-al + */ + public static void installTlsOnlyForHttpsForcing() { + System.setProperty("https.protocols", "TLSv1"); + } + public static void installTlsForHttpsIfAppropriate() { + if (System.getProperty("https.protocols")==null && System.getProperty("brooklyn.https.protocols.leave_untouched")==null) { + installTlsOnlyForHttpsForcing(); + } + } + static { + installTlsForHttpsIfAppropriate(); + } + + /** does nothing, but forces the class to be loaded and do static initialization */ + public static void init() {} + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/net/Protocol.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/Protocol.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/Protocol.java new file mode 100644 index 0000000..52efd94 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/Protocol.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.net; + +public enum Protocol { + + TCP("tcp"), + UDP("udp"), + ICMP("icmp"), + ALL("all"); + + final String protocol; + + private Protocol(String protocol) { + this.protocol = protocol; + } + + @Override + public String toString() { + return protocol; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/net/URLParamEncoder.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/URLParamEncoder.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/URLParamEncoder.java new file mode 100644 index 0000000..5c761aa --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/URLParamEncoder.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.net; + +/** + * Encodes URLs, escaping as appropriate. + * + * Copied from fmucar's answer in http://stackoverflow.com/questions/724043/http-url-address-encoding-in-java + * + * TODO Want to use a library utility, but couldn't find this in guava and don't want to introduce + * dependency on commons-httpclient-3.1 to use URIUtil. + * + * @author aled + */ +public class URLParamEncoder { + + public static String encode(String input) { + StringBuilder resultStr = new StringBuilder(); + for (char ch : input.toCharArray()) { + if (isUnsafe(ch)) { + resultStr.append('%'); + resultStr.append(toHex(ch / 16)); + resultStr.append(toHex(ch % 16)); + } else { + resultStr.append(ch); + } + } + return resultStr.toString(); + } + + private static char toHex(int ch) { + return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10); + } + + private static boolean isUnsafe(char ch) { + if (ch > 128 || ch < 0) + return true; + return (" %$&+,/:;=?@<>#%" + // these are included in httpclient URI as "unwise", and have been found to be problematic + // * backslash in a query param breaks URI.create + + "\\" + ).indexOf(ch) >= 0; + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/net/Urls.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/Urls.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/Urls.java new file mode 100644 index 0000000..0d4af35 --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/Urls.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.net; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.util.text.Strings; + +import com.google.common.base.Function; +import com.google.common.base.Throwables; +import com.google.common.io.BaseEncoding; +import com.google.common.net.MediaType; + +public class Urls { + + public static Function<String,URI> stringToUriFunction() { + return StringToUri.INSTANCE; + } + + public static Function<String,URL> stringToUrlFunction() { + return StringToUrl.INSTANCE; + } + + private static enum StringToUri implements Function<String,URI> { + INSTANCE; + @Override public URI apply(@Nullable String input) { + return toUri(input); + } + @Override + public String toString() { + return "StringToUri"; + } + } + + private static enum StringToUrl implements Function<String,URL> { + INSTANCE; + @Override public URL apply(@Nullable String input) { + return toUrl(input); + } + @Override + public String toString() { + return "StringToUrl"; + } + } + + /** creates a URL, preserving null and propagating exceptions *unchecked* */ + public static final URL toUrl(@Nullable String url) { + if (url==null) return null; + try { + return new URL(url); + } catch (MalformedURLException e) { + // FOAD + throw Throwables.propagate(e); + } + } + + /** creates a URL, preserving null and propagating exceptions *unchecked* */ + public static final URL toUrl(@Nullable URI uri) { + if (uri==null) return null; + try { + return uri.toURL(); + } catch (MalformedURLException e) { + // FOAD + throw Throwables.propagate(e); + } + } + + /** creates a URI, preserving null and propagating exceptions *unchecked* */ + public static final URI toUri(@Nullable String uri) { + if (uri==null) return null; + return URI.create(uri); + } + + /** creates a URI, preserving null and propagating exceptions *unchecked* */ + public static final URI toUri(@Nullable URL url) { + if (url==null) return null; + try { + return url.toURI(); + } catch (URISyntaxException e) { + // FOAD + throw Throwables.propagate(e); + } + } + + /** returns true if the string begins with a non-empty string of letters followed by a colon, + * i.e. "protocol:" returns true, but "/" returns false */ + public static boolean isUrlWithProtocol(String x) { + if (x==null) return false; + for (int i=0; i<x.length(); i++) { + char c = x.charAt(i); + if (c==':') return i>0; + if (!Character.isLetter(c)) return false; + } + return false; + } + + /** returns the items with exactly one "/" between items (whether or not the individual items start or end with /), + * except where character before the / is a : (url syntax) in which case it will permit multiple (will not remove any). + * Throws a NullPointerException if any elements of 'items' is null. + * */ + public static String mergePaths(String ...items) { + List<String> parts = Arrays.asList(items); + + if (parts.contains(null)) { + throw new NullPointerException(String.format("Unable to reliably merge path from parts: %s; input contains null values", parts)); + } + + StringBuilder result = new StringBuilder(); + for (String part: parts) { + boolean trimThisMerge = result.length()>0 && !result.toString().endsWith("://") && !result.toString().endsWith(":///") && !result.toString().endsWith(":"); + if (trimThisMerge) { + while (result.length()>0 && result.charAt(result.length()-1)=='/') + result.deleteCharAt(result.length()-1); + result.append('/'); + } + int i = result.length(); + result.append(part); + if (trimThisMerge) { + while (result.length()>i && result.charAt(i)=='/') + result.deleteCharAt(i); + } + } + return result.toString(); + } + + /** encodes the string suitable for use in a URL, using default character set + * (non-deprecated version of URLEncoder.encode) */ + @SuppressWarnings("deprecation") + public static String encode(String text) { + return URLEncoder.encode(text); + } + /** As {@link #encode(String)} */ + @SuppressWarnings("deprecation") + public static String decode(String text) { + return URLDecoder.decode(text); + } + + /** returns the protocol (e.g. http) if one appears to be specified, or else null; + * 'protocol' here should consist of 2 or more _letters_ only followed by a colon + * (2 required to prevent {@code c:\xxx} being treated as a url) + */ + public static String getProtocol(String url) { + if (url==null) return null; + int i=0; + StringBuilder result = new StringBuilder(); + while (true) { + if (url.length()<=i) return null; + char c = url.charAt(i); + if (Character.isLetter(c)) result.append(c); + else if (c==':') { + if (i>=2) return result.toString().toLowerCase(); + return null; + } else return null; + i++; + } + } + + /** return the last segment of the given url before any '?', e.g. the filename or last directory name in the case of directories + * (cf unix `basename`) */ + public static String getBasename(String url) { + if (url==null) return null; + if (getProtocol(url)!=null) { + int firstQ = url.indexOf('?'); + if (firstQ>=0) + url = url.substring(0, firstQ); + } + url = Strings.removeAllFromEnd(url, "/"); + return url.substring(url.lastIndexOf('/')+1); + } + + public static boolean isDirectory(String fileUrl) { + File file; + if (isUrlWithProtocol(fileUrl)) { + if (getProtocol(fileUrl).equals("file")) { + file = new File(URI.create(fileUrl)); + } else { + return false; + } + } else { + file = new File(fileUrl); + } + return file.isDirectory(); + } + + public static File toFile(String fileUrl) { + if (isUrlWithProtocol(fileUrl)) { + if (getProtocol(fileUrl).equals("file")) { + return new File(URI.create(fileUrl)); + } else { + throw new IllegalArgumentException("Not a file protocol URL: " + fileUrl); + } + } else { + return new File(fileUrl); + } + } + + /** as {@link #asDataUrlBase64(String)} with plain text */ + public static String asDataUrlBase64(String data) { + return asDataUrlBase64(MediaType.PLAIN_TEXT_UTF_8, data.getBytes()); + } + + /** + * Creates a "data:..." scheme URL for use with supported parsers, using Base64 encoding. + * (But note, by default Java's URL is not one of them, although Brooklyn's ResourceUtils does support it.) + * <p> + * It is not necessary (at least for Brookyn's routines) to base64 encode it, but recommended as that is likely more + * portable and easier to work with if odd characters are included. + * <p> + * It is worth noting that Base64 uses '+' which can be replaced by ' ' in some URL parsing. + * But in practice it does not seem to cause issues. + * An alternative is to use {@link BaseEncoding#base64Url()} but it is not clear how widely that is supported + * (nor what parameter should be given to indicate that type of encoding, as the spec calls for 'base64'!) + * <p> + * null type means no type info will be included in the URL. */ + public static String asDataUrlBase64(MediaType type, byte[] bytes) { + return "data:"+(type!=null ? type.withoutParameters().toString() : "")+";base64,"+new String(BaseEncoding.base64().encode(bytes)); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/cf2f7a93/utils/common/src/main/java/org/apache/brooklyn/util/net/UserAndHostAndPort.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/UserAndHostAndPort.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/UserAndHostAndPort.java new file mode 100644 index 0000000..d48dccd --- /dev/null +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/UserAndHostAndPort.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.util.net; + +import java.io.Serializable; + +import com.google.common.base.Objects; +import com.google.common.net.HostAndPort; + +public class UserAndHostAndPort implements Serializable { + + private static final long serialVersionUID = 1525306419968853853L; + + public static UserAndHostAndPort fromParts(String user, String host, int port) { + return new UserAndHostAndPort(user, HostAndPort.fromParts(host, port)); + } + + public static UserAndHostAndPort fromParts(String user, HostAndPort hostAndPort) { + return new UserAndHostAndPort(user, hostAndPort); + } + + /** + * Split a string of the form myuser@myhost:1234 into a user, host and port. + * + * @param str The input string to parse. + * @return If parsing was successful, a populated UserAndHostAndPort object. + * @throws IllegalArgumentException + * if nothing meaningful could be parsed. + */ + public static UserAndHostAndPort fromString(String str) { + int userEnd = str.indexOf("@"); + if (userEnd < 0) throw new IllegalArgumentException("User missing (no '@' in "+str); + return new UserAndHostAndPort(str.substring(0, userEnd).trim(), HostAndPort.fromString(str.substring(userEnd+1).trim())); + } + + private final String user; + private final HostAndPort hostAndPort; + + protected UserAndHostAndPort(String user, HostAndPort hostAndPort) { + this.user = user; + this.hostAndPort = hostAndPort; + } + + public String getUser() { + return user; + } + + public HostAndPort getHostAndPort() { + return hostAndPort; + } + + @Override + public String toString() { + return user + "@" + hostAndPort.getHostText() + (hostAndPort.hasPort() ? ":" + hostAndPort.getPort() : ""); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof UserAndHostAndPort)) return false; + UserAndHostAndPort o = (UserAndHostAndPort) obj; + return Objects.equal(user, o.user) && Objects.equal(hostAndPort, o.hostAndPort); + } + + @Override + public int hashCode() { + return Objects.hashCode(user, hostAndPort); + } +}
