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.HashMap;
026import java.util.LinkedHashSet;
027import java.util.LinkedList;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.camel.CamelContext;
032import org.apache.camel.ProducerTemplate;
033import org.apache.camel.impl.MainSupport;
034import org.apache.camel.util.IOHelper;
035import org.springframework.context.ApplicationContext;
036import org.springframework.context.support.AbstractApplicationContext;
037import org.springframework.context.support.ClassPathXmlApplicationContext;
038import org.springframework.context.support.FileSystemXmlApplicationContext;
039
040/**
041 * A command line tool for booting up a CamelContext using an optional Spring
042 * {@link org.springframework.context.ApplicationContext}.
043 * <p/>
044 * By placing a file in the {@link #LOCATION_PROPERTIES} directory of any JARs on the classpath,
045 * allows this Main class to load those additional Spring XML files as Spring
046 * {@link org.springframework.context.ApplicationContext} to be included.
047 * <p/>
048 * Each line in the {@link #LOCATION_PROPERTIES} is a reference to a Spring XML file to include,
049 * which by default gets loaded from classpath.
050 */
051@SuppressWarnings("deprecation")
052public class Main extends MainSupport {
053
054    public static final String LOCATION_PROPERTIES = "META-INF/camel-spring/location.properties";
055    protected static Main instance;
056    private static final Charset UTF8 = Charset.forName("UTF-8");
057
058    private String applicationContextUri = "META-INF/spring/*.xml";
059    private String fileApplicationContextUri;
060    private AbstractApplicationContext applicationContext;
061    private AbstractApplicationContext parentApplicationContext;
062    private AbstractApplicationContext additionalApplicationContext;
063    private String parentApplicationContextUri;
064
065    public Main() {
066
067        addOption(new ParameterOption("ac", "applicationContext",
068                "Sets the classpath based spring ApplicationContext", "applicationContext") {
069            protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
070                setApplicationContextUri(parameter);
071            }
072        });
073
074        addOption(new ParameterOption("fa", "fileApplicationContext",
075                "Sets the filesystem based spring ApplicationContext", "fileApplicationContext") {
076            protected void doProcess(String arg, String parameter, LinkedList<String> remainingArgs) {
077                setFileApplicationContextUri(parameter);
078            }
079        });
080
081    }
082
083    public static void main(String... args) throws Exception {
084        Main main = new Main();
085        instance = main;
086        main.enableHangupSupport();
087        main.run(args);
088    }
089
090    /**
091     * Returns the currently executing main
092     *
093     * @return the current running instance
094     */
095    public static Main getInstance() {
096        return instance;
097    }
098
099    // Properties
100    // -------------------------------------------------------------------------
101    public AbstractApplicationContext getApplicationContext() {
102        return applicationContext;
103    }
104
105    public void setApplicationContext(AbstractApplicationContext applicationContext) {
106        this.applicationContext = applicationContext;
107    }
108
109    public String getApplicationContextUri() {
110        return applicationContextUri;
111    }
112
113    public void setApplicationContextUri(String applicationContextUri) {
114        this.applicationContextUri = applicationContextUri;
115    }
116
117    public String getFileApplicationContextUri() {
118        return fileApplicationContextUri;
119    }
120
121    public void setFileApplicationContextUri(String fileApplicationContextUri) {
122        this.fileApplicationContextUri = fileApplicationContextUri;
123    }
124
125    public AbstractApplicationContext getParentApplicationContext() {
126        if (parentApplicationContext == null) {
127            if (parentApplicationContextUri != null) {
128                parentApplicationContext = new ClassPathXmlApplicationContext(parentApplicationContextUri);
129                parentApplicationContext.start();
130            }
131        }
132        return parentApplicationContext;
133    }
134
135    public void setParentApplicationContext(AbstractApplicationContext parentApplicationContext) {
136        this.parentApplicationContext = parentApplicationContext;
137    }
138
139    public String getParentApplicationContextUri() {
140        return parentApplicationContextUri;
141    }
142
143    public void setParentApplicationContextUri(String parentApplicationContextUri) {
144        this.parentApplicationContextUri = parentApplicationContextUri;
145    }
146
147    // Implementation methods
148    // -------------------------------------------------------------------------
149
150    @Override
151    protected void doStart() throws Exception {
152        super.doStart();
153        if (applicationContext == null) {
154            applicationContext = createDefaultApplicationContext();
155        }
156
157        // then start any additional after Camel has been started
158        if (additionalApplicationContext == null) {
159            additionalApplicationContext = createAdditionalLocationsFromClasspath();
160            if (additionalApplicationContext != null) {
161                LOG.debug("Starting Additional ApplicationContext: " + additionalApplicationContext.getId());
162                additionalApplicationContext.start();
163            }
164        }
165
166        LOG.debug("Starting Spring ApplicationContext: " + applicationContext.getId());
167        applicationContext.start();
168
169        postProcessContext();
170    }
171
172    protected void doStop() throws Exception {
173        super.doStop();
174        if (additionalApplicationContext != null) {
175            LOG.debug("Stopping Additional ApplicationContext: " + additionalApplicationContext.getId());
176            IOHelper.close(additionalApplicationContext);
177        }
178        if (applicationContext != null) {
179            LOG.debug("Stopping Spring ApplicationContext: " + applicationContext.getId());
180            IOHelper.close(applicationContext);
181        }
182    }
183
184    protected ProducerTemplate findOrCreateCamelTemplate() {
185        String[] names = getApplicationContext().getBeanNamesForType(ProducerTemplate.class);
186        if (names != null && names.length > 0) {
187            return getApplicationContext().getBean(names[0], ProducerTemplate.class);
188        }
189        if (getCamelContexts().isEmpty()) {
190            throw new IllegalArgumentException("No CamelContexts are available so cannot create a ProducerTemplate!");
191        }
192        return getCamelContexts().get(0).createProducerTemplate();
193    }
194
195    protected AbstractApplicationContext createDefaultApplicationContext() throws IOException {
196        ApplicationContext parentContext = getParentApplicationContext();
197
198        // file based
199        if (getFileApplicationContextUri() != null) {
200            String[] args = getFileApplicationContextUri().split(";");
201
202            if (parentContext != null) {
203                return new FileSystemXmlApplicationContext(args, parentContext);
204            } else {
205                return new FileSystemXmlApplicationContext(args);
206            }
207        }
208
209        // default to classpath based
210        String[] args = getApplicationContextUri().split(";");
211        if (parentContext != null) {
212            return new ClassPathXmlApplicationContext(args, parentContext);
213        } else {
214            return new ClassPathXmlApplicationContext(args);
215        }
216    }
217
218    protected Map<String, CamelContext> getCamelContextMap() {
219        Map<String, SpringCamelContext> map = applicationContext.getBeansOfType(SpringCamelContext.class);
220        Set<Map.Entry<String, SpringCamelContext>> entries = map.entrySet();
221        Map<String, CamelContext> answer = new HashMap<String, CamelContext>();
222        for (Map.Entry<String, SpringCamelContext> entry : entries) {
223            String name = entry.getKey();
224            CamelContext camelContext = entry.getValue();
225            answer.put(name, camelContext);
226        }
227        return answer;
228    }
229
230    protected AbstractApplicationContext createAdditionalLocationsFromClasspath() throws IOException {
231        Set<String> locations = new LinkedHashSet<String>();
232        findLocations(locations, Main.class.getClassLoader());
233
234        if (!locations.isEmpty()) {
235            LOG.info("Found locations for additional Spring XML files: {}", locations);
236
237            String[] locs = locations.toArray(new String[locations.size()]);
238            return new ClassPathXmlApplicationContext(locs);
239        } else {
240            return null;
241        }
242    }
243
244    protected void findLocations(Set<String> locations, ClassLoader classLoader) throws IOException {
245        Enumeration<URL> resources = classLoader.getResources(LOCATION_PROPERTIES);
246        while (resources.hasMoreElements()) {
247            URL url = resources.nextElement();
248            BufferedReader reader = IOHelper.buffered(new InputStreamReader(url.openStream(), UTF8));
249            try {
250                while (true) {
251                    String line = reader.readLine();
252                    if (line == null) {
253                        break;
254                    }
255                    line = line.trim();
256                    if (line.startsWith("#") || line.length() == 0) {
257                        continue;
258                    }
259                    locations.add(line);
260                }
261            } finally {
262                IOHelper.close(reader, null, LOG);
263            }
264        }
265    }
266
267}