XlsSaver.java

  1. package com.gh.mygreen.xlsmapper;

  2. import java.io.IOException;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. import java.lang.annotation.Annotation;
  6. import java.lang.reflect.Field;
  7. import java.lang.reflect.Method;
  8. import java.util.ArrayList;
  9. import java.util.Collections;
  10. import java.util.List;

  11. import org.apache.poi.ss.usermodel.Sheet;
  12. import org.apache.poi.ss.usermodel.Workbook;
  13. import org.apache.poi.ss.usermodel.WorkbookFactory;
  14. import org.slf4j.Logger;
  15. import org.slf4j.LoggerFactory;

  16. import com.gh.mygreen.xlsmapper.annotation.XlsFieldProcessor;
  17. import com.gh.mygreen.xlsmapper.annotation.XlsListener;
  18. import com.gh.mygreen.xlsmapper.annotation.XlsPostSave;
  19. import com.gh.mygreen.xlsmapper.annotation.XlsPreSave;
  20. import com.gh.mygreen.xlsmapper.annotation.XlsSheet;
  21. import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessor;
  22. import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorFactory;
  23. import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorProxy;
  24. import com.gh.mygreen.xlsmapper.fieldaccessor.FieldAccessorProxyComparator;
  25. import com.gh.mygreen.xlsmapper.fieldprocessor.FieldProcessor;
  26. import com.gh.mygreen.xlsmapper.fieldprocessor.ProcessCase;
  27. import com.gh.mygreen.xlsmapper.localization.MessageBuilder;
  28. import com.gh.mygreen.xlsmapper.util.ArgUtils;
  29. import com.gh.mygreen.xlsmapper.util.ClassUtils;
  30. import com.gh.mygreen.xlsmapper.util.Utils;
  31. import com.gh.mygreen.xlsmapper.validation.MultipleSheetBindingErrors;
  32. import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors;
  33. import com.gh.mygreen.xlsmapper.xml.AnnotationReader;


  34. /**
  35.  * JavaBeanをExcelのシートにマッピングし出力するクラス。
  36.  *
  37.  * @version 2.0
  38.  * @author T.TSUCHIE
  39.  *
  40.  */
  41. public class XlsSaver {

  42.     private static final Logger logger = LoggerFactory.getLogger(XlsSaver.class);

  43.     private Configuration configuration;

  44.     /**
  45.      * 独自のシステム情報を設定するコンストラクタ
  46.      * @param configuration システム情報
  47.      */
  48.     public XlsSaver(Configuration configuration) {
  49.         this.configuration = configuration;
  50.     }

  51.     /**
  52.      * デフォルトのコンストラクタ
  53.      */
  54.     public XlsSaver() {
  55.         this(new Configuration());
  56.     }

  57.     /**
  58.      * JavaのオブジェクトをExeclファイルに出力する。
  59.      * <p>出力するファイルは、引数で指定した雛形となるテンプレート用のExcelファイルをもとに出力する。</p>
  60.      *
  61.      * @param templateXlsIn 雛形となるExcelファイルの入力
  62.      * @param xlsOut 出力先のストリーム
  63.      * @param beanObj 書き込むBeanオブジェクト
  64.      * @throws IllegalArgumentException {@literal templateXlsIn == null or xlsOut == null or beanObj == null}
  65.      * @throws XlsMapperException マッピングに失敗した場合
  66.      * @throws IOException テンプレートのファイルの読み込みやファイルの出力に失敗した場合
  67.      */
  68.     public void save(final InputStream templateXlsIn, final OutputStream xlsOut, final Object beanObj)
  69.             throws XlsMapperException, IOException {

  70.         saveDetail(templateXlsIn, xlsOut, beanObj);
  71.     }

  72.     /**
  73.      * JavaのオブジェクトをExeclファイルに出力する。
  74.      * <p>出力するファイルは、引数で指定した雛形となるテンプレート用のExcelファイルをもとに出力する。</p>
  75.      *
  76.      * @param <P> マッピング対象のクラスタイプ
  77.      * @param templateXlsIn 雛形となるExcelファイルの入力
  78.      * @param xlsOut 出力先のストリーム
  79.      * @param beanObj 書き込むBeanオブジェクト
  80.      * @return マッピング結果。
  81.      *         {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、nullを返します。
  82.      * @throws IllegalArgumentException {@literal templateXlsIn == null or xlsOut == null or beanObj == null}
  83.      * @throws XlsMapperException マッピングに失敗した場合
  84.      * @throws IOException テンプレートのファイルの読み込みやファイルの出力に失敗した場合
  85.      */
  86.     public <P> SheetBindingErrors<P> saveDetail(final InputStream templateXlsIn, final OutputStream xlsOut, final P beanObj)
  87.             throws XlsMapperException, IOException {

  88.         ArgUtils.notNull(templateXlsIn, "templateXlsIn");
  89.         ArgUtils.notNull(xlsOut, "xlsOut");
  90.         ArgUtils.notNull(beanObj, "beanObj");


  91.         final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));

  92.         try(Workbook book = WorkbookFactory.create(templateXlsIn)) {

  93.             final Class<?> clazz = beanObj.getClass();
  94.             final XlsSheet sheetAnno = clazz.getAnnotation(XlsSheet.class);
  95.             if(sheetAnno == null) {
  96.                 throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.notFound")
  97.                         .varWithClass("property", clazz)
  98.                         .varWithAnno("anno", XlsSheet.class)
  99.                         .format());
  100.    
  101.             }
  102.    
  103.             final SheetBindingErrors<P> bindingResult;
  104.             try {
  105.                 final Sheet[] xlsSheet = configuration.getSheetFinder().findForSaving(book, sheetAnno, annoReader, beanObj);
  106.                 bindingResult = saveSheet(xlsSheet[0], beanObj, annoReader);
  107.    
  108.             } catch(SheetNotFoundException e) {
  109.                 if(configuration.isIgnoreSheetNotFound()){
  110.                     logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), e);
  111.                     return null;
  112.    
  113.                 } else {
  114.                     throw e;
  115.                 }
  116.             }
  117.    
  118.             if(configuration.isFormulaRecalcurationOnSave()) {
  119.                 book.setForceFormulaRecalculation(true);
  120.             }
  121.    
  122.             book.write(xlsOut);
  123.    
  124.             return bindingResult;
  125.            
  126.         }

  127.     }

  128.     /**
  129.      * 複数のオブジェクトをそれぞれのシートへ保存する。
  130.      * @param templateXlsIn 雛形となるExcelファイルの入力
  131.      * @param xlsOut xlsOut 出力先のストリーム
  132.      * @param beanObjs 書き込むオブジェクトの配列。
  133.      * @throws IllegalArgumentException {@literal templateXlsIn == null or xlsOut == null or beanObjs == null}
  134.      * @throws XlsMapperException マッピングに失敗した場合
  135.      * @throws IOException テンプレートのファイルの読み込みやファイルの出力に失敗した場合
  136.      */
  137.     public void saveMultiple(final InputStream templateXlsIn, final OutputStream xlsOut, final Object[] beanObjs)
  138.             throws XlsMapperException, IOException {

  139.         saveMultipleDetail(templateXlsIn, xlsOut, beanObjs);
  140.     }

  141.     /**
  142.      * 複数のオブジェクトをそれぞれのシートへ保存する。
  143.      * @param templateXlsIn 雛形となるExcelファイルの入力
  144.      * @param xlsOut xlsOut 出力先のストリーム
  145.      * @param beanObjs 書き込むオブジェクトの配列。
  146.      * @return マッピング結果。
  147.      *         {@link Configuration#isIgnoreSheetNotFound()}の値がtrueで、シートが見つからない場合、結果に含まれません。
  148.      * @throws IllegalArgumentException {@literal templateXlsIn == null or xlsOut == null or beanObjs == null}
  149.      * @throws XlsMapperException マッピングに失敗した場合
  150.      * @throws IOException テンプレートのファイルの読み込みやファイルの出力に失敗した場合
  151.      */
  152.     public MultipleSheetBindingErrors<Object> saveMultipleDetail(final InputStream templateXlsIn, final OutputStream xlsOut, final Object[] beanObjs)
  153.             throws XlsMapperException, IOException {

  154.         ArgUtils.notNull(templateXlsIn, "templateXlsIn");
  155.         ArgUtils.notNull(xlsOut, "xlsOut");
  156.         ArgUtils.notEmpty(beanObjs, "beanObjs");

  157.         final AnnotationReader annoReader = new AnnotationReader(configuration.getAnnotationMapping().orElse(null));

  158.         final MultipleSheetBindingErrors<Object> multipleResult = new MultipleSheetBindingErrors<>();

  159.         try(Workbook book = WorkbookFactory.create(templateXlsIn)) {

  160.             for(int i=0; i < beanObjs.length; i++) {
  161.                 final Object beanObj = beanObjs[i];
  162.                 final Class<?> clazz = beanObj.getClass();
  163.    
  164.                 final XlsSheet sheetAnno = annoReader.getAnnotation(clazz, XlsSheet.class);
  165.                 if(sheetAnno == null) {
  166.                     throw new AnnotationInvalidException(sheetAnno, MessageBuilder.create("anno.notFound")
  167.                             .varWithClass("property", clazz)
  168.                             .varWithAnno("anno", XlsSheet.class)
  169.                             .format());
  170.                 }
  171.    
  172.                 try {
  173.                     final Sheet[] xlsSheet = configuration.getSheetFinder().findForSaving(book, sheetAnno, annoReader, beanObj);
  174.                     multipleResult.addBindingErrors(saveSheet(xlsSheet[0], beanObj, annoReader));
  175.    
  176.                 } catch(SheetNotFoundException e) {
  177.                     if(configuration.isIgnoreSheetNotFound()){
  178.                         logger.warn(MessageBuilder.create("log.skipNotFoundSheet").format(), e);
  179.                         continue;
  180.                     } else {
  181.                         throw e;
  182.                     }
  183.                 }
  184.             }
  185.    
  186.             if(configuration.isFormulaRecalcurationOnSave()) {
  187.                 book.setForceFormulaRecalculation(true);
  188.             }
  189.    
  190.             book.write(xlsOut);
  191.    
  192.             return multipleResult;
  193.            
  194.         }

  195.     }

  196.     /**
  197.      * 任意のクラスのオブジェクトを、Excelシートにマッピングする。
  198.      * @param sheet
  199.      * @param beanObj
  200.      * @param configuration
  201.      * @param work
  202.      * @throws XlsMapperException
  203.      */
  204.     private <P> SheetBindingErrors<P> saveSheet(final Sheet sheet, final P beanObj, final AnnotationReader annoReader)
  205.             throws XlsMapperException {

  206.         final Class<?> clazz = beanObj.getClass();

  207.         final SheetBindingErrors<P> errors =  configuration.getBindingErrorsFactory().create(beanObj);
  208.         errors.setSheetName(sheet.getSheetName());
  209.         errors.setSheetIndex(sheet.getWorkbook().getSheetIndex(sheet));

  210.         final SavingWorkObject work = new SavingWorkObject();
  211.         work.setAnnoReader(annoReader);
  212.         work.setErrors(errors);

  213.         // セルのキャッシュ情報の初期化
  214.         configuration.getCellFormatter().init(false);

  215.         final FieldAccessorFactory adpterFactory = new FieldAccessorFactory(annoReader);

  216.         // リスナークラスの@PreSave用メソッドの実行
  217.         final XlsListener listenerAnno = annoReader.getAnnotation(beanObj.getClass(), XlsListener.class);
  218.         if(listenerAnno != null) {
  219.             for(Class<?> listenerClass : listenerAnno.value()) {
  220.                 final Object listenerObj = configuration.createBean(listenerClass);

  221.                 for(Method method : listenerObj.getClass().getMethods()) {
  222.                     if(annoReader.hasAnnotation(method, XlsPreSave.class)) {
  223.                         Utils.invokeNeedProcessMethod(listenerObj, method, beanObj, sheet, configuration, work.getErrors(), ProcessCase.Save);
  224.                     }
  225.                 }
  226.             }

  227.         }

  228.         // @PreSave用のメソッドの取得と実行
  229.         for(Method method : clazz.getMethods()) {

  230.             final XlsPreSave preProcessAnno = annoReader.getAnnotation(method, XlsPreSave.class);
  231.             if(preProcessAnno != null) {
  232.                 Utils.invokeNeedProcessMethod(beanObj, method, beanObj, sheet, configuration, work.getErrors(), ProcessCase.Save);
  233.             }
  234.         }

  235.         final List<FieldAccessorProxy> accessorProxies = new ArrayList<>();

  236.         // public メソッドの処理
  237.         for(Method method : clazz.getMethods()) {
  238.             method.setAccessible(true);

  239.             for(Annotation anno : annoReader.getAnnotations(method)) {

  240.                 final XlsFieldProcessor annoFieldProcessor = anno.annotationType().getAnnotation(XlsFieldProcessor.class);
  241.                 if(ClassUtils.isAccessorMethod(method) && annoFieldProcessor != null) {

  242.                     // 登録済みのFieldProcessorの取得
  243.                     FieldProcessor<?> processor = configuration.getFieldProcessorRegistry().getProcessor(anno.annotationType());

  244.                     // アノテーションに指定されているFieldProcessorの場合
  245.                     if(processor == null && annoFieldProcessor.value().length > 0) {
  246.                         processor = configuration.createBean(annoFieldProcessor.value()[0]);

  247.                     }

  248.                     if(processor != null) {
  249.                         final FieldAccessor accessor = adpterFactory.create(method);

  250.                         final FieldAccessorProxy accessorProxy = new FieldAccessorProxy(anno, processor, accessor);
  251.                         if(!accessorProxies.contains(accessorProxy)) {
  252.                             accessorProxies.add(accessorProxy);
  253.                         }

  254.                     } else {
  255.                         // FieldProcessorが見つからない場合
  256.                         throw new AnnotationInvalidException(anno, MessageBuilder.create("anno.XlsFieldProcessor.notResolve")
  257.                                 .varWithAnno("anno", anno.annotationType())
  258.                                 .format());
  259.                     }

  260.                 }

  261.                 if(anno instanceof XlsPostSave) {
  262.                     work.addNeedPostProcess(new NeedProcess(beanObj, beanObj, method));
  263.                 }
  264.             }
  265.         }

  266.         // フィールドの処理
  267.         for(Field field : clazz.getDeclaredFields()) {

  268.             field.setAccessible(true);
  269.             final FieldAccessor accessor = adpterFactory.create(field);

  270.             for(Annotation anno : annoReader.getAnnotations(field)) {

  271.                 final XlsFieldProcessor annoFieldProcessor = anno.annotationType().getAnnotation(XlsFieldProcessor.class);
  272.                 if(annoFieldProcessor != null) {
  273.                     // 登録済みのFieldProcessorの取得
  274.                     FieldProcessor<?> processor = configuration.getFieldProcessorRegistry().getProcessor(anno.annotationType());

  275.                     // アノテーションに指定されているFieldProcessorの場合
  276.                     if(processor == null && annoFieldProcessor.value().length > 0) {
  277.                         processor = configuration.createBean(annoFieldProcessor.value()[0]);
  278.                     }

  279.                     if(processor != null) {
  280.                         final FieldAccessorProxy accessorProxy = new FieldAccessorProxy(anno, processor, accessor);
  281.                         if(!accessorProxies.contains(accessorProxy)) {
  282.                             accessorProxies.add(accessorProxy);
  283.                         }

  284.                     } else {
  285.                         // FieldProcessorが見つからない場合
  286.                         throw new AnnotationInvalidException(anno, MessageBuilder.create("anno.XlsFieldProcessor.notResolve")
  287.                                 .varWithAnno("anno", anno.annotationType())
  288.                                 .format());
  289.                     }

  290.                 }
  291.             }

  292.         }

  293.         // 順番を並び替えて保存処理を実行する
  294.         Collections.sort(accessorProxies, new FieldAccessorProxyComparator());
  295.         for(FieldAccessorProxy accessorProxy : accessorProxies) {
  296.             accessorProxy.saveProcess(sheet, beanObj, configuration, work);
  297.         }

  298.         // リスナークラスの@PostSaveの取得
  299.         if(listenerAnno != null) {
  300.             for(Class<?> listenerClass : listenerAnno.value()) {
  301.                 final Object listenerObj = configuration.createBean(listenerClass);
  302.                 for(Method method : listenerObj.getClass().getMethods()) {
  303.                     if(annoReader.hasAnnotation(method, XlsPostSave.class)) {
  304.                         work.addNeedPostProcess(new NeedProcess(beanObj, listenerObj, method));
  305.                     }
  306.                 }
  307.             }

  308.         }

  309.         //@PostSaveが付与されているメソッドの実行
  310.         for(NeedProcess need : work.getNeedPostProcesses()) {
  311.             Utils.invokeNeedProcessMethod(need.getProcess(), need.getMethod(), need.getTarget(), sheet, configuration, work.getErrors(), ProcessCase.Save);
  312.         }

  313.         return errors;

  314.     }

  315.     /**
  316.      * システム情報を取得します。
  317.      * @return 現在のシステム情報
  318.      */
  319.     public Configuration getConfiguration() {
  320.         return configuration;
  321.     }

  322.     /**
  323.      * システム情報を設定します。
  324.      * @param configuration システム情報
  325.      */
  326.     public void setConfiguration(Configuration configuration) {
  327.         this.configuration = configuration;
  328.     }

  329. }