From 7329fb595997cd90c8648f29ac7156e598097db2 Mon Sep 17 00:00:00 2001 From: fangjian0423 <fangjian0423@gmail.com> Date: Fri, 31 Aug 2018 19:23:45 +0800 Subject: [PATCH] add storage starter --- pom.xml | 2 + spring-cloud-alibaba-dependencies/pom.xml | 16 ++ .../pom.xml | 60 ++++++ .../storage/OSSApplicationListener.java | 48 +++++ .../alibaba/storage/OSSAutoConfiguration.java | 61 ++++++ .../cloud/alibaba/storage/OSSConstants.java | 29 +++ .../cloud/alibaba/storage/OSSProperties.java | 118 +++++++++++ .../alibaba/storage/endpoint/OSSEndpoint.java | 72 +++++++ .../OSSEndpointAutoConfiguration.java | 41 ++++ .../resource/OSSStorageProtocolResolver.java | 84 ++++++++ .../storage/resource/OSSStorageResource.java | 195 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 5 + .../test/OSSAutoConfigurationTests.java | 78 +++++++ .../OSSMultiClientAutoConfigurationTests.java | 116 +++++++++++ spring-cloud-starter-storage/pom.xml | 24 +++ 15 files changed, 949 insertions(+) create mode 100644 spring-cloud-alibaba-storage-autoconfigure/pom.xml create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSApplicationListener.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSAutoConfiguration.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSConstants.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSProperties.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageProtocolResolver.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/main/resources/META-INF/spring.factories create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSAutoConfigurationTests.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSMultiClientAutoConfigurationTests.java create mode 100644 spring-cloud-starter-storage/pom.xml diff --git a/pom.xml b/pom.xml index f64f4e6cd..379791867 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,8 @@ <module>spring-cloud-alibaba-dependencies</module> <module>spring-cloud-alibaba-sentinel-autoconfigure</module> <module>spring-cloud-starter-sentinel</module> + <module>spring-cloud-alibaba-storage-autoconfigure</module> + <module>spring-cloud-starter-storage</module> <module>spring-cloud-alibaba-examples</module> </modules> diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index 0b45f8065..e7bcdbfdc 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -17,6 +17,7 @@ <properties> <sentinel.version>0.1.1</sentinel.version> + <oss.version>3.1.0</oss.version> </properties> <dependencyManagement> @@ -66,6 +67,11 @@ <artifactId>sentinel-dubbo-adapter</artifactId> <version>${sentinel.version}</version> </dependency> + <dependency> + <groupId>com.aliyun.oss</groupId> + <artifactId>aliyun-sdk-oss</artifactId> + <version>${oss.version}</version> + </dependency> <!-- Own dependencies autoconfigure --> @@ -74,6 +80,11 @@ <artifactId>spring-cloud-alibaba-sentinel-autoconfigure</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-alibaba-storage-autoconfigure</artifactId> + <version>${project.version}</version> + </dependency> <!-- Own dependencies - Starters --> <dependency> @@ -81,6 +92,11 @@ <artifactId>spring-cloud-starter-sentinel</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-starter-storage</artifactId> + <version>${project.version}</version> + </dependency> <!-- Own dependencies - Sentinel-Dubbo-Api --> <dependency> diff --git a/spring-cloud-alibaba-storage-autoconfigure/pom.xml b/spring-cloud-alibaba-storage-autoconfigure/pom.xml new file mode 100644 index 000000000..b40e4286d --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/pom.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + + <parent> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-alibaba</artifactId> + <version>0.2.0.BUILD-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-alibaba-storage-autoconfigure</artifactId> + <name>Spring Cloud Alibaba Storage Autoconfigure</name> + + <dependencies> + + <dependency> + <groupId>com.aliyun.oss</groupId> + <artifactId>aliyun-sdk-oss</artifactId> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-actuator</artifactId> + <scope>provided</scope> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-actuator-autoconfigure</artifactId> + <scope>provided</scope> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <scope>provided</scope> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + <optional>true</optional> + </dependency> + + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> + +</project> diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSApplicationListener.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSApplicationListener.java new file mode 100644 index 000000000..e5e56de7d --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSApplicationListener.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 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.alibaba.storage; + +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextClosedEvent; + +import com.aliyun.oss.OSS; + +/** + * Shutdown All OSS Clients when {@code ApplicationContext} gets closed {@link ApplicationListener} + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +public class OSSApplicationListener implements ApplicationListener<ContextClosedEvent> { + + private static final Logger logger = LoggerFactory + .getLogger(OSSApplicationListener.class); + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + Map<String, OSS> ossClientMap = event.getApplicationContext() + .getBeansOfType(OSS.class); + logger.info("{} OSSClients will be shutdown soon", ossClientMap.size()); + ossClientMap.keySet().forEach(beanName -> { + logger.info("shutdown ossClient: {}", beanName); + ossClientMap.get(beanName).shutdown(); + }); + } +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSAutoConfiguration.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSAutoConfiguration.java new file mode 100644 index 000000000..9ab78b03f --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2018 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.alibaba.storage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alibaba.storage.resource.OSSStorageProtocolResolver; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClientBuilder; + +/** + * OSS Auto {@link Configuration} + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +@Configuration +@ConditionalOnClass(OSS.class) +@ConditionalOnProperty(name = OSSConstants.ENABLED, havingValue = "true", matchIfMissing = true) +@EnableConfigurationProperties(OSSProperties.class) +public class OSSAutoConfiguration { + + private static final Logger logger = LoggerFactory + .getLogger(OSSAutoConfiguration.class); + + @ConditionalOnMissingBean + @Bean + public OSS ossClient(OSSProperties ossProperties) { + logger.info("construct OSS because it is missing"); + return new OSSClientBuilder().build(ossProperties.getEndpoint(), + ossProperties.getAccessKeyId(), ossProperties.getSecretAccessKey(), + ossProperties.getSecurityToken(), ossProperties.getConfiguration()); + } + + @ConditionalOnMissingBean + @Bean + public OSSStorageProtocolResolver ossStorageProtocolResolver() { + return new OSSStorageProtocolResolver(); + } + +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSConstants.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSConstants.java new file mode 100644 index 000000000..7bf76938d --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSConstants.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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.alibaba.storage; + +/** + * OSS constants + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +public interface OSSConstants { + + String PREFIX = "spring.cloud.alibaba.oss"; + String ENABLED = PREFIX + ".enabled"; + +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSProperties.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSProperties.java new file mode 100644 index 000000000..cd39077ef --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/OSSProperties.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2018 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.alibaba.storage; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.properties.ConfigurationProperties; + +import com.aliyun.oss.ClientBuilderConfiguration; + +/** + * {@link ConfigurationProperties} for configuring OSS. + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +@ConfigurationProperties(prefix = OSSConstants.PREFIX) +public class OSSProperties { + + private static final Logger logger = LoggerFactory.getLogger(OSSProperties.class); + + public static final Map<String, String> endpointMap = new HashMap<>(); + + static { + endpointMap.put("cn-beijing", "http://oss-cn-beijing.aliyuncs.com"); + endpointMap.put("cn-qingdao", "http://oss-cn-qingdao.aliyuncs.com"); + endpointMap.put("cn-hangzhou", "http://oss-cn-hangzhou.aliyuncs.com"); + endpointMap.put("cn-hongkong", "http://oss-cn-hongkong.aliyuncs.com"); + endpointMap.put("cn-shenzhen", "http://oss-cn-shenzhen.aliyuncs.com"); + endpointMap.put("us-west-1", "http://oss-us-west-1.aliyuncs.com"); + endpointMap.put("ap-southeast-1", "http://oss-ap-southeast-1.aliyuncs.com"); + } + + private ClientBuilderConfiguration configuration; + + private String accessKeyId; + + private String secretAccessKey; + + private String region; + + private String endpoint; + + // support ram sts + private String securityToken; + + public ClientBuilderConfiguration getConfiguration() { + return configuration; + } + + public void setConfiguration(ClientBuilderConfiguration configuration) { + this.configuration = configuration; + } + + public String getAccessKeyId() { + return accessKeyId; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public String getSecretAccessKey() { + return secretAccessKey; + } + + public void setSecretAccessKey(String secretAccessKey) { + this.secretAccessKey = secretAccessKey; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getSecurityToken() { + return securityToken; + } + + public void setSecurityToken(String securityToken) { + this.securityToken = securityToken; + } + + public String getRegion() { + return region; + } + + public void setRegion(String region) { + if (!endpointMap.containsKey(region)) { + String errorStr = "error region: " + region + ", please choose from " + + Arrays.toString(endpointMap.keySet().toArray()); + logger.error(errorStr); + throw new IllegalArgumentException(errorStr); + } + this.region = region; + this.setEndpoint(endpointMap.get(region)); + } +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java new file mode 100644 index 000000000..02a89b588 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 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.alibaba.storage.endpoint; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.context.ApplicationContext; + +import com.aliyun.oss.OSSClient; + +/** + * Actuator {@link Endpoint} to expose OSS Meta Data + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +@Endpoint(id = "oss") +public class OSSEndpoint { + + @Autowired + private ApplicationContext applicationContext; + + @ReadOperation + public Map<String, Object> invoke() { + Map<String, Object> result = new HashMap<>(); + + Map<String, OSSClient> ossClientMap = applicationContext + .getBeansOfType(OSSClient.class); + + int size = ossClientMap.size(); + + List<Object> ossClientList = new ArrayList<>(); + + ossClientMap.keySet().forEach(beanName -> { + Map<String, Object> ossProperties = new HashMap<>(); + OSSClient client = ossClientMap.get(beanName); + ossProperties.put("beanName", beanName); + ossProperties.put("endpoint", client.getEndpoint().toString()); + ossProperties.put("clientConfiguration", client.getClientConfiguration()); + ossProperties.put("credentials", + client.getCredentialsProvider().getCredentials()); + ossProperties.put("bucketList", client.listBuckets().stream() + .map(bucket -> bucket.getName()).toArray()); + ossClientList.add(ossProperties); + }); + + result.put("size", size); + result.put("info", ossClientList); + + return result; + } + +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java new file mode 100644 index 000000000..e764d2c4b --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2018 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.alibaba.storage.endpoint; + +import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.endpoint.annotation.Endpoint; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * OSS {@link Endpoint} Auto-{@link Configuration} + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +@ConditionalOnClass(Endpoint.class) +public class OSSEndpointAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint + public OSSEndpoint sentinelEndPoint() { + return new OSSEndpoint(); + } + +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageProtocolResolver.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageProtocolResolver.java new file mode 100644 index 000000000..bd46ee0b5 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageProtocolResolver.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2018 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.alibaba.storage.resource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.ProtocolResolver; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import com.aliyun.oss.OSS; + +/** + * A {@link ProtocolResolver} implementation for the {@code oss://} protocol. + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +public class OSSStorageProtocolResolver + implements ProtocolResolver, BeanFactoryPostProcessor, ResourceLoaderAware { + + public static final String PROTOCOL = "oss://"; + + private static final Logger logger = LoggerFactory + .getLogger(OSSStorageProtocolResolver.class); + + private ConfigurableListableBeanFactory beanFactory; + + private OSS oss; + + private OSS getOSS() { + if (this.oss == null) { + if (this.beanFactory.getBeansOfType(OSS.class).size() > 1) { + logger.warn( + "There are multiple OSS instances, consider marking one of them as @Primary to resolve oss protocol."); + } + this.oss = this.beanFactory.getBean(OSS.class); + } + return this.oss; + } + + @Override + public Resource resolve(String location, ResourceLoader resourceLoader) { + if (!location.startsWith(PROTOCOL)) { + return null; + } + return new OSSStorageResource(getOSS(), location); + } + + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + if (DefaultResourceLoader.class.isAssignableFrom(resourceLoader.getClass())) { + ((DefaultResourceLoader) resourceLoader).addProtocolResolver(this); + } + else { + logger.warn("The provided delegate resource loader is not an implementation " + + "of DefaultResourceLoader. Custom Protocol using oss:// prefix will not be enabled."); + } + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) + throws BeansException { + this.beanFactory = beanFactory; + } +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java new file mode 100644 index 000000000..30f48b235 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2018 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.alibaba.storage.resource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +import com.aliyun.oss.ClientException; +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSException; +import com.aliyun.oss.model.Bucket; +import com.aliyun.oss.model.OSSObject; + +/** + * Implements {@link Resource} for reading and writing objects in Aliyun Object Storage + * Service (OSS). An instance of this class represents a handle to a bucket or an OSSObject. + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + * @see OSS + * @see Bucket + * @see OSSObject + */ +public class OSSStorageResource implements Resource { + + private final OSS oss; + private final String bucketName; + private final String objectKey; + private final URI location; + + public OSSStorageResource(OSS oss, String location) { + Assert.notNull(oss, "Object Storage Service can not be null"); + Assert.isTrue(location.startsWith(OSSStorageProtocolResolver.PROTOCOL), + "Location must start with " + OSSStorageProtocolResolver.PROTOCOL); + this.oss = oss; + try { + URI locationUri = new URI(location); + this.bucketName = locationUri.getAuthority(); + + if (locationUri.getPath() != null && locationUri.getPath().length() > 1) { + this.objectKey = locationUri.getPath().substring(1); + } + else { + this.objectKey = null; + } + this.location = locationUri; + } + catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid location: " + location, e); + } + } + + @Override + public boolean exists() { + try { + return isBucket() ? getBucket() != null : getOSSObject() != null; + } + catch (Exception e) { + return false; + } + } + + /** + * Since the oss: protocol will normally not have a URL stream handler registered, + * this method will always throw a {@link java.net.MalformedURLException}. + * @return The URL for the OSS resource, if a URL stream handler is registered for the + * oss protocol. + */ + @Override + public URL getURL() throws IOException { + return this.location.toURL(); + } + + @Override + public URI getURI() throws IOException { + return this.location; + } + + @Override + public File getFile() throws IOException { + throw new UnsupportedOperationException( + getDescription() + " cannot be resolved to absolute file path"); + } + + @Override + public long contentLength() throws IOException { + assertExisted(); + if (isBucket()) { + throw new FileNotFoundException("OSSObject not existed."); + } + return getOSSObject().getObjectMetadata().getContentLength(); + } + + @Override + public long lastModified() throws IOException { + assertExisted(); + if (isBucket()) { + throw new FileNotFoundException("OSSObject not existed."); + } + return getOSSObject().getObjectMetadata().getLastModified().getTime(); + } + + @Override + public Resource createRelative(String relativePath) throws IOException { + return new OSSStorageResource(this.oss, + this.location.resolve(relativePath).toString()); + } + + @Override + public String getFilename() { + return isBucket() ? this.bucketName : this.objectKey; + } + + @Override + public String getDescription() { + return this.location.toString(); + } + + @Override + public InputStream getInputStream() throws IOException { + assertExisted(); + if (isBucket()) { + throw new IllegalStateException( + "Cannot open an input stream to a bucket: '" + this.location + "'"); + } + else { + return getOSSObject().getObjectContent(); + } + } + + /** + * Returns the {@link Bucket} associated with the resource. + * @return the bucket if it exists, or null otherwise + */ + public Bucket getBucket() { + return this.oss.listBuckets().stream() + .filter(bucket -> bucket.getName().equals(this.bucketName)).findFirst() + .get(); + } + + /** + * Checks for the existence of the {@link Bucket} associated with the resource. + * @return true if the bucket exists + */ + public boolean bucketExists() { + return getBucket() != null; + } + + /** + * Gets the underlying resource object in Aliyun Object Storage Service. + * @return The resource object, will be null if it does not exist in Aliyun Object + * Storage Service. + * @throws OSSException it is thrown upon error when accessing OSS + * @throws ClientException it is the one thrown by the client side when accessing OSS + */ + public OSSObject getOSSObject() { + return this.oss.getObject(this.bucketName, this.objectKey); + } + + /** + * Check if this resource references a bucket and not a blob. + * @return if the resource is bucket + */ + public boolean isBucket() { + return this.objectKey == null; + } + + private void assertExisted() throws FileNotFoundException { + if (!exists()) { + throw new FileNotFoundException("Bucket or OSSObject not existed."); + } + } + +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-storage-autoconfigure/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..9945ef830 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/resources/META-INF/spring.factories @@ -0,0 +1,5 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.springframework.cloud.alibaba.storage.OSSAutoConfiguration,\ +org.springframework.cloud.alibaba.storage.endpoint.OSSEndpointAutoConfiguration +org.springframework.context.ApplicationListener=\ +org.springframework.cloud.alibaba.storage.OSSApplicationListener \ No newline at end of file diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSAutoConfigurationTests.java b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSAutoConfigurationTests.java new file mode 100644 index 000000000..a87e0af1f --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSAutoConfigurationTests.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 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.alibaba.storage.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.alibaba.storage.OSSAutoConfiguration; +import org.springframework.cloud.alibaba.storage.OSSProperties; + +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClient; + +/** + * {@link OSS} {@link OSSProperties} Test + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +public class OSSAutoConfigurationTests { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OSSAutoConfiguration.class)) + .withPropertyValues("spring.cloud.alibaba.oss.accessKeyId=your-ak") + .withPropertyValues("spring.cloud.alibaba.oss.secretAccessKey=your-sk") + .withPropertyValues( + "spring.cloud.alibaba.oss.endpoint=http://oss-cn-beijing.aliyuncs.com") + .withPropertyValues( + "spring.cloud.alibaba.oss.configuration.userAgent=alibaba"); + + @Test + public void testOSSProperties() { + this.contextRunner.run(context -> { + assertThat(context.getBeansOfType(OSSProperties.class).size() == 1).isTrue(); + OSSProperties ossProperties = context.getBean(OSSProperties.class); + assertThat(ossProperties.getAccessKeyId()).isEqualTo("your-ak"); + assertThat(ossProperties.getSecretAccessKey()).isEqualTo("your-sk"); + assertThat(ossProperties.getEndpoint()) + .isEqualTo("http://oss-cn-beijing.aliyuncs.com"); + assertThat(ossProperties.getConfiguration().getUserAgent()) + .isEqualTo("alibaba"); + }); + } + + @Test + public void testOSSClient() { + this.contextRunner.run(context -> { + assertThat(context.getBeansOfType(OSS.class).size() == 1).isTrue(); + assertThat(context.getBeanNamesForType(OSS.class)[0]).isEqualTo("ossClient"); + OSSClient ossClient = (OSSClient) context.getBean(OSS.class); + assertThat(ossClient.getEndpoint().toString()) + .isEqualTo("http://oss-cn-beijing.aliyuncs.com"); + assertThat(ossClient.getClientConfiguration().getUserAgent()) + .isEqualTo("alibaba"); + assertThat( + ossClient.getCredentialsProvider().getCredentials().getAccessKeyId()) + .isEqualTo("your-ak"); + assertThat(ossClient.getCredentialsProvider().getCredentials() + .getSecretAccessKey()).isEqualTo("your-sk"); + }); + } + +} diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSMultiClientAutoConfigurationTests.java b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSMultiClientAutoConfigurationTests.java new file mode 100644 index 000000000..b0caf0196 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/test/OSSMultiClientAutoConfigurationTests.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2018 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.alibaba.storage.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.cloud.alibaba.storage.OSSAutoConfiguration; +import org.springframework.cloud.alibaba.storage.OSSProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClient; +import com.aliyun.oss.OSSClientBuilder; + +/** + * Multi {@link OSS} {@link OSSProperties} Test + * + * @author <a href="mailto:fangjian0423@gmail.com">Jim</a> + */ +public class OSSMultiClientAutoConfigurationTests { + + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(OSSAutoConfiguration.class)) + .withPropertyValues("spring.cloud.alibaba.oss.accessKeyId=your-ak") + .withPropertyValues("spring.cloud.alibaba.oss.secretAccessKey=your-sk") + .withPropertyValues( + "spring.cloud.alibaba.oss.endpoint=http://oss-cn-beijing.aliyuncs.com") + .withPropertyValues( + "spring.cloud.alibaba.oss.configuration.userAgent=alibaba") + .withPropertyValues("spring.cloud.alibaba.oss1.accessKeyId=your-ak1") + .withPropertyValues("spring.cloud.alibaba.oss1.secretAccessKey=your-sk1") + .withPropertyValues( + "spring.cloud.alibaba.oss1.endpoint=http://oss-cn-beijing.aliyuncs.com") + .withPropertyValues( + "spring.cloud.alibaba.oss1.configuration.userAgent=alibaba1"); + + @Test + public void testOSSClient() { + this.contextRunner.withUserConfiguration(MultiClientConfiguration.class) + .run(context -> { + assertThat(context.getBeansOfType(OSS.class).size() == 2).isTrue(); + OSSClient ossClient = (OSSClient) context.getBean("ossClient1", + OSS.class); + assertThat(ossClient.getEndpoint().toString()) + .isEqualTo("http://oss-cn-beijing.aliyuncs.com"); + assertThat(ossClient.getClientConfiguration().getUserAgent()) + .isEqualTo("alibaba"); + assertThat(ossClient.getCredentialsProvider().getCredentials() + .getAccessKeyId()).isEqualTo("your-ak"); + assertThat(ossClient.getCredentialsProvider().getCredentials() + .getSecretAccessKey()).isEqualTo("your-sk"); + OSSClient ossClient1 = (OSSClient) context.getBean("ossClient2", + OSS.class); + assertThat(ossClient1.getEndpoint().toString()) + .isEqualTo("http://oss-cn-beijing.aliyuncs.com"); + assertThat(ossClient1.getClientConfiguration().getUserAgent()) + .isEqualTo("alibaba1"); + assertThat(ossClient1.getCredentialsProvider().getCredentials() + .getAccessKeyId()).isEqualTo("your-ak1"); + assertThat(ossClient1.getCredentialsProvider().getCredentials() + .getSecretAccessKey()).isEqualTo("your-sk1"); + }); + } + + @Configuration + protected static class MultiClientConfiguration { + + @Bean + @ConfigurationProperties(prefix = "spring.cloud.alibaba.oss") + public OSSProperties ossProperties1() { + return new OSSProperties(); + } + + @Bean + public OSS ossClient1(@Qualifier("ossProperties1") OSSProperties ossProperties) { + return new OSSClientBuilder().build(ossProperties.getEndpoint(), + ossProperties.getAccessKeyId(), ossProperties.getSecretAccessKey(), + ossProperties.getSecurityToken(), ossProperties.getConfiguration()); + } + + @Bean + @ConfigurationProperties(prefix = "spring.cloud.alibaba.oss1") + public OSSProperties ossProperties2() { + return new OSSProperties(); + } + + @Bean + public OSS ossClient2(@Qualifier("ossProperties2") OSSProperties ossProperties) { + return new OSSClientBuilder().build(ossProperties.getEndpoint(), + ossProperties.getAccessKeyId(), ossProperties.getSecretAccessKey(), + ossProperties.getSecurityToken(), ossProperties.getConfiguration()); + } + + } + +} diff --git a/spring-cloud-starter-storage/pom.xml b/spring-cloud-starter-storage/pom.xml new file mode 100644 index 000000000..5d17ae4c2 --- /dev/null +++ b/spring-cloud-starter-storage/pom.xml @@ -0,0 +1,24 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-alibaba</artifactId> + <version>0.2.0.BUILD-SNAPSHOT</version> + </parent> + <artifactId>spring-cloud-starter-storage</artifactId> + <name>Spring Cloud Starter Storage</name> + + <dependencies> + <dependency> + <groupId>org.springframework.cloud</groupId> + <artifactId>spring-cloud-alibaba-storage-autoconfigure</artifactId> + </dependency> + <dependency> + <groupId>com.aliyun.oss</groupId> + <artifactId>aliyun-sdk-oss</artifactId> + </dependency> + </dependencies> + +</project>