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.run(args);
087    }
088
089    /**
090     * Returns the currently executing main
091     *
092     * @return the current running instance
093     */
094    public static Main getInstance() {
095        return instance;
096    }
097
098    // Properties
099    // -------------------------------------------------------------------------
100    public AbstractApplicationContext getApplicationContext() {
101        return applicationContext;
102    }
103
104    public void setApplicationContext(AbstractApplicationContext applicationContext) {
105        this.applicationContext = applicationContext;
106    }
107
108    public String getApplicationContextUri() {
109        return applicationContextUri;
110    }
111
112    public void setApplicationContextUri(String applicationContextUri) {
113        this.applicationContextUri = applicationContextUri;
114    }
115
116    public String getFileApplicationContextUri() {
117        return fileApplicationContextUri;
118    }
119
120    public void setFileApplicationContextUri(String fileApplicationContextUri) {
121        this.fileApplicationContextUri = fileApplicationContextUri;
122    }
123
124    public AbstractApplicationContext getParentApplicationContext() {
125        if (parentApplicationContext == null) {
126            if (parentApplicationContextUri != null) {
127                parentApplicationContext = new ClassPathXmlApplicationContext(parentApplicationContextUri);
128                parentApplicationContext.start();
129            }
130        }
131        return parentApplicationContext;
132    }
133
134    public void setParentApplicationContext(AbstractApplicationContext parentApplicationContext) {
135        this.parentApplicationContext = parentApplicationContext;
136    }
137
138    public String getParentApplicationContextUri() {
139        return parentApplicationContextUri;
140    }
141
142    public void setParentApplicationContextUri(String parentApplicationContextUri) {
143        this.parentApplicationContextUri = parentApplicationContextUri;
144    }
145
146    // Implementation methods
147    // -------------------------------------------------------------------------
148
149    @Override
150    protected void doStart() throws Exception {
151        super.doStart();
152        if (applicationContext == null) {
153            applicationContext = createDefaultApplicationContext();
154        }
155
156        // then start any additional after Camel has been started
157        if (additionalApplicationContext == null) {
158            additionalApplicationContext = createAdditionalLocationsFromClasspath();
159            if (additionalApplicationContext != null) {
160                LOG.debug("Starting Additional ApplicationContext: " + additionalApplicationContext.getId());
161                additionalApplicationContext.start();
162            }
163        }
164
165        LOG.debug("Starting Spring ApplicationContext: " + applicationContext.getId());
166        applicationContext.start();
167
168        postProcessContext();
169    }
170
171    protected void doStop() throws Exception {
172        super.doStop();
173        if (additionalApplicationContext != null) {
174            LOG.debug("Stopping Additional ApplicationContext: " + additionalApplicationContext.getId());
175            IOHelper.close(additionalApplicationContext);
176        }
177        if (applicationContext != null) {
178            LOG.debug("Stopping Spring ApplicationContext: " + applicationContext.getId());
179            IOHelper.close(applicationContext);
180        }
181    }
182
183    protected ProducerTemplate findOrCreateCamelTemplate() {
184        String[] names = getApplicationContext().getBeanNamesForType(ProducerTemplate.class);
185        if (names != null && names.length > 0) {
186            return getApplicationContext().getBean(names[0], ProducerTemplate.class);
187        }
188        if (getCamelContexts().isEmpty()) {
189            throw new IllegalArgumentException("No CamelContexts are available so cannot create a ProducerTemplate!");
190        }
191        return getCamelContexts().get(0).createProducerTemplate();
192    }
193
194    protected AbstractApplicationContext createDefaultApplicationContext() throws IOException {
195        ApplicationContext parentContext = getParentApplicationContext();
196
197        // file based
198        if (getFileApplicationContextUri() != null) {
199            String[] args = getFileApplicationContextUri().split(";");
200
201            if (parentContext != null) {
202                return new FileSystemXmlApplicationContext(args, parentContext);
203            } else {
204                return new FileSystemXmlApplicationContext(args);
205            }
206        }
207
208        // default to classpath based
209        String[] args = getApplicationContextUri().split(";");
210        if (parentContext != null) {
211            return new ClassPathXmlApplicationContext(args, parentContext);
212        } else {
213            return new ClassPathXmlApplicationContext(args);
214        }
215    }
216
217    protected Map<String, CamelContext> getCamelContextMap() {
218        Map<String, SpringCamelContext> map = applicationContext.getBeansOfType(SpringCamelContext.class);
219        Set<Map.Entry<String, SpringCamelContext>> entries = map.entrySet();
220        Map<String, CamelContext> answer = new HashMap<String, CamelContext>();
221        for (Map.Entry<String, SpringCamelContext> entry : entries) {
222            String name = entry.getKey();
223            CamelContext camelContext = entry.getValue();
224            answer.put(name, camelContext);
225        }
226        return answer;
227    }
228
229    protected AbstractApplicationContext createAdditionalLocationsFromClasspath() throws IOException {
230        Set<String> locations = new LinkedHashSet<String>();
231        findLocations(locations, Main.class.getClassLoader());
232
233        if (!locations.isEmpty()) {
234            LOG.info("Found locations for additional Spring XML files: {}", locations);
235
236            String[] locs = locations.toArray(new String[locations.size()]);
237            return new ClassPathXmlApplicationContext(locs);
238        } else {
239            return null;
240        }
241    }
242
243    protected void findLocations(Set<String> locations, ClassLoader classLoader) throws IOException {
244        Enumeration<URL> resources = classLoader.getResources(LOCATION_PROPERTIES);
245        while (resources.hasMoreElements()) {
246            URL url = resources.nextElement();
247            BufferedReader reader = IOHelper.buffered(new InputStreamReader(url.openStream(), UTF8));
248            try {
249                while (true) {
250                    String line = reader.readLine();
251                    if (line == null) {
252                        break;
253                    }
254                    line = line.trim();
255                    if (line.startsWith("#") || line.length() == 0) {
256                        continue;
257                    }
258                    locations.add(line);
259                }
260            } finally {
261                IOHelper.close(reader, null, LOG);
262            }
263        }
264    }
265
266}