From d8fa13f3129d080ff51962017ed89e5c832231a8 Mon Sep 17 00:00:00 2001 From: fangjian0423 Date: Sat, 1 Sep 2018 17:25:36 +0800 Subject: [PATCH] add storage starter in springboot 1.x --- pom.xml | 2 + spring-cloud-alibaba-dependencies/pom.xml | 16 ++ .../pom.xml | 67 ++++++ .../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 | 80 +++++++ .../OSSEndpointAutoConfiguration.java | 44 ++++ .../resource/OSSStorageProtocolResolver.java | 84 +++++++ .../storage/resource/OSSStorageResource.java | 208 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 5 + .../storage/OSSAutoConfigurationTests.java | 75 +++++++ .../OSSMultiClientAutoConfigurationTests.java | 116 ++++++++++ spring-cloud-starter-storage/pom.xml | 24 ++ 15 files changed, 977 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/OSSAutoConfigurationTests.java create mode 100644 spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/OSSMultiClientAutoConfigurationTests.java create mode 100644 spring-cloud-starter-storage/pom.xml diff --git a/pom.xml b/pom.xml index 41490ef68..9d639946f 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,8 @@ spring-cloud-alibaba-dependencies spring-cloud-alibaba-sentinel-autoconfigure spring-cloud-starter-sentinel + spring-cloud-alibaba-storage-autoconfigure + spring-cloud-starter-storage spring-cloud-alibaba-examples diff --git a/spring-cloud-alibaba-dependencies/pom.xml b/spring-cloud-alibaba-dependencies/pom.xml index bd1bb5db1..1f7223bb3 100644 --- a/spring-cloud-alibaba-dependencies/pom.xml +++ b/spring-cloud-alibaba-dependencies/pom.xml @@ -17,6 +17,7 @@ 0.1.1 + 3.1.0 @@ -53,6 +54,11 @@ sentinel-dubbo-adapter ${sentinel.version} + + com.aliyun.oss + aliyun-sdk-oss + ${oss.version} + @@ -61,6 +67,11 @@ spring-cloud-alibaba-sentinel-autoconfigure ${project.version} + + org.springframework.cloud + spring-cloud-alibaba-storage-autoconfigure + ${project.version} + @@ -68,6 +79,11 @@ spring-cloud-starter-sentinel ${project.version} + + org.springframework.cloud + spring-cloud-starter-storage + ${project.version} + diff --git a/spring-cloud-alibaba-storage-autoconfigure/pom.xml b/spring-cloud-alibaba-storage-autoconfigure/pom.xml new file mode 100644 index 000000000..e97369974 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/pom.xml @@ -0,0 +1,67 @@ + + + + + org.springframework.cloud + spring-cloud-alibaba + 0.1.0.BUILD-SNAPSHOT + + 4.0.0 + + org.springframework.cloud + spring-cloud-alibaba-storage-autoconfigure + Spring Cloud Alibaba Storage Autoconfigure + + + + + com.aliyun.oss + aliyun-sdk-oss + true + + + + org.springframework.boot + spring-boot-actuator + provided + true + + + + org.springframework.boot + spring-boot-configuration-processor + provided + true + + + + org.springframework.boot + spring-boot + provided + true + + + + org.springframework.boot + spring-boot-autoconfigure + provided + true + + + + org.springframework.boot + spring-boot-starter-logging + provided + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + \ No newline at end of file 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..ff1a6c1fd --- /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 Jim + */ +public class OSSApplicationListener implements ApplicationListener { + + private static final Logger logger = LoggerFactory + .getLogger(OSSApplicationListener.class); + + @Override + public void onApplicationEvent(ContextClosedEvent event) { + Map ossClientMap = event.getApplicationContext() + .getBeansOfType(OSS.class); + logger.info("{} OSSClients will be shutdown soon", ossClientMap.size()); + for(String beanName : ossClientMap.keySet()) { + logger.info("shutdown ossClient: {}", beanName); + ossClientMap.get(beanName).shutdown(); + } + } +} \ No newline at end of file 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..799d32a97 --- /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 Jim + */ +@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(); + } + +} \ No newline at end of file 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..e7e29df8c --- /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 Jim + */ +public interface OSSConstants { + + String PREFIX = "spring.cloud.alibaba.oss"; + String ENABLED = PREFIX + ".enabled"; + +} \ No newline at end of file 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..c0278ece5 --- /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 Jim + */ +@ConfigurationProperties(prefix = OSSConstants.PREFIX) +public class OSSProperties { + + private static final Logger logger = LoggerFactory.getLogger(OSSProperties.class); + + public static final Map 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)); + } +} \ No newline at end of file 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..bbf77f1aa --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpoint.java @@ -0,0 +1,80 @@ +/* + * 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.AbstractEndpoint; +import org.springframework.context.ApplicationContext; + +import com.aliyun.oss.OSSClient; + +/** + * Actuator Endpoint to expose OSS Meta Data + * + * @author Jim + */ +public class OSSEndpoint extends AbstractEndpoint> { + + @Autowired + private ApplicationContext applicationContext; + + public OSSEndpoint() { + super("oss"); + } + + @Override + public Map invoke() { + Map result = new HashMap<>(); + + Map ossClientMap = applicationContext + .getBeansOfType(OSSClient.class); + + int size = ossClientMap.size(); + + List ossClientList = new ArrayList<>(); + + for(String beanName : ossClientMap.keySet()) { + Map 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()); + + String[] bucketList = new String[client.listBuckets().size()]; + + for(int index = 0; index < bucketList.length; index ++) { + bucketList[index] = client.listBuckets().get(index).getName(); + } + + ossProperties.put("bucketList", bucketList); + ossClientList.add(ossProperties); + } + + result.put("size", size); + result.put("info", ossClientList); + + return result; + } + +} \ No newline at end of file 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..f4ac18ed0 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/endpoint/OSSEndpointAutoConfiguration.java @@ -0,0 +1,44 @@ +/* + * 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.condition.ConditionalOnEnabledEndpoint; +import org.springframework.boot.actuate.endpoint.Endpoint; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.alibaba.storage.OSSProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * OSS {@link Endpoint} Auto-{@link Configuration} + * + * @author Jim + */ +@ConditionalOnClass(Endpoint.class) +@EnableConfigurationProperties({ OSSProperties.class }) +public class OSSEndpointAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnEnabledEndpoint("oss") + public OSSEndpoint sentinelEndPoint() { + return new OSSEndpoint(); + } + +} \ No newline at end of file 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..a00288771 --- /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 Jim + */ +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; + } +} \ No newline at end of file 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..589c70bf5 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/main/java/org/springframework/cloud/alibaba/storage/resource/OSSStorageResource.java @@ -0,0 +1,208 @@ +/* + * 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 Jim + * @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; + } + } + + @Override + public boolean isReadable() { + return true; + } + + @Override + public boolean isOpen() { + 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() { + for(Bucket bucket : this.oss.listBuckets()) { + if(bucket.getName().equals(this.bucketName)) { + return bucket; + } + } + return null; + } + + /** + * 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."); + } + } + +} \ No newline at end of file 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/OSSAutoConfigurationTests.java b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/OSSAutoConfigurationTests.java new file mode 100644 index 000000000..1ad7e1d00 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/OSSAutoConfigurationTests.java @@ -0,0 +1,75 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import com.aliyun.oss.OSS; +import com.aliyun.oss.OSSClient; + +/** + * {@link OSS} {@link OSSProperties} Test + * + * @author Jim + */ +public class OSSAutoConfigurationTests { + + private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @Before + public void init() { + context.register(OSSAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.cloud.alibaba.oss.accessKeyId=your-ak", + "spring.cloud.alibaba.oss.secretAccessKey=your-sk", + "spring.cloud.alibaba.oss.endpoint=http://oss-cn-beijing.aliyuncs.com", + "spring.cloud.alibaba.oss.configuration.userAgent=alibaba"); + this.context.refresh(); + } + + @Test + public void testOSSProperties() { + 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() { + assertThat(context.getBeansOfType(OSS.class).size()).isEqualTo(1); + assertThat(context.getBeanNamesForType(OSS.class)[0]).isEqualTo("ossClient"); + OSSClient ossClient = (OSSClient) context.getBean(OSS.class); + assertThat(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId()) + .isEqualTo("your-ak"); + assertThat( + ossClient.getCredentialsProvider().getCredentials().getSecretAccessKey()) + .isEqualTo("your-sk"); + assertThat(ossClient.getEndpoint().toString()) + .isEqualTo("http://oss-cn-beijing.aliyuncs.com"); + assertThat(ossClient.getClientConfiguration().getUserAgent()) + .isEqualTo("alibaba"); + } + +} \ No newline at end of file diff --git a/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/OSSMultiClientAutoConfigurationTests.java b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/OSSMultiClientAutoConfigurationTests.java new file mode 100644 index 000000000..607a9d847 --- /dev/null +++ b/spring-cloud-alibaba-storage-autoconfigure/src/test/java/org/springframework/cloud/alibaba/storage/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; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.util.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +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 Jim + */ +public class OSSMultiClientAutoConfigurationTests { + + private final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @Before + public void init() { + context.register(MultiClientConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.cloud.alibaba.oss.accessKeyId=your-ak", + "spring.cloud.alibaba.oss.secretAccessKey=your-sk", + "spring.cloud.alibaba.oss.endpoint=http://oss-cn-beijing.aliyuncs.com", + "spring.cloud.alibaba.oss.configuration.userAgent=alibaba", + "spring.cloud.alibaba.oss1.accessKeyId=your-ak1", + "spring.cloud.alibaba.oss1.secretAccessKey=your-sk1", + "spring.cloud.alibaba.oss1.endpoint=http://oss-cn-beijing.aliyuncs.com", + "spring.cloud.alibaba.oss1.configuration.userAgent=alibaba1"); + this.context.refresh(); + } + + @Test + public void testOSSClient() { + assertThat(context.getBeansOfType(OSS.class).size()).isEqualTo(2); + OSSClient ossClient = (OSSClient) context.getBean("ossClient1", OSS.class); + assertThat(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId()) + .isEqualTo("your-ak"); + assertThat( + ossClient.getCredentialsProvider().getCredentials().getSecretAccessKey()) + .isEqualTo("your-sk"); + assertThat(ossClient.getEndpoint().toString()) + .isEqualTo("http://oss-cn-beijing.aliyuncs.com"); + assertThat(ossClient.getClientConfiguration().getUserAgent()) + .isEqualTo("alibaba"); + OSSClient ossClient1 = (OSSClient) context.getBean("ossClient2", OSS.class); + assertThat(ossClient1.getCredentialsProvider().getCredentials().getAccessKeyId()) + .isEqualTo("your-ak1"); + assertThat( + ossClient1.getCredentialsProvider().getCredentials().getSecretAccessKey()) + .isEqualTo("your-sk1"); + assertThat(ossClient1.getEndpoint().toString()) + .isEqualTo("http://oss-cn-beijing.aliyuncs.com"); + assertThat(ossClient1.getClientConfiguration().getUserAgent()) + .isEqualTo("alibaba1"); + } + + @Configuration + @EnableConfigurationProperties + 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()); + } + + } + +} \ No newline at end of file diff --git a/spring-cloud-starter-storage/pom.xml b/spring-cloud-starter-storage/pom.xml new file mode 100644 index 000000000..c09bf513b --- /dev/null +++ b/spring-cloud-starter-storage/pom.xml @@ -0,0 +1,24 @@ + + 4.0.0 + + + org.springframework.cloud + spring-cloud-alibaba + 0.1.0.BUILD-SNAPSHOT + + spring-cloud-starter-storage + Spring Cloud Starter Storage + + + + org.springframework.cloud + spring-cloud-alibaba-storage-autoconfigure + + + com.aliyun.oss + aliyun-sdk-oss + + + + \ No newline at end of file