diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c184ae64..4f6f7d172 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -74,13 +74,13 @@ jobs: restore-keys: | ${{ runner.os }}-m2 - name: Test with Maven - run: ./mvnw test -B -Dmaven.test.skip=false -pl fesod + run: ./mvnw test -B -Dmaven.test.skip=false -pl fesod,fesod-spring-boot-starter -am - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: (!cancelled()) with: files: "**/target/surefire-reports/*.xml" - name: Maven Build - run: ./mvnw install -B -V -pl fesod + run: ./mvnw install -B -V -pl fesod,fesod-spring-boot-starter -am - name: Java Doc run: ./mvnw javadoc:javadoc diff --git a/fesod-spring-boot-starter/pom.xml b/fesod-spring-boot-starter/pom.xml new file mode 100644 index 000000000..527e312a0 --- /dev/null +++ b/fesod-spring-boot-starter/pom.xml @@ -0,0 +1,107 @@ + + + + 4.0.0 + + org.apache.fesod + fesod-parent + 1.3.0 + ../pom.xml + + fesod-spring-boot-starter + jar + fesod-spring-boot-starter + Spring Boot Starter for Fesod + + 2.7.18 + false + + 1.2.13 + + + + + org.springframework.boot + spring-boot-autoconfigure + ${spring-boot.version} + provided + + + org.apache.fesod + fesod + ${project.version} + + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot.version} + true + + + + org.springframework.boot + spring-boot-starter + ${spring-boot.version} + test + + + + ch.qos.logback + logback-classic + ${logback.compat.version} + test + + + org.springframework.boot + spring-boot-starter-test + ${spring-boot.version} + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + org.projectlombok + lombok + ${lombok.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot.version} + + + + + + + diff --git a/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/FesodTemplate.java b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/FesodTemplate.java new file mode 100644 index 000000000..888844b53 --- /dev/null +++ b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/FesodTemplate.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.spring.boot; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import org.apache.fesod.excel.FastExcelFactory; +import org.apache.fesod.excel.metadata.AbstractParameterBuilder; +import org.apache.fesod.excel.read.builder.AbstractExcelReaderParameterBuilder; +import org.apache.fesod.excel.read.builder.ExcelReaderBuilder; +import org.apache.fesod.excel.read.listener.ReadListener; +import org.apache.fesod.excel.write.builder.ExcelWriterBuilder; +import org.apache.fesod.spring.boot.autoconfigure.FesodProperties; +import org.apache.fesod.spring.boot.autoconfigure.FesodReaderBuilderCustomizer; +import org.apache.fesod.spring.boot.autoconfigure.FesodWriterBuilderCustomizer; + +/** + * Convenience facade that exposes the {@link FastExcelFactory} builders as Spring beans + * with sensible defaults driven by {@link FesodProperties}. + */ +public class FesodTemplate { + + private final FesodProperties properties; + private final List writerCustomizers; + private final List readerCustomizers; + + public FesodTemplate( + FesodProperties properties, + List writerCustomizers, + List readerCustomizers) { + this.properties = properties != null ? properties : new FesodProperties(); + this.writerCustomizers = writerCustomizers == null ? Collections.emptyList() : writerCustomizers; + this.readerCustomizers = readerCustomizers == null ? Collections.emptyList() : readerCustomizers; + } + + public ExcelWriterBuilder writer() { + return customizeWriter(FastExcelFactory.write()); + } + + public ExcelWriterBuilder writer(File file) { + return customizeWriter(FastExcelFactory.write(file)); + } + + public ExcelWriterBuilder writer(File file, Class head) { + return customizeWriter(FastExcelFactory.write(file, head)); + } + + public ExcelWriterBuilder writer(String pathName) { + return customizeWriter(FastExcelFactory.write(pathName)); + } + + public ExcelWriterBuilder writer(String pathName, Class head) { + return customizeWriter(FastExcelFactory.write(pathName, head)); + } + + public ExcelWriterBuilder writer(OutputStream outputStream) { + return customizeWriter(FastExcelFactory.write(outputStream)); + } + + public ExcelWriterBuilder writer(OutputStream outputStream, Class head) { + return customizeWriter(FastExcelFactory.write(outputStream, head)); + } + + public ExcelReaderBuilder reader() { + return customizeReader(FastExcelFactory.read()); + } + + public ExcelReaderBuilder reader(File file) { + return customizeReader(FastExcelFactory.read(file)); + } + + public ExcelReaderBuilder reader(File file, ReadListener readListener) { + return customizeReader(FastExcelFactory.read(file, readListener)); + } + + public ExcelReaderBuilder reader(File file, Class head, ReadListener readListener) { + return customizeReader(FastExcelFactory.read(file, head, readListener)); + } + + public ExcelReaderBuilder reader(String pathName) { + return customizeReader(FastExcelFactory.read(pathName)); + } + + public ExcelReaderBuilder reader(String pathName, ReadListener readListener) { + return customizeReader(FastExcelFactory.read(pathName, readListener)); + } + + public ExcelReaderBuilder reader(String pathName, Class head, ReadListener readListener) { + return customizeReader(FastExcelFactory.read(pathName, head, readListener)); + } + + public ExcelReaderBuilder reader(InputStream inputStream) { + return customizeReader(FastExcelFactory.read(inputStream)); + } + + public ExcelReaderBuilder reader(InputStream inputStream, ReadListener readListener) { + return customizeReader(FastExcelFactory.read(inputStream, readListener)); + } + + public ExcelReaderBuilder reader(InputStream inputStream, Class head, ReadListener readListener) { + return customizeReader(FastExcelFactory.read(inputStream, head, readListener)); + } + + private ExcelWriterBuilder customizeWriter(ExcelWriterBuilder builder) { + applyGlobal(builder); + applyWriterDefaults(builder); + writerCustomizers.forEach(customizer -> customizer.customize(builder)); + return builder; + } + + private ExcelReaderBuilder customizeReader(ExcelReaderBuilder builder) { + applyGlobal(builder); + applyReaderDefaults(builder); + readerCustomizers.forEach(customizer -> customizer.customize(builder)); + return builder; + } + + private void applyGlobal(AbstractParameterBuilder builder) { + FesodProperties.Global global = properties.getGlobal(); + if (global == null) { + return; + } + if (global.getAutoTrim() != null) { + builder.autoTrim(global.getAutoTrim()); + } + if (global.getAutoStrip() != null) { + builder.autoStrip(global.getAutoStrip()); + } + if (global.getUse1904Windowing() != null) { + builder.use1904windowing(global.getUse1904Windowing()); + } + Locale locale = global.getLocale(); + if (locale != null) { + builder.locale(locale); + } + if (global.getUseScientificFormat() != null && builder instanceof AbstractExcelReaderParameterBuilder) { + AbstractExcelReaderParameterBuilder readerBuilder = + (AbstractExcelReaderParameterBuilder) builder; + readerBuilder.useScientificFormat(global.getUseScientificFormat()); + } + if (global.getFiledCacheLocation() != null) { + builder.filedCacheLocation(global.getFiledCacheLocation()); + } + } + + private void applyWriterDefaults(ExcelWriterBuilder builder) { + FesodProperties.Writer writer = properties.getWriter(); + if (writer == null) { + return; + } + if (writer.getAutoCloseStream() != null) { + builder.autoCloseStream(writer.getAutoCloseStream()); + } + if (writer.getWithBom() != null) { + builder.withBom(writer.getWithBom()); + } + if (writer.getInMemory() != null) { + builder.inMemory(writer.getInMemory()); + } + if (writer.getWriteExcelOnException() != null) { + builder.writeExcelOnException(writer.getWriteExcelOnException()); + } + } + + private void applyReaderDefaults(ExcelReaderBuilder builder) { + FesodProperties.Reader reader = properties.getReader(); + if (reader == null) { + return; + } + if (reader.getIgnoreEmptyRow() != null) { + builder.ignoreEmptyRow(reader.getIgnoreEmptyRow()); + } + if (reader.getAutoCloseStream() != null) { + builder.autoCloseStream(reader.getAutoCloseStream()); + } + if (reader.getMandatoryUseInputStream() != null) { + builder.mandatoryUseInputStream(reader.getMandatoryUseInputStream()); + } + } +} diff --git a/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodAutoConfiguration.java b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodAutoConfiguration.java new file mode 100644 index 000000000..b9ce5662f --- /dev/null +++ b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.spring.boot.autoconfigure; + +import java.util.List; +import java.util.stream.Collectors; +import org.apache.fesod.excel.FastExcelFactory; +import org.apache.fesod.spring.boot.FesodTemplate; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +@ConditionalOnClass(FastExcelFactory.class) +@EnableConfigurationProperties(FesodProperties.class) +public class FesodAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public FesodTemplate fesodTemplate( + FesodProperties properties, + ObjectProvider writerCustomizers, + ObjectProvider readerCustomizers) { + List writer = + writerCustomizers.orderedStream().collect(Collectors.toList()); + List reader = + readerCustomizers.orderedStream().collect(Collectors.toList()); + return new FesodTemplate(properties, writer, reader); + } +} diff --git a/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodProperties.java b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodProperties.java new file mode 100644 index 000000000..6996da777 --- /dev/null +++ b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodProperties.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.spring.boot.autoconfigure; + +import java.util.Locale; +import org.apache.fesod.excel.enums.CacheLocationEnum; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Root configuration properties for Fesod Spring Boot starter. + *

