1 | /* |
2 | * Copyright 2006-2013 the original author or authors. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | package org.springframework.batch.core.launch.support; |
17 | |
18 | import java.io.IOException; |
19 | import java.util.ArrayList; |
20 | import java.util.Arrays; |
21 | import java.util.Collections; |
22 | import java.util.List; |
23 | |
24 | import org.apache.commons.logging.Log; |
25 | import org.apache.commons.logging.LogFactory; |
26 | import org.springframework.batch.core.Job; |
27 | import org.springframework.batch.core.configuration.DuplicateJobException; |
28 | import org.springframework.batch.core.configuration.JobFactory; |
29 | import org.springframework.batch.core.configuration.JobRegistry; |
30 | import org.springframework.batch.core.configuration.support.DefaultJobLoader; |
31 | import org.springframework.batch.core.configuration.support.GenericApplicationContextFactory; |
32 | import org.springframework.batch.core.configuration.support.JobLoader; |
33 | import org.springframework.batch.core.launch.JobLauncher; |
34 | import org.springframework.beans.factory.BeanFactory; |
35 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; |
36 | import org.springframework.context.ApplicationContext; |
37 | import org.springframework.context.support.ClassPathXmlApplicationContext; |
38 | import org.springframework.core.io.Resource; |
39 | import org.springframework.util.Assert; |
40 | |
41 | /** |
42 | * <p> |
43 | * Command line launcher for registering jobs with a {@link JobRegistry}. |
44 | * Normally this will be used in conjunction with an external trigger for the |
45 | * jobs registered, e.g. a JMX MBean wrapper for a {@link JobLauncher}, or a |
46 | * Quartz trigger. |
47 | * </p> |
48 | * |
49 | * <p> |
50 | * With any launch of a batch job within Spring Batch, a Spring context |
51 | * containing the {@link Job} has to be created. Using this launcher, the jobs |
52 | * are all registered with a {@link JobRegistry} defined in a parent application |
53 | * context. The jobs are then set up in child contexts. All dependencies of the |
54 | * runner will then be satisfied by autowiring by type from the parent |
55 | * application context. Default values are provided for all fields except the |
56 | * {@link JobRegistry}. Therefore, if autowiring fails to set it then an |
57 | * exception will be thrown. |
58 | * </p> |
59 | * |
60 | * @author Dave Syer |
61 | * |
62 | */ |
63 | public class JobRegistryBackgroundJobRunner { |
64 | |
65 | /** |
66 | * System property key that switches the runner to "embedded" mode |
67 | * (returning immediately from the main method). Useful for testing |
68 | * purposes. |
69 | */ |
70 | public static final String EMBEDDED = JobRegistryBackgroundJobRunner.class.getSimpleName() + ".EMBEDDED"; |
71 | |
72 | private static Log logger = LogFactory.getLog(JobRegistryBackgroundJobRunner.class); |
73 | |
74 | private JobLoader jobLoader; |
75 | |
76 | private ApplicationContext parentContext = null; |
77 | |
78 | public static boolean testing = false; |
79 | |
80 | final private String parentContextPath; |
81 | |
82 | private JobRegistry jobRegistry; |
83 | |
84 | private static List<Exception> errors = Collections.synchronizedList(new ArrayList<Exception>()); |
85 | |
86 | /** |
87 | * @param parentContextPath |
88 | */ |
89 | public JobRegistryBackgroundJobRunner(String parentContextPath) { |
90 | super(); |
91 | this.parentContextPath = parentContextPath; |
92 | } |
93 | |
94 | /** |
95 | * A loader for the jobs that are going to be registered. |
96 | * |
97 | * @param jobLoader the {@link JobLoader} to set |
98 | */ |
99 | public void setJobLoader(JobLoader jobLoader) { |
100 | this.jobLoader = jobLoader; |
101 | } |
102 | |
103 | /** |
104 | * A job registry that can be used to create a job loader (if none is provided). |
105 | * |
106 | * @param jobRegistry the {@link JobRegistry} to set |
107 | */ |
108 | public void setJobRegistry(JobRegistry jobRegistry) { |
109 | this.jobRegistry = jobRegistry; |
110 | } |
111 | |
112 | /** |
113 | * Public getter for the startup errors encountered during parent context |
114 | * creation. |
115 | * @return the errors |
116 | */ |
117 | public static List<Exception> getErrors() { |
118 | synchronized (errors) { |
119 | return new ArrayList<Exception>(errors); |
120 | } |
121 | } |
122 | |
123 | private void register(String[] paths) throws DuplicateJobException, IOException { |
124 | |
125 | maybeCreateJobLoader(); |
126 | |
127 | for (int i = 0; i < paths.length; i++) { |
128 | |
129 | Resource[] resources = parentContext.getResources(paths[i]); |
130 | |
131 | for (int j = 0; j < resources.length; j++) { |
132 | |
133 | Resource path = resources[j]; |
134 | logger.info("Registering Job definitions from " + Arrays.toString(resources)); |
135 | |
136 | GenericApplicationContextFactory factory = new GenericApplicationContextFactory(path); |
137 | factory.setApplicationContext(parentContext); |
138 | jobLoader.load(factory); |
139 | } |
140 | |
141 | } |
142 | |
143 | } |
144 | |
145 | /** |
146 | * If there is no {@link JobLoader} then try and create one from existing |
147 | * bean definitions. |
148 | */ |
149 | private void maybeCreateJobLoader() { |
150 | |
151 | if (jobLoader != null) { |
152 | return; |
153 | } |
154 | |
155 | String[] names = parentContext.getBeanNamesForType(JobLoader.class); |
156 | if (names.length == 0) { |
157 | if (parentContext.containsBean("jobLoader")) { |
158 | jobLoader = parentContext.getBean("jobLoader", JobLoader.class); |
159 | return; |
160 | } |
161 | if (jobRegistry != null) { |
162 | jobLoader = new DefaultJobLoader(jobRegistry); |
163 | return; |
164 | } |
165 | } |
166 | |
167 | jobLoader = parentContext.getBean(names[0], JobLoader.class); |
168 | return; |
169 | |
170 | } |
171 | |
172 | /** |
173 | * Supply a list of application context locations, starting with the parent |
174 | * context, and followed by the children. The parent must contain a |
175 | * {@link JobRegistry} and the child contexts are expected to contain |
176 | * {@link Job} definitions, each of which will be registered wit the |
177 | * registry. |
178 | * |
179 | * Example usage: |
180 | * |
181 | * <pre> |
182 | * $ java -classpath ... JobRegistryBackgroundJobRunner job-registry-context.xml job1.xml job2.xml ... |
183 | * </pre> |
184 | * |
185 | * The child contexts are created only when needed though the |
186 | * {@link JobFactory} interface (but the XML is validated on startup by |
187 | * using it to create a {@link BeanFactory} which is then discarded). |
188 | * |
189 | * The parent context is created in a separate thread, and the program will |
190 | * pause for input in an infinite loop until the user hits any key. |
191 | * |
192 | * @param args the context locations to use (first one is for parent) |
193 | * @throws Exception if anything goes wrong with the context creation |
194 | */ |
195 | public static void main(String... args) throws Exception { |
196 | |
197 | Assert.state(args.length >= 1, "At least one argument (the parent context path) must be provided."); |
198 | |
199 | final JobRegistryBackgroundJobRunner launcher = new JobRegistryBackgroundJobRunner(args[0]); |
200 | errors.clear(); |
201 | |
202 | logger.info("Starting job registry in parent context from XML at: [" + args[0] + "]"); |
203 | |
204 | new Thread(new Runnable() { |
205 | @Override |
206 | public void run() { |
207 | try { |
208 | launcher.run(); |
209 | } |
210 | catch (RuntimeException e) { |
211 | errors.add(e); |
212 | throw e; |
213 | } |
214 | }; |
215 | }).start(); |
216 | |
217 | logger.info("Waiting for parent context to start."); |
218 | while (launcher.parentContext == null && errors.isEmpty()) { |
219 | Thread.sleep(100L); |
220 | } |
221 | |
222 | synchronized (errors) { |
223 | if (!errors.isEmpty()) { |
224 | logger.info(errors.size() + " errors detected on startup of parent context. Rethrowing."); |
225 | throw errors.get(0); |
226 | } |
227 | } |
228 | errors.clear(); |
229 | |
230 | // Paths to individual job configurations. |
231 | final String[] paths = new String[args.length - 1]; |
232 | System.arraycopy(args, 1, paths, 0, paths.length); |
233 | |
234 | logger.info("Parent context started. Registering jobs from paths: " + Arrays.asList(paths)); |
235 | launcher.register(paths); |
236 | |
237 | if (System.getProperty(EMBEDDED) != null) { |
238 | launcher.destroy(); |
239 | return; |
240 | } |
241 | |
242 | synchronized (JobRegistryBackgroundJobRunner.class) { |
243 | System.out |
244 | .println("Started application. Interrupt (CTRL-C) or call JobRegistryBackgroundJobRunner.stop() to exit."); |
245 | JobRegistryBackgroundJobRunner.class.wait(); |
246 | } |
247 | launcher.destroy(); |
248 | |
249 | } |
250 | |
251 | /** |
252 | * De-register all the {@link Job} instances that were regsistered by this |
253 | * post processor. |
254 | * @see org.springframework.beans.factory.DisposableBean#destroy() |
255 | */ |
256 | private void destroy() throws Exception { |
257 | jobLoader.clear(); |
258 | } |
259 | |
260 | private void run() { |
261 | final ApplicationContext parent = new ClassPathXmlApplicationContext(parentContextPath); |
262 | parent.getAutowireCapableBeanFactory().autowireBeanProperties(this, |
263 | AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); |
264 | parent.getAutowireCapableBeanFactory().initializeBean(this, getClass().getSimpleName()); |
265 | this.parentContext = parent; |
266 | } |
267 | |
268 | /** |
269 | * If embedded in a JVM, call this method to terminate the main method. |
270 | */ |
271 | public static void stop() { |
272 | synchronized (JobRegistryBackgroundJobRunner.class) { |
273 | JobRegistryBackgroundJobRunner.class.notify(); |
274 | } |
275 | } |
276 | |
277 | } |