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.MainSupport {
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    protected void doStop() throws Exception {
188        super.doStop();
189        if (additionalApplicationContext != null) {
190            LOG.debug("Stopping Additional ApplicationContext: {}", additionalApplicationContext.getId());
191            IOHelper.close(additionalApplicationContext);
192        }
193        if (applicationContext != null) {
194            LOG.debug("Stopping Spring ApplicationContext: {}", applicationContext.getId());
195            IOHelper.close(applicationContext);
196        }
197    }
198
199    protected ProducerTemplate findOrCreateCamelTemplate() {
200        String[] names = getApplicationContext().getBeanNamesForType(ProducerTemplate.class);
201        if (names != null && names.length > 0) {
202            return getApplicationContext().getBean(names[0], ProducerTemplate.class);
203        }
204        if (getCamelContext() == null) {
205            throw new IllegalArgumentException("No CamelContext are available so cannot create a ProducerTemplate!");
206        }
207        return getCamelContext().createProducerTemplate();
208    }
209
210    protected AbstractApplicationContext createDefaultApplicationContext() throws IOException {
211        ApplicationContext parentContext = getParentApplicationContext();
212
213        // file based
214        if (getFileApplicationContextUri() != null) {
215            String[] args = getFileApplicationContextUri().split(";");
216
217            if (parentContext != null) {
218                return new FileSystemXmlApplicationContext(args, parentContext);
219            } else {
220                return new FileSystemXmlApplicationContext(args);
221            }
222        }
223
224        // default to classpath based
225        String[] args = getApplicationContextUri().split(";");
226        if (parentContext != null) {
227            return new ClassPathXmlApplicationContext(args, parentContext);
228        } else {
229            // okay no application context specified so lets look for either
230            // classpath xml or annotation based
231            if (routeBuilderClasses != null) {
232                AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
233                ac.register(SpringCamelContext.class);
234                Set<String> packages = new LinkedHashSet<>();
235                String[] classes = routeBuilderClasses.split(",");
236                for (String clazz : classes) {
237                    if (clazz.contains(".")) {
238                        String packageName = clazz.substring(0, clazz.lastIndexOf("."));
239                        packages.add(packageName);
240                    }
241                }
242                LOG.info("Using Spring annotation scanning in packages: {}", packages);
243                ac.scan(packages.toArray(new String[packages.size()]));
244                ac.refresh();
245                return ac;
246            } else {
247                return new ClassPathXmlApplicationContext(args);
248            }
249        }
250    }
251
252    protected AbstractApplicationContext createAdditionalLocationsFromClasspath() throws IOException {
253        Set<String> locations = new LinkedHashSet<>();
254        findLocations(locations, Main.class.getClassLoader());
255
256        if (!locations.isEmpty()) {
257            LOG.info("Found locations for additional Spring XML files: {}", locations);
258
259            String[] locs = locations.toArray(new String[locations.size()]);
260            return new ClassPathXmlApplicationContext(locs);
261        } else {
262            return null;
263        }
264    }
265
266    protected void findLocations(Set<String> locations, ClassLoader classLoader) throws IOException {
267        Enumeration<URL> resources = classLoader.getResources(LOCATION_PROPERTIES);
268        while (resources.hasMoreElements()) {
269            URL url = resources.nextElement();
270            BufferedReader reader = IOHelper.buffered(new InputStreamReader(url.openStream(), UTF8));
271            try {
272                while (true) {
273                    String line = reader.readLine();
274                    if (line == null) {
275                        break;
276                    }
277                    line = line.trim();
278                    if (line.startsWith("#") || line.length() == 0) {
279                        continue;
280                    }
281                    locations.add(line);
282                }
283            } finally {
284                IOHelper.close(reader, null, LOG);
285            }
286        }
287    }
288
289}