Skip to content
This repository has been archived by the owner on Nov 6, 2024. It is now read-only.

Commit

Permalink
alioss (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
A-Little-Excited authored Apr 16, 2024
1 parent b185132 commit 0861529
Show file tree
Hide file tree
Showing 12 changed files with 449 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,15 @@ public class FileNameAndUploadedTimeBasedIdGenerator implements FileIdGenerator
*
* @throws NullPointerException if {@code messageDigest} is {@code null}
*/
protected FileNameAndUploadedTimeBasedIdGenerator(final @NotNull MessageDigest messageDigest) {
public FileNameAndUploadedTimeBasedIdGenerator(final @NotNull MessageDigest messageDigest) {
this.messageDigest = Objects.requireNonNull(messageDigest);
}

public static FileNameAndUploadedTimeBasedIdGenerator createFileNameAndUploadedTimeBasedIdGenerator(final @NotNull
MessageDigest messageDigest) {
return new FileNameAndUploadedTimeBasedIdGenerator(messageDigest);
}

/**
* Returns a {@link FileIdGenerator} object that generates the ID using hash and base64 encoding.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
/*
* Copyright Paion Data
*
* * Copyright Paion Data
* *
* * 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.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.paiondata.athena.filestore.alioss

import com.aliyun.oss.OSS
Expand Down
10 changes: 10 additions & 0 deletions athena-spring/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Athena Spring Artifacts
=======================

Athena comes bundled with two Spring artifacts:

1. [athena-spring-boot-autoconfigure] - Provides controllers and configuration for using Athena with Spring Boot.
2. [athena-spring-boot-starter] - Provides a Spring Boot dependency starter for an opinionated setup.

[athena-spring-boot-autoconfigure]: https://github.com/paion-data/athena/tree/master/athena-spring/athena-spring-boot-autoconfigure/README.md
[athena-spring-boot-starter]: https://github.com/paion-data/athena/tree/master/athena-spring/athena-spring-boot-starter/README.md
27 changes: 27 additions & 0 deletions athena-spring/athena-spring-boot-autoconfigure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Athena Spring Boot Autoconfigure
================================

The Athena spring boot autoconfigure package provides the core code needed to use Athena with Spring Boot 3. It includes:

1. BeanConfig

2. FileController

BeanConfig
----------

| Core Bean | Description|
|-------------------|-------------------------------------------------------------------------------|
| `fileIdGenerator` | Generator of the unique identification of the file|
| `ossClient` | Ali OSS Java client for managing OSS resources such as storage space and files|
| `aliOssFileStore` | Upload files to and download files from Ali OSS|

FileController
--------------

FileController will receive and process requests to upload and download files.
It includes:

1. upload: Receives a request with a com.paiondata.athena.file.File object as an argument to upload the file to Ali OSS and generate a FileId as a unique identifier for the file to return

2. download: Receives a request with FileId as the parameter, and returns the InputStream corresponding to FileId
70 changes: 70 additions & 0 deletions athena-spring/athena-spring-boot-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.paiondata.athena</groupId>
<artifactId>athena-spring</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>athena-spring-boot-autoconfigure</artifactId>
<packaging>jar</packaging>
<name>Athena: Spring Boot - AutoConfigure</name>
<description>Athena Spring Boot AutoConfigure</description>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.4</version>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.1.5</version>
</dependency>

<dependency>
<groupId>com.paiondata.athena</groupId>
<artifactId>athena-core</artifactId>
</dependency>

<dependency>
<groupId>com.paiondata.athena</groupId>
<artifactId>athena-filestore-alioss</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>com.paiondata.athena</groupId>
<artifactId>athena-filestore-swift</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.2.4</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.4.14</version>
</dependency>

<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
<version>1.10.2</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright Paion Data
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.paiondata.athena.spring.boot.autoconfigure.config;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyuncs.exceptions.ClientException;
import com.paiondata.athena.config.ErrorMessageFormat;
import com.paiondata.athena.config.SystemConfig;
import com.paiondata.athena.config.SystemConfigFactory;
import com.paiondata.athena.file.identifier.FileIdGenerator;
import com.paiondata.athena.file.identifier.FileNameAndUploadedTimeBasedIdGenerator;
import com.paiondata.athena.filestore.alioss.AliOSSFileStore;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import jakarta.validation.constraints.NotNull;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

@Configuration
public class BeanConfig {

private static final String BEAN_CONFIGURATION = BeanConfig.class.toString();
private static final Logger LOG = LoggerFactory.getLogger(BeanConfig.class);
private static final String FILE_ID_HASHING_ALGORITHM_DEFAULT = "MD5";
private static final String ALIOSS_ENDPOINT_KEY = "alioss_endpoint_key";
private static final SystemConfig SYSTEM_CONFIG = SystemConfigFactory.getInstance();

private static final String OSS_ENDPOINT = SYSTEM_CONFIG.getStringProperty(
SYSTEM_CONFIG.getPackageVariableName(ALIOSS_ENDPOINT_KEY)
).orElseThrow(() -> {
LOG.error(ErrorMessageFormat.CONFIG_NOT_FOUND.logFormat(ALIOSS_ENDPOINT_KEY));
return new IllegalStateException(ErrorMessageFormat.CONFIG_NOT_FOUND.format());
});

@Bean
@ConditionalOnProperty(name = "athena.spring.alioss.enabled", havingValue = "true")
public AliOSSFileStore aliOssFileStore(
@NotNull final OSS ossClient, @NotNull final FileIdGenerator fileIdGenerator
) {
return new AliOSSFileStore(Objects.requireNonNull(ossClient), Objects.requireNonNull(fileIdGenerator));
}

@Bean
@ConditionalOnProperty(name = "athena.spring.alioss.enabled", havingValue = "true")
public OSS ossClient(@NotNull final EnvironmentVariableCredentialsProvider credentialsProvider) {
return new OSSClientBuilder().build(OSS_ENDPOINT, Objects.requireNonNull(credentialsProvider));
}

@Bean
public FileIdGenerator fileIdGenerator(@NotNull final MessageDigest messageDigest) {
return new FileNameAndUploadedTimeBasedIdGenerator(messageDigest);
}

@Bean
public MessageDigest messageDigest() {
try {
return MessageDigest.getInstance(FILE_ID_HASHING_ALGORITHM_DEFAULT);
} catch (final NoSuchAlgorithmException exception) {
final String message = String.format(
"No Provider supports a MessageDigestSpi implementation for the specified algorithm in '%s'",
BEAN_CONFIGURATION
);
LOG.error(message, exception);
throw new IllegalStateException(message, exception);
}
}

@Bean
@ConditionalOnProperty(name = "athena.spring.alioss.enabled", havingValue = "true")
public EnvironmentVariableCredentialsProvider credentialsProvider() {
try {
return CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
} catch (final ClientException exception) {
final String message = String.format(
"An error occurred when the client tried to send a request or data transfer to Ali OSS in '%s'",
BEAN_CONFIGURATION
);
LOG.error(message, exception);
throw new IllegalStateException(message, exception);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright Paion Data
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.paiondata.athena.spring.boot.autoconfigure.controller;

import com.paiondata.athena.file.File;
import com.paiondata.athena.filestore.alioss.AliOSSFileStore;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.validation.constraints.NotNull;

import java.io.InputStream;
import java.util.Objects;

@RestController
@RequestMapping("/file")
public class FileController {

@Autowired
private AliOSSFileStore aliOSSFileStore;

@RequestMapping("/upload")
public String upload(@NotNull final File file) {
return aliOSSFileStore.upload(Objects.requireNonNull(file));
}

@RequestMapping("/download")
public InputStream download(@NotNull final String fileId) {
return aliOSSFileStore.download(Objects.requireNonNull(fileId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright Paion Data
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.paiondata.athena.spring.boot.autoconfigure.config

import com.paiondata.athena.config.SystemConfig
import com.paiondata.athena.config.SystemConfigFactory

import org.springframework.boot.autoconfigure.AutoConfigurations
import org.springframework.boot.test.context.runner.ApplicationContextRunner

import spock.lang.Specification
import spock.lang.Unroll

class BeanConfigSpec extends Specification{

static final String ALIOSS_ENDPOINT_KEY = "alioss_endpoint_key"
static final String ALIOSS_ENDPOINT_VALUE = "https://oss-cn-hangzhou.aliyuncs.com"
static final SystemConfig SYSTEM_CONFIG = SystemConfigFactory.getInstance()
static final String[] BEAN_WITH_ALIOSS = ["credentialsProvider", "messageDigest", "fileIdGenerator",
"ossClient", "aliOssFileStore"]
static final String[] BEAN_WITHOUT_ALIOSS = ["messageDigest", "fileIdGenerator"]

@Unroll
def 'When Ali OSS #enabled enabled, initialized beans are #expectedInitializedBeans only'() {
given: "an auto-config class that sources beans"
ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(BeanConfig.class))

and: "Ali OSS is configured to be ${aliOssEnabled ? "enabled" : "disabled"}"
if (aliOssEnabled) {
SYSTEM_CONFIG.setProperty(SYSTEM_CONFIG.getPackageVariableName(ALIOSS_ENDPOINT_KEY), ALIOSS_ENDPOINT_VALUE)
}

expect: "required beans are always injected and conditional beans are injected conditionally"
contextRunner
.withPropertyValues("athena.spring.alioss.enabled=${aliOssEnabled}")
.run {context -> {
expectedInitializedBeans.each { it -> context.containsBean(it) }
expectedUninitializedBeans.each { it -> !context.containsBean(it) }
}}

where:
aliOssEnabled | expectedInitializedBeans | expectedUninitializedBeans
true | BEAN_WITH_ALIOSS | []
false | BEAN_WITHOUT_ALIOSS | ["credentialsProvider", "ossClient", "aliOssFileStore"]

enabled = aliOssEnabled ? "is" : "is not"
}
}
Loading

0 comments on commit 0861529

Please sign in to comment.