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}