+ * All properties are bound under the prefix {@code fesod}. + * Example (application.yml): + *

+ * fesod:
+ *   global:
+ *     auto-trim: true
+ *     locale: en_US
+ *   reader:
+ *     ignore-empty-row: true
+ *   writer:
+ *     in-memory: true
+ * 
+ */ +@ConfigurationProperties(prefix = "fesod") +public class FesodProperties { + + /** Global shared configuration applied to both reading and writing. */ + private Global global = new Global(); + /** Reader specific configuration. */ + private Reader reader = new Reader(); + /** Writer specific configuration. */ + private Writer writer = new Writer(); + + public Global getGlobal() { + return global; + } + + public void setGlobal(Global global) { + this.global = global; + } + + public Reader getReader() { + return reader; + } + + public void setReader(Reader reader) { + this.reader = reader; + } + + public Writer getWriter() { + return writer; + } + + public void setWriter(Writer writer) { + this.writer = writer; + } + + /** + * Global configuration options that affect both reading and writing operations. + */ + public static class Global { + /** + * Whether to automatically trim leading and trailing whitespace from cell string values. + */ + private Boolean autoTrim; + /** + * Whether to automatically remove invisible / special control characters (strip) from cell values. + */ + private Boolean autoStrip; + /** + * Whether to interpret dates using the 1904 date windowing (common in older Mac Excel files). + */ + private Boolean use1904Windowing; + /** + * Whether numeric values which are large or small should be written using scientific notation. + */ + private Boolean useScientificFormat; + /** + * The default locale to use for formatting and parsing (e.g. number, date patterns). If not set, JVM default is used. + */ + private Locale locale; + /** + * The cache used when parsing fields such as head. + */ + private CacheLocationEnum filedCacheLocation; + + public Boolean getAutoTrim() { + return autoTrim; + } + + public void setAutoTrim(Boolean autoTrim) { + this.autoTrim = autoTrim; + } + + public Boolean getAutoStrip() { + return autoStrip; + } + + public void setAutoStrip(Boolean autoStrip) { + this.autoStrip = autoStrip; + } + + public Boolean getUse1904Windowing() { + return use1904Windowing; + } + + public void setUse1904Windowing(Boolean use1904Windowing) { + this.use1904Windowing = use1904Windowing; + } + + public Boolean getUseScientificFormat() { + return useScientificFormat; + } + + public void setUseScientificFormat(Boolean useScientificFormat) { + this.useScientificFormat = useScientificFormat; + } + + public Locale getLocale() { + return locale; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } + + public CacheLocationEnum getFiledCacheLocation() { + return filedCacheLocation; + } + + public void setFiledCacheLocation(CacheLocationEnum filedCacheLocation) { + this.filedCacheLocation = filedCacheLocation; + } + } + + /** + * Reader specific configuration options. + */ + public static class Reader { + /** + * Whether empty rows (all cells empty) should be ignored during reading. + */ + private Boolean ignoreEmptyRow; + /** + * Whether the underlying input stream should be closed automatically after reading completes. + */ + private Boolean autoCloseStream; + /** + * Whether reading must be performed strictly from an InputStream (instead of file path / other sources). + */ + private Boolean mandatoryUseInputStream; + + public Boolean getIgnoreEmptyRow() { + return ignoreEmptyRow; + } + + public void setIgnoreEmptyRow(Boolean ignoreEmptyRow) { + this.ignoreEmptyRow = ignoreEmptyRow; + } + + public Boolean getAutoCloseStream() { + return autoCloseStream; + } + + public void setAutoCloseStream(Boolean autoCloseStream) { + this.autoCloseStream = autoCloseStream; + } + + public Boolean getMandatoryUseInputStream() { + return mandatoryUseInputStream; + } + + public void setMandatoryUseInputStream(Boolean mandatoryUseInputStream) { + this.mandatoryUseInputStream = mandatoryUseInputStream; + } + } + + /** + * Writer specific configuration options. + */ + public static class Writer { + /** + * Whether the underlying output stream should be closed automatically after writing completes. + */ + private Boolean autoCloseStream; + /** + * Whether to write a UTF-8 BOM (Byte Order Mark) for CSV/text outputs where applicable. + */ + private Boolean withBom; + /** + * Whether writing operations should be performed entirely in memory (might increase memory usage for large files). + */ + private Boolean inMemory; + /** + * Whether the Excel file should still be written/flushed when an exception occurs during writing. + */ + private Boolean writeExcelOnException; + + public Boolean getAutoCloseStream() { + return autoCloseStream; + } + + public void setAutoCloseStream(Boolean autoCloseStream) { + this.autoCloseStream = autoCloseStream; + } + + public Boolean getWithBom() { + return withBom; + } + + public void setWithBom(Boolean withBom) { + this.withBom = withBom; + } + + public Boolean getInMemory() { + return inMemory; + } + + public void setInMemory(Boolean inMemory) { + this.inMemory = inMemory; + } + + public Boolean getWriteExcelOnException() { + return writeExcelOnException; + } + + public void setWriteExcelOnException(Boolean writeExcelOnException) { + this.writeExcelOnException = writeExcelOnException; + } + } +} diff --git a/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodReaderBuilderCustomizer.java b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodReaderBuilderCustomizer.java new file mode 100644 index 000000000..e2a9962d1 --- /dev/null +++ b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodReaderBuilderCustomizer.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.spring.boot.autoconfigure; + +import org.apache.fesod.excel.read.builder.ExcelReaderBuilder; + +/** + * Callback interface that allows customization of the {@link ExcelReaderBuilder} created by + * {@link org.apache.fesod.spring.boot.FesodTemplate}. + */ +@FunctionalInterface +public interface FesodReaderBuilderCustomizer { + + void customize(ExcelReaderBuilder builder); +} diff --git a/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodWriterBuilderCustomizer.java b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodWriterBuilderCustomizer.java new file mode 100644 index 000000000..2166f243c --- /dev/null +++ b/fesod-spring-boot-starter/src/main/java/org/apache/fesod/spring/boot/autoconfigure/FesodWriterBuilderCustomizer.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.spring.boot.autoconfigure; + +import org.apache.fesod.excel.write.builder.ExcelWriterBuilder; + +/** + * Callback interface that allows customization of the {@link ExcelWriterBuilder} created by + * {@link org.apache.fesod.spring.boot.FesodTemplate}. + */ +@FunctionalInterface +public interface FesodWriterBuilderCustomizer { + + void customize(ExcelWriterBuilder builder); +} diff --git a/fesod-spring-boot-starter/src/main/resources/META-INF/spring.factories b/fesod-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..124f01e33 --- /dev/null +++ b/fesod-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,17 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.apache.fesod.spring.boot.autoconfigure.FesodAutoConfiguration diff --git a/fesod-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/fesod-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..38ca5d844 --- /dev/null +++ b/fesod-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,2 @@ +org.apache.fesod.spring.boot.autoconfigure.FesodAutoConfiguration + diff --git a/fesod-spring-boot-starter/src/test/java/org/apache/fesod/spring/boot/autoconfigure/FesodAutoConfigurationTest.java b/fesod-spring-boot-starter/src/test/java/org/apache/fesod/spring/boot/autoconfigure/FesodAutoConfigurationTest.java new file mode 100644 index 000000000..704abfe16 --- /dev/null +++ b/fesod-spring-boot-starter/src/test/java/org/apache/fesod/spring/boot/autoconfigure/FesodAutoConfigurationTest.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.spring.boot.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; +import org.apache.fesod.excel.read.builder.ExcelReaderBuilder; +import org.apache.fesod.excel.read.metadata.ReadWorkbook; +import org.apache.fesod.excel.write.builder.ExcelWriterBuilder; +import org.apache.fesod.excel.write.metadata.WriteWorkbook; +import org.apache.fesod.spring.boot.FesodTemplate; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.test.util.ReflectionTestUtils; + +class FesodAutoConfigurationTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(FesodAutoConfiguration.class)); + + @Test + void contextProvidesFesodTemplate() { + contextRunner.run(context -> assertThat(context).hasSingleBean(FesodTemplate.class)); + } + + @Test + void globalAndSectionPropertiesAreApplied() { + contextRunner + .withPropertyValues( + "fesod.global.auto-trim=false", + "fesod.global.use1904-windowing=true", + "fesod.writer.with-bom=false", + "fesod.reader.ignore-empty-row=false") + .run(context -> { + FesodTemplate template = context.getBean(FesodTemplate.class); + + ExcelWriterBuilder writerBuilder = template.writer(); + WriteWorkbook writeWorkbook = + (WriteWorkbook) ReflectionTestUtils.getField(writerBuilder, "writeWorkbook"); + assertThat(writeWorkbook).isNotNull(); + assertThat(writeWorkbook.getAutoTrim()).isFalse(); + assertThat(writeWorkbook.getUse1904windowing()).isTrue(); + assertThat(writeWorkbook.getWithBom()).isFalse(); + + ExcelReaderBuilder readerBuilder = template.reader(); + ReadWorkbook readWorkbook = + (ReadWorkbook) ReflectionTestUtils.getField(readerBuilder, "readWorkbook"); + assertThat(readWorkbook).isNotNull(); + assertThat(readWorkbook.getIgnoreEmptyRow()).isFalse(); + assertThat(readWorkbook.getAutoTrim()).isFalse(); + }); + } + + @Test + void customizersAreInvoked() { + contextRunner + .withBean( + "writerCustomizer", + FesodWriterBuilderCustomizer.class, + () -> builder -> builder.inMemory(Boolean.TRUE)) + .withBean( + "readerCustomizer", + FesodReaderBuilderCustomizer.class, + () -> builder -> builder.mandatoryUseInputStream(Boolean.TRUE)) + .run(context -> { + FesodTemplate template = context.getBean(FesodTemplate.class); + + ExcelWriterBuilder writerBuilder = template.writer(); + WriteWorkbook writeWorkbook = + (WriteWorkbook) ReflectionTestUtils.getField(writerBuilder, "writeWorkbook"); + assertThat(writeWorkbook.getInMemory()).isTrue(); + + ExcelReaderBuilder readerBuilder = template.reader(); + ReadWorkbook readWorkbook = + (ReadWorkbook) ReflectionTestUtils.getField(readerBuilder, "readWorkbook"); + assertThat(readWorkbook.getMandatoryUseInputStream()).isTrue(); + }); + } +} diff --git a/fesod-spring-boot-starter/src/test/java/org/apache/fesod/spring/boot/autoconfigure/FesodPropertiesBindingTest.java b/fesod-spring-boot-starter/src/test/java/org/apache/fesod/spring/boot/autoconfigure/FesodPropertiesBindingTest.java new file mode 100644 index 000000000..cdc360d94 --- /dev/null +++ b/fesod-spring-boot-starter/src/test/java/org/apache/fesod/spring/boot/autoconfigure/FesodPropertiesBindingTest.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fesod.spring.boot.autoconfigure; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * Basic binding test to verify {@link FesodProperties} is bound from environment properties. + */ +@ExtendWith(SpringExtension.class) +@SpringBootTest( + classes = FesodPropertiesBindingTest.TestConfig.class, + properties = {"fesod.global.auto-trim=true", "fesod.reader.ignore-empty-row=true", "fesod.writer.in-memory=true" + }) +class FesodPropertiesBindingTest { + + @Autowired + private FesodProperties properties; + + @Test + void propertiesAreBound() { + assertNotNull(properties); + assertNotNull(properties.getGlobal()); + assertTrue(properties.getGlobal().getAutoTrim(), "autoTrim should bind to true"); + assertNotNull(properties.getReader()); + assertTrue(properties.getReader().getIgnoreEmptyRow(), "ignoreEmptyRow should bind to true"); + assertNotNull(properties.getWriter()); + assertTrue(properties.getWriter().getInMemory(), "inMemory should bind to true"); + } + + @Configuration + @EnableConfigurationProperties(FesodProperties.class) + static class TestConfig {} +} diff --git a/pom.xml b/pom.xml index 153e13d96..fd24492e4 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,7 @@ fesod fesod-examples + fesod-spring-boot-starter diff --git a/website/docs/read/spring.md b/website/docs/read/spring.md index 0d25d158c..ef3ed2a77 100644 --- a/website/docs/read/spring.md +++ b/website/docs/read/spring.md @@ -116,3 +116,83 @@ You can override the `onException` method in custom listeners for detailed excep In real-world scenarios, parsed data may be stored in a database. Database interaction logic can be implemented in the `doAfterAllAnalysed` method to ensure data persistence. + + +## Spring Boot Starter + +For Spring Boot 2.x or 3.x applications you can depend on the starter to get auto-configuration, a preconfigured `FesodTemplate` bean, and property binding out of the box. The starter keeps its Spring Boot dependencies in `provided` scope, so your application controls the exact Boot version it runs with. + +```xml + + org.apache.fesod + fesod-spring-boot-starter + version + +``` + +Inject the template anywhere in your application to obtain reader builders: + +```java +@Service +@RequiredArgsConstructor +public class SampleService { + private final FesodTemplate fesodTemplate; + + @Data + public static class Sample { + private String data; + } + + public List read(String filePath) throws IOException { + List result = new LinkedList<>(); + fesodTemplate.reader(filePath, Sample.class, new ReadListener() { + @Override + public void invoke(Sample data, AnalysisContext context) { + result.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext context) { + } + }).doReadAll(); + return result; + } +} +``` + +> ℹ️ When using Spring Boot 3.x, make sure your project targets Java 17 or newer, as required by Spring Boot 3. + +Starter behaviour can be tuned through standard Spring configuration properties (`application.yml` shown, `application.properties` also supported): + +```yaml +fesod: + global: + auto-trim: true + locale: zh-CN + reader: + ignore-empty-row: true + auto-close-stream: true + writer: + in-memory: false + write-excel-on-exception: true +``` + +By default (without explicit configuration) the starter keeps the same defaults as core Fesod: + +| Property | Default | Description | +| --- | --- | --- | +| `fesod.global.auto-trim` | `true` | Trim leading/trailing whitespace for sheet names and cell values | +| `fesod.global.auto-strip` | `false` | Strip non-printable characters only when enabled | +| `fesod.global.use1904-windowing` | `false` | Use the 1900 Excel date system | +| `fesod.global.use-scientific-format` | `false` | Reader-specific toggle for scientific number formatting | +| `fesod.global.locale` | JVM default locale | Drives number/date formatting | +| `fesod.global.filed-cache-location` | `THREAD_LOCAL` | Metadata cache strategy | +| `fesod.reader.ignore-empty-row` | `true` | Skip blank rows during reads | +| `fesod.reader.auto-close-stream` | `true` | Close input streams when finished | +| `fesod.reader.mandatory-use-input-stream` | `false` | If `false`, streams may be spooled to disk for performance | +| `fesod.writer.auto-close-stream` | `true` | Close output streams automatically | +| `fesod.writer.with-bom` | `true` | Emit BOM for CSV output | +| `fesod.writer.in-memory` | `false` | Write through temporary files before finalizing | +| `fesod.writer.write-excel-on-exception` | `false` | Do not keep partially written files on failure | + +For advanced scenarios you can also contribute `FesodReaderBuilderCustomizer` or `FesodWriterBuilderCustomizer` beans to tweak the underlying builders before every operation. diff --git a/website/docs/write/spring.md b/website/docs/write/spring.md index 66a9d507e..737d98a44 100644 --- a/website/docs/write/spring.md +++ b/website/docs/write/spring.md @@ -28,3 +28,74 @@ public void download(HttpServletResponse response) throws IOException { .doWrite(data()); } ``` + + + +## Spring Boot Starter + +For Spring Boot 2.x or 3.x applications you can depend on the starter to get auto-configuration, a preconfigured `FesodTemplate` bean, and property binding out of the box. The starter keeps its Spring Boot dependencies in `provided` scope, so your application controls the exact Boot version it runs with. + +```xml + + org.apache.fesod + fesod-spring-boot-starter + version + +``` + +Inject the template anywhere in your application to obtain writer builders: + +```java +@Service +public class ReportService { + + private final FesodTemplate fesodTemplate; + + public ReportService(FesodTemplate fesodTemplate) { + this.fesodTemplate = fesodTemplate; + } + + public void exportReport(OutputStream out, Class head, Collection rows) { + fesodTemplate.writer(out, head) + .sheet("report") + .doWrite(rows); + } +} +``` + +> ℹ️ When using Spring Boot 3.x, make sure your project targets Java 17 or newer, as required by Spring Boot 3. + +Starter behaviour can be tuned through standard Spring configuration properties (`application.yml` shown, `application.properties` also supported): + +```yaml +fesod: + global: + auto-trim: true + locale: zh-CN + reader: + ignore-empty-row: true + auto-close-stream: true + writer: + in-memory: false + write-excel-on-exception: true +``` + +By default (without explicit configuration) the starter keeps the same defaults as core Fesod: + +| Property | Default | Description | +| --- | --- | --- | +| `fesod.global.auto-trim` | `true` | Trim leading/trailing whitespace for sheet names and cell values | +| `fesod.global.auto-strip` | `false` | Strip non-printable characters only when enabled | +| `fesod.global.use1904-windowing` | `false` | Use the 1900 Excel date system | +| `fesod.global.use-scientific-format` | `false` | Reader-specific toggle for scientific number formatting | +| `fesod.global.locale` | JVM default locale | Drives number/date formatting | +| `fesod.global.filed-cache-location` | `THREAD_LOCAL` | Metadata cache strategy | +| `fesod.reader.ignore-empty-row` | `true` | Skip blank rows during reads | +| `fesod.reader.auto-close-stream` | `true` | Close input streams when finished | +| `fesod.reader.mandatory-use-input-stream` | `false` | If `false`, streams may be spooled to disk for performance | +| `fesod.writer.auto-close-stream` | `true` | Close output streams automatically | +| `fesod.writer.with-bom` | `true` | Emit BOM for CSV output | +| `fesod.writer.in-memory` | `false` | Write through temporary files before finalizing | +| `fesod.writer.write-excel-on-exception` | `false` | Do not keep partially written files on failure | + +For advanced scenarios you can also contribute `FesodReaderBuilderCustomizer` or `FesodWriterBuilderCustomizer` beans to tweak the underlying builders before every operation.