1 | /* |
2 | * Copyright 2006-2007 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.ApplicationContextJobFactory; |
31 | import org.springframework.batch.core.configuration.support.ClassPathXmlApplicationContextFactory; |
32 | import org.springframework.batch.core.launch.JobLauncher; |
33 | import org.springframework.beans.factory.BeanFactory; |
34 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; |
35 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; |
36 | import org.springframework.beans.factory.xml.XmlBeanFactory; |
37 | import org.springframework.context.ApplicationContext; |
38 | import org.springframework.context.support.ClassPathXmlApplicationContext; |
39 | import org.springframework.core.io.Resource; |
40 | import org.springframework.util.Assert; |
41 | |
42 | /** |
43 | * <p> |
44 | * Command line launcher for registering jobs with a {@link JobRegistry}. |
45 | * Normally this will be used in conjunction with an external trigger for the |
46 | * jobs registered, e.g. a JMX MBean wrapper for a {@link JobLauncher}, or a |
47 | * Quartz trigger. |
48 | * </p> |
49 | * |
50 | * <p> |
51 | * With any launch of a batch job within Spring Batch, a Spring context |
52 | * containing the {@link Job} has to be created. Using this launcher, the jobs |
53 | * are all registered with a {@link JobRegistry} defined in a parent application |
54 | * context. The jobs are then set up in child contexts. All dependencies of the |
55 | * runner will then be satisfied by autowiring by type from the parent |
56 | * application context. Default values are provided for all fields except the |
57 | * {@link JobRegistry}. Therefore, if autowiring fails to set it then an |
58 | * exception will be thrown. |
59 | * </p> |
60 | * |
61 | * @author Dave Syer |
62 | * |
63 | */ |
64 | public class JobRegistryBackgroundJobRunner { |
65 | |
66 | /** |
67 | * System property key that switches the runner to "embedded" mode |
68 | * (returning immediately from the main method). Useful for testing |
69 | * purposes. |
70 | */ |
71 | public static final String EMBEDDED = JobRegistryBackgroundJobRunner.class.getSimpleName() + ".EMBEDDED"; |
72 | |
73 | private static Log logger = LogFactory.getLog(JobRegistryBackgroundJobRunner.class); |
74 | |
75 | private JobRegistry registry; |
76 | |
77 | private ApplicationContext parentContext = null; |
78 | |
79 | public static boolean testing = false; |
80 | |
81 | final private String parentContextPath; |
82 | |
83 | private static List<Exception> errors = Collections.synchronizedList(new ArrayList<Exception>()); |
84 | |
85 | /** |
86 | * @param parentContextPath |
87 | */ |
88 | public JobRegistryBackgroundJobRunner(String parentContextPath) { |
89 | super(); |
90 | this.parentContextPath = parentContextPath; |
91 | } |
92 | |
93 | /** |
94 | * Public setter for the {@link JobRegistry}. |
95 | * @param registry the registry to set |
96 | */ |
97 | public void setRegistry(JobRegistry registry) { |
98 | this.registry = registry; |
99 | } |
100 | |
101 | /** |
102 | * Public getter for the startup errors encountered during parent context |
103 | * creation. |
104 | * @return the errors |
105 | */ |
106 | public static List<Exception> getErrors() { |
107 | synchronized (errors) { |
108 | return new ArrayList<Exception>(errors); |
109 | } |
110 | } |
111 | |
112 | private void register(String[] paths) throws DuplicateJobException, IOException { |
113 | |
114 | for (int i = 0; i < paths.length; i++) { |
115 | |
116 | Resource[] resources = parentContext.getResources(paths[i]); |
117 | |
118 | for (int j = 0; j < resources.length; j++) { |
119 | |
120 | Resource path = resources[j]; |
121 | logger.info("Registering Job definitions from " + Arrays.toString(resources)); |
122 | |
123 | ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(path, parentContext |
124 | .getAutowireCapableBeanFactory()); |
125 | String[] names = beanFactory.getBeanNamesForType(Job.class); |
126 | |
127 | for (int k = 0; k < names.length; k++) { |
128 | ClassPathXmlApplicationContextFactory factory = new ClassPathXmlApplicationContextFactory(); |
129 | factory.setApplicationContext(parentContext); |
130 | factory.setPath(path); |
131 | logger.info("Registering Job definition: " + names[k]); |
132 | registry.register(new ApplicationContextJobFactory(factory, names[k])); |
133 | } |
134 | |
135 | } |
136 | |
137 | } |
138 | |
139 | } |
140 | |
141 | /** |
142 | * Supply a list of application context locations, starting with the parent |
143 | * context, and followed by the children. The parent must contain a |
144 | * {@link JobRegistry} and the child contexts are expected to contain |
145 | * {@link Job} definitions, each of which will be registered wit the |
146 | * registry. |
147 | * |
148 | * Example usage: |
149 | * |
150 | * <pre> |
151 | * $ java -classpath ... JobRegistryBackgroundJobRunner job-registry-context.xml job1.xml job2.xml ... |
152 | * </pre> |
153 | * |
154 | * The child contexts are created only when needed though the |
155 | * {@link JobFactory} interface (but the XML is validated on startup by |
156 | * using it to create a {@link BeanFactory} which is then discarded). |
157 | * |
158 | * The parent context is created in a separate thread, and the program will |
159 | * pause for input in an infinite loop until the user hits any key. |
160 | * |
161 | * @param args the context locations to use (first one is for parent) |
162 | * @throws Exception if anything goes wrong with the context creation |
163 | */ |
164 | public static void main(String... args) throws Exception { |
165 | |
166 | Assert.state(args.length >= 1, "At least one argument (the parent context path) must be provided."); |
167 | |
168 | final JobRegistryBackgroundJobRunner launcher = new JobRegistryBackgroundJobRunner(args[0]); |
169 | errors.clear(); |
170 | |
171 | logger.info("Starting job registry in parent context from XML at: [" + args[0] + "]"); |
172 | |
173 | new Thread(new Runnable() { |
174 | public void run() { |
175 | try { |
176 | launcher.run(); |
177 | } |
178 | catch (RuntimeException e) { |
179 | errors.add(e); |
180 | throw e; |
181 | } |
182 | }; |
183 | }).start(); |
184 | |
185 | logger.info("Waiting for parent context to start."); |
186 | while (launcher.parentContext == null && errors.isEmpty()) { |
187 | Thread.sleep(100L); |
188 | } |
189 | |
190 | synchronized (errors) { |
191 | if (!errors.isEmpty()) { |
192 | logger.info(errors.size() + " errors detected on startup of parent context. Rethrowing."); |
193 | throw errors.get(0); |
194 | } |
195 | } |
196 | errors.clear(); |
197 | |
198 | // Paths to individual job configurations. |
199 | final String[] paths = new String[args.length - 1]; |
200 | System.arraycopy(args, 1, paths, 0, paths.length); |
201 | |
202 | logger.info("Parent context started. Registering jobs from paths: " + Arrays.asList(paths)); |
203 | launcher.register(paths); |
204 | |
205 | if (System.getProperty(EMBEDDED) != null) { |
206 | return; |
207 | } |
208 | |
209 | synchronized (JobRegistryBackgroundJobRunner.class) { |
210 | System.out |
211 | .println("Started application. Interrupt (CTRL-C) or call JobRegistryBackgroundJobRunner.stop() to exit."); |
212 | JobRegistryBackgroundJobRunner.class.wait(); |
213 | } |
214 | |
215 | } |
216 | |
217 | private void run() { |
218 | final ApplicationContext parent = new ClassPathXmlApplicationContext(parentContextPath); |
219 | parent.getAutowireCapableBeanFactory().autowireBeanProperties(this, |
220 | AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false); |
221 | parent.getAutowireCapableBeanFactory().initializeBean(this, getClass().getSimpleName()); |
222 | this.parentContext = parent; |
223 | } |
224 | |
225 | /** |
226 | * If embedded in a JVM, call this method to terminate the main method. |
227 | */ |
228 | public static void stop() { |
229 | synchronized (JobRegistryBackgroundJobRunner.class) { |
230 | JobRegistryBackgroundJobRunner.class.notify(); |
231 | } |
232 | } |
233 | |
234 | } |