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