001/* 002 * Copyright 2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.batch.item.data.builder; 018 019 020import java.lang.reflect.Method; 021import java.util.ArrayList; 022import java.util.Arrays; 023import java.util.List; 024 025import org.springframework.batch.item.data.RepositoryItemWriter; 026import org.springframework.cglib.proxy.Enhancer; 027import org.springframework.cglib.proxy.MethodInterceptor; 028import org.springframework.cglib.proxy.MethodProxy; 029import org.springframework.data.repository.CrudRepository; 030import org.springframework.util.Assert; 031 032/** 033 * A builder implementation for the {@link RepositoryItemWriter}. 034 * 035 * @author Glenn Renfro 036 * @since 4.0 037 * @see RepositoryItemWriter 038 */ 039public class RepositoryItemWriterBuilder<T> { 040 041 private CrudRepository<T, ?> repository; 042 043 private String methodName; 044 045 private RepositoryMethodReference repositoryMethodReference; 046 047 /** 048 * Specifies what method on the repository to call. This method must have the type of 049 * object passed to this writer as the <em>sole</em> argument. 050 * 051 * @param methodName the name of the method to be used for saving the item. 052 * @return The current instance of the builder. 053 * @see RepositoryItemWriter#setMethodName(String) 054 */ 055 public RepositoryItemWriterBuilder<T> methodName(String methodName) { 056 this.methodName = methodName; 057 058 return this; 059 } 060 061 /** 062 * Set the {@link org.springframework.data.repository.CrudRepository} implementation 063 * for persistence 064 * 065 * @param repository the Spring Data repository to be set 066 * @return The current instance of the builder. 067 * @see RepositoryItemWriter#setRepository(CrudRepository) 068 */ 069 public RepositoryItemWriterBuilder<T> repository(CrudRepository<T, ?> repository) { 070 this.repository = repository; 071 072 return this; 073 } 074 075 /** 076 * Specifies a repository and the type-safe method to call for the writer. The method 077 * configured via this mechanism must take 078 * {@link org.springframework.data.domain.Pageable} as the <em>last</em> 079 * argument. This method can be used in place of {@link #repository(CrudRepository)}, 080 * {@link #methodName(String)}}. 081 * 082 * Note: The repository that is used by the repositoryMethodReference must be 083 * non-final. 084 * 085 * @param repositoryMethodReference of the used to get a repository and type-safe 086 * method for use by the writer. 087 * @return The current instance of the builder. 088 * @see RepositoryItemWriter#setMethodName(String) 089 * @see RepositoryItemWriter#setRepository(CrudRepository) 090 * 091 */ 092 public RepositoryItemWriterBuilder<T> repository(RepositoryItemWriterBuilder.RepositoryMethodReference repositoryMethodReference) { 093 this.repositoryMethodReference = repositoryMethodReference; 094 095 return this; 096 } 097 098 /** 099 * Builds the {@link RepositoryItemWriter}. 100 * 101 * @return a {@link RepositoryItemWriter} 102 */ 103 public RepositoryItemWriter<T> build() { 104 if (this.repositoryMethodReference != null) { 105 this.methodName = this.repositoryMethodReference.getMethodName(); 106 this.repository = this.repositoryMethodReference.getRepository(); 107 } 108 109 Assert.hasText(this.methodName, "methodName is required."); 110 Assert.notNull(this.repository, "repository is required."); 111 112 RepositoryItemWriter<T> writer = new RepositoryItemWriter<>(); 113 writer.setMethodName(this.methodName); 114 writer.setRepository(this.repository); 115 return writer; 116 } 117 118 /** 119 * Establishes a proxy that will capture a the Repository and the associated 120 * methodName that will be used by the writer. 121 * @param <T> The type of repository that will be used by the writer. The class must 122 * not be final. 123 */ 124 public static class RepositoryMethodReference<T> { 125 private RepositoryMethodInterceptor repositoryInvocationHandler; 126 127 private CrudRepository<?, ?> repository; 128 129 public RepositoryMethodReference(CrudRepository<?, ?> repository) { 130 this.repository = repository; 131 this.repositoryInvocationHandler = new RepositoryMethodInterceptor(); 132 } 133 134 /** 135 * The proxy returned prevents actual method execution and is only used to gather, 136 * information about the method. 137 * @return T is a proxy of the object passed in in the constructor 138 */ 139 public T methodIs() { 140 Enhancer enhancer = new Enhancer(); 141 enhancer.setSuperclass(this.repository.getClass()); 142 enhancer.setCallback(this.repositoryInvocationHandler); 143 return (T) enhancer.create(); 144 } 145 146 CrudRepository<?, ?> getRepository() { 147 return this.repository; 148 } 149 150 String getMethodName() { 151 return this.repositoryInvocationHandler.getMethodName(); 152 } 153 } 154 155 private static class RepositoryMethodInterceptor implements MethodInterceptor { 156 private String methodName; 157 158 @Override 159 public Object intercept(Object o, Method method, Object[] objects, 160 MethodProxy methodProxy) throws Throwable { 161 this.methodName = method.getName(); 162 return null; 163 } 164 165 String getMethodName() { 166 return this.methodName; 167 } 168 169 } 170}