/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.aws.core.naming;
import com.amazonaws.regions.Region;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.Arrays;
/**
* Support class which is used to parse Amazon Webservice Resource Names. Resource names are unique identifiers for
* different type of resource inside the Amazon Webservices. These resource are represented by a service and could be
* bound into a particular region, if the service is region based. Also most of the resources will be typically bound
* to an account if the resource is scoped ot an account.
* <p>
* More information on resources are available on
* <a href="http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html">the Amazon Webservice Manual</a>
* </p>
*
* @author Agim Emruli
* @since 1.0
*/
public class AmazonResourceName {
/*
* The delimiter for the arn which delimits the different parts of the arn string
*/
private static final String RESOURCE_NAME_DELIMITER = ":";
/*
* The resource type delimiter which is used on particular resources to split the name and type.
* This typically the case for service which can contain nested paths (e.g. S3 Buckets, IAM groups)
*/
private static final String RESOURCE_TYPE_DELIMITER = "/";
private final String service;
private final String region;
private final String account;
private final String resourceType;
private final String resourceName;
private final String actualResourceTypeDelimiter;
private AmazonResourceName(String service, String region, String account, String resourceType, String resourceName, String actualResourceTypeDelimiter) {
Assert.notNull(service, "service must not be null");
Assert.notNull(resourceType, "resourceType must not be null");
this.service = service;
this.region = region;
this.account = account;
this.resourceType = resourceType;
this.resourceName = resourceName;
this.actualResourceTypeDelimiter = actualResourceTypeDelimiter;
}
/**
* Returns the service name for this particular AmazonResourceName. The service name is a plain string which will be
* one of the service from the namespace defined at <a href="http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces">user
* manual</a>
*
* @return - the service as a string - never be null
*/
public String getService() {
return this.service;
}
/**
* Returns the region to which the particular service for this object is bound. This is one of the known regions or
* null if the service is globally available (e.g. Amazon S3)
*
* @return - the region or null if the service is globally available
*/
public String getRegion() {
return this.region;
}
/**
* Returns the account to which the resource is assigned. This is the account id to which the owner of the resources
* belongs to. If the resources does not have any owner (e.g. solutions stacks in ElasticBeansTalk) this method will
* return null
*
* @return - the account number of the resource owner or null for non assigned resources
*/
public String getAccount() {
return this.account;
}
/**
* Return the resource type for the resource. This is service dependent and can be an user (for IAM) or a bucket (for
* S3). The resource type is never null and can be the resource name for particular service (e.g. Amazon SQS Queue or
* Amazon SNS Topic)
*
* @return the type of the resource of the name of the resource itself
*/
public String getResourceType() {
return this.resourceType;
}
/**
* Return the name of the resource inside the particular type. This can be a bucket name for Amazon S3 or the
* subscriptions id for the subscription of one particular topic.
*
* @return - the resource name or null if the resource type itself is the name of the resource
*/
public String getResourceName() {
return this.resourceName;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("arn");
builder.append(RESOURCE_NAME_DELIMITER);
builder.append("aws");
builder.append(RESOURCE_NAME_DELIMITER);
builder.append(this.service);
builder.append(RESOURCE_NAME_DELIMITER);
if (this.region != null) {
builder.append(this.region);
}
builder.append(RESOURCE_NAME_DELIMITER);
if (this.account != null) {
builder.append(this.account);
}
builder.append(RESOURCE_NAME_DELIMITER);
builder.append(this.resourceType);
if (this.resourceName != null) {
builder.append(this.actualResourceTypeDelimiter);
builder.append(this.resourceName);
}
return builder.toString();
}
public static AmazonResourceName fromString(String name) {
Assert.notNull(name, "name must not be null");
String[] tokens = name.split(RESOURCE_NAME_DELIMITER);
if (tokens.length < 6 || tokens.length > 7) {
throw new IllegalArgumentException("Resource name:'" + name + "' is not a valid resource identifier");
}
if (!"arn".equals(tokens[0])) {
throw new IllegalArgumentException("Resource name:'" + name + "' must have a arn qualifier at the beginning");
}
if (!"aws".equals(tokens[1])) {
throw new IllegalArgumentException("Resource name:'" + name + "' must have a aws qualifier");
}
String actualResourceTypeDelimiter;
if (tokens.length == 6) {
tokens = Arrays.copyOf(tokens, 7);
String[] split = StringUtils.split(tokens[5], RESOURCE_TYPE_DELIMITER);
if (split != null) {
tokens[5] = split[0];
tokens[6] = split[1];
}
actualResourceTypeDelimiter = RESOURCE_TYPE_DELIMITER;
} else {
actualResourceTypeDelimiter = RESOURCE_NAME_DELIMITER;
}
return new AmazonResourceName(tokens[2], trimToNull(tokens[3]), trimToNull(tokens[4]), trimToNull(tokens[5]), trimToNull(tokens[6]), actualResourceTypeDelimiter);
}
public static boolean isValidAmazonResourceName(String name) {
try {
fromString(name);
return true;
} catch (IllegalArgumentException ignore) {
return false;
}
}
private static String trimToNull(String input) {
return StringUtils.hasText(input) ? input : null;
}
@SuppressWarnings("ClassNamingConvention")
public static class Builder {
private String service;
private String region;
private String account;
private String resourceType;
private String resourceName;
private String actualResourceTypeDelimiter;
public Builder withService(String service) {
this.service = service;
return this;
}
public Builder withRegion(Region region) {
this.region = region.getName();
return this;
}
public Builder withAccount(String account) {
this.account = account;
return this;
}
public Builder withResourceType(String resourceType) {
this.resourceType = resourceType;
return this;
}
public Builder withResourceName(String resourceName) {
this.resourceName = resourceName;
return this;
}
public Builder withResourceTypeDelimiter(String resourceTypeDelimiter) {
this.actualResourceTypeDelimiter = resourceTypeDelimiter;
return this;
}
public AmazonResourceName build() {
return new AmazonResourceName(this.service, this.region, this.account, this.resourceType, this.resourceName, this.actualResourceTypeDelimiter);
}
}
}