001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.camel.spring;
018
019import java.io.BufferedReader;
020import java.io.IOException;
021import java.io.InputStreamReader;
022import java.net.URL;
023import java.nio.charset.Charset;
024import java.util.Enumeration;
025import java.util.LinkedHashSet;
026import java.util.LinkedList;
027import java.util.Map;
028import java.util.Set;
029
030import org.apache.camel.CamelContext;
031import org.apache.camel.ProducerTemplate;
032import org.apache.camel.util.IOHelper;
033import org.springframework.context.ApplicationContext;
034import org.springframework.context.annotation.AnnotationConfigApplicationContext;
035import org.springframework.context.support.AbstractApplicationContext;
036import org.springframework.context.support.ClassPathXmlApplicationContext;
037import org.springframework.context.support.FileSystemXmlApplicationContext;
038
039/**
040 * A command line tool for booting up a CamelContext using an optional Spring
041 * {@link org.springframework.context.ApplicationContext}.
042 * <p/>
043 * By placing a file in the {@link #LOCATION_PROPERTIES} directory of any JARs on the classpath,
044 * allows this Main class to load those additional Spring XML files as Spring
045 * {@link org.springframework.context.ApplicationContext} to be included.
046 * <p/>
047 * Each line in the {@link #LOCATION_PROPERTIES} is a reference to a Spring XML file to include,
048 * which by default gets loaded from classpath.
049 */
050public class Main extends org.apache.camel.main.MainCommandLineSupport {
051
052    public static final String LOCATION_PROPERTIES = "META-INF/camel-spring/location.properties";
053    protected static Main instance;
054    private static final Charset UTF8 = Charset.forName("UTF-8");
055
056    private String applicationContextUri = "META-INF/spring/*.xml";
057    private String fileApplicationContextUri;
058    private AbstractApplicationContext applicationContext;
059    private AbstractApplicationContext parentApplicationContext;
060    private AbstractApplicationContext additionalApplicationContext;
061    private String parentApplicationContextUri;
062
063    public Main() {
064
065        addOption(new ParameterOption("ac", "applicationContext",
066                "Sets the classpath based spring ApplicationContext", "applicationContext") {
067            protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
068                setApplicationContextUri(parameter);
069            }
070        });
071
072        addOption(new ParameterOption("fa", "fileApplicationContext",
073                "Sets the filesystem based spring ApplicationContext", "fileApplicationContext") {
074            protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
075                setFileApplicationContextUri(parameter);
076            }
077        });
078
079    }
080
081    public static void main(String... args) throws Exception {
082        Main main = new Main();
083        instance = main;
084        main.run(args);
085    }
086
087    /**
088     * Returns the currently executing main
089     *
090     * @return the current running instance
091     */
092    public static Main getInstance() {
093        return instance;
094    }
095
096    // Properties
097    // -------------------------------------------------------------------------
098    public AbstractApplicationContext getApplicationContext() {
099        return applicationContext;
100    }
101
102    public void setApplicationContext(AbstractApplicationContext applicationContext) {
103        this.applicationContext = applicationContext;
104    }
105
106    public String getApplicationContextUri() {
107        return applicationContextUri;
108    }
109
110    public void setApplicationContextUri(String applicationContextUri) {
111        this.applicationContextUri = applicationContextUri;
112    }
113
114    public String getFileApplicationContextUri() {
115        return fileApplicationContextUri;
116    }
117
118    public void setFileApplicationContextUri(String fileApplicationContextUri) {
119        this.fileApplicationContextUri = fileApplicationContextUri;
120    }
121
122    public AbstractApplicationContext getParentApplicationContext() {
123        if (parentApplicationContext == null) {
124            if (parentApplicationContextUri != null) {
125                parentApplicationContext = new ClassPathXmlApplicationContext(parentApplicationContextUri);
126                parentApplicationContext.start();
127            }
128        }
129        return parentApplicationContext;
130    }
131
132    public void setParentApplicationContext(AbstractApplicationContext parentApplicationContext) {
133        this.parentApplicationContext = parentApplicationContext;
134    }
135
136    public String getParentApplicationContextUri() {
137        return parentApplicationContextUri;
138    }
139
140    public void setParentApplicationContextUri(String parentApplicationContextUri) {
141        this.parentApplicationContextUri = parentApplicationContextUri;
142    }
143
144    // Implementation methods
145    // -------------------------------------------------------------------------
146
147    @Override
148    protected CamelContext createCamelContext() {
149        Map<String, SpringCamelContext> camels = applicationContext.getBeansOfType(SpringCamelContext.class);
150        if (camels.size() > 1) {
151            throw new IllegalArgumentException("Multiple CamelContext detected. This Main class only supports single CamelContext");
152        } else if (camels.size() == 1) {
153            return camels.values().iterator().next();
154        }
155        return null;
156    }
157
158    @Override
159    protected void doStart() throws Exception {
160        try {
161            super.doStart();
162            if (applicationContext == null) {
163                applicationContext = createDefaultApplicationContext();
164            }
165
166            // then start any additional after Camel has been started
167            if (additionalApplicationContext == null) {
168                additionalApplicationContext = createAdditionalLocationsFromClasspath();
169                if (additionalApplicationContext != null) {
170                    LOG.debug("Starting Additional ApplicationContext: {}", additionalApplicationContext.getId());
171                    additionalApplicationContext.start();
172                }
173            }
174
175            LOG.debug("Starting Spring ApplicationContext: {}", applicationContext.getId());
176            applicationContext.start();
177
178            initCamelContext();
179        } finally {
180            // if we were veto started then mark as completed
181            if (getCamelContext() != null && getCamelContext().isVetoStarted()) {
182                completed();
183            }
184        }
185    }
186
187    @Override
188    protected void doStop() throws Exception {
189        super.doStop();
190        if (additionalApplicationContext != null) {
191            LOG.debug("Stopping Additional ApplicationContext: {}", additionalApplicationContext.getId());
192            IOHelper.close(additionalApplicationContext);
193        }
194        if (applicationContext != null) {
195            LOG.debug("Stopping Spring ApplicationContext: {}", applicationContext.getId());
196            IOHelper.close(applicationContext);
197        }
198    }
199
200    @Override
201    protected ProducerTemplate findOrCreateCamelTemplate() {
202        String[] names = getApplicationContext().getBeanNamesForType(ProducerTemplate.class);
203        if (names != null && names.length > 0) {
204            return getApplicationContext().getBean(names[0], ProducerTemplate.class);
205        }
206        if (getCamelContext() == null) {
207            throw new IllegalArgumentException("No CamelContext are available so cannot create a ProducerTemplate!");
208        }
209        return getCamelContext().createProducerTemplate();
210    }
211
212    protected AbstractApplicationContext createDefaultApplicationContext() throws IOException {
213        ApplicationContext parentContext = getParentApplicationContext();
214
215        // file based
216        if (getFileApplicationContextUri() != null) {
217            String[] args = getFileApplicationContextUri().split(";");
218
219            if (parentContext != null) {
220                return new FileSystemXmlApplicationContext(args, parentContext);
221            } else {
222                return new FileSystemXmlApplicationContext(args);
223            }
224        }
225
226        // default to classpath based
227        String[] args = getApplicationContextUri().split(";");
228        if (parentContext != null) {
229            return new ClassPathXmlApplicationContext(args, parentContext);
230        } else {
231            // okay no application context specified so lets look for either
232            // classpath xml or annotation based
233            if (routeBuilderClasses != null) {
234                AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
235                ac.register(SpringCamelContext.class);
236                Set<String> packages = new LinkedHashSet<>();
237                String[] classes = routeBuilderClasses.split(",");
238                for (String clazz : classes) {
239                    if (clazz.contains(".")) {
240                        String packageName = clazz.substring(0, clazz.lastIndexOf("."));
241                        packages.add(packageName);
242                    }
243                }
244                LOG.info("Using Spring annotation scanning in packages: {}", packages);
245                ac.scan(packages.toArray(new String[packages.size()]));
246                ac.refresh();
247                return ac;
248            } else {
249                return new ClassPathXmlApplicationContext(args);
250            }
251        }
252    }
253
254    protected AbstractApplicationContext createAdditionalLocationsFromClasspath() throws IOException {
255        Set<String> locations = new LinkedHashSet<>();
256        findLocations(locations, Main.class.getClassLoader());
257
258        if (!locations.isEmpty()) {
259            LOG.info("Found locations for additional Spring XML files: {}", locations);
260
261            String[] locs = locations.toArray(new String[locations.size()]);
262            return new ClassPathXmlApplicationContext(locs);
263        } else {
264            return null;
265        }
266    }
267
268    protected void findLocations(Set<String> locations, ClassLoader classLoader) throws IOException {
269        Enumeration<URL> resources = classLoader.getResources(LOCATION_PROPERTIES);
270        while (resources.hasMoreElements()) {
271            URL url = resources.nextElement();
272            BufferedReader reader = IOHelper.buffered(new InputStreamReader(url.openStream(), UTF8));
273            try {
274                while (true) {
275                    String line = reader.readLine();
276                    if (line == null) {
277                        break;
278                    }
279                    line = line.trim();
280                    if (line.startsWith("#") || line.length() == 0) {
281                        continue;
282                    }
283                    locations.add(line);
284                }
285            } finally {
286                IOHelper.close(reader, null, LOG);
287            }
288        }
289    }
290
291}