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.util.IOHelper;
034import org.springframework.context.ApplicationContext;
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 void doStart() throws Exception {
149        try {
150            super.doStart();
151            if (applicationContext == null) {
152                applicationContext = createDefaultApplicationContext();
153            }
154
155            // then start any additional after Camel has been started
156            if (additionalApplicationContext == null) {
157                additionalApplicationContext = createAdditionalLocationsFromClasspath();
158                if (additionalApplicationContext != null) {
159                    LOG.debug("Starting Additional ApplicationContext: {}", additionalApplicationContext.getId());
160                    additionalApplicationContext.start();
161                }
162            }
163
164            LOG.debug("Starting Spring ApplicationContext: {}", applicationContext.getId());
165            applicationContext.start();
166
167            postProcessContext();
168        } finally {
169            if (camelContexts != null && !camelContexts.isEmpty()) {
170                // if we were veto started then mark as completed
171                if (getCamelContexts().get(0).isVetoStarted()) {
172                    completed();
173                }
174            }
175        }
176    }
177
178    protected void doStop() throws Exception {
179        super.doStop();
180        if (additionalApplicationContext != null) {
181            LOG.debug("Stopping Additional ApplicationContext: {}", additionalApplicationContext.getId());
182            IOHelper.close(additionalApplicationContext);
183        }
184        if (applicationContext != null) {
185            LOG.debug("Stopping Spring ApplicationContext: {}", applicationContext.getId());
186            IOHelper.close(applicationContext);
187        }
188    }
189
190    protected ProducerTemplate findOrCreateCamelTemplate() {
191        String[] names = getApplicationContext().getBeanNamesForType(ProducerTemplate.class);
192        if (names != null && names.length > 0) {
193            return getApplicationContext().getBean(names[0], ProducerTemplate.class);
194        }
195        if (getCamelContexts().isEmpty()) {
196            throw new IllegalArgumentException("No CamelContexts are available so cannot create a ProducerTemplate!");
197        }
198        return getCamelContexts().get(0).createProducerTemplate();
199    }
200
201    protected AbstractApplicationContext createDefaultApplicationContext() throws IOException {
202        ApplicationContext parentContext = getParentApplicationContext();
203
204        // file based
205        if (getFileApplicationContextUri() != null) {
206            String[] args = getFileApplicationContextUri().split(";");
207
208            if (parentContext != null) {
209                return new FileSystemXmlApplicationContext(args, parentContext);
210            } else {
211                return new FileSystemXmlApplicationContext(args);
212            }
213        }
214
215        // default to classpath based
216        String[] args = getApplicationContextUri().split(";");
217        if (parentContext != null) {
218            return new ClassPathXmlApplicationContext(args, parentContext);
219        } else {
220            return new ClassPathXmlApplicationContext(args);
221        }
222    }
223
224    protected Map<String, CamelContext> getCamelContextMap() {
225        Map<String, SpringCamelContext> map = applicationContext.getBeansOfType(SpringCamelContext.class);
226        Set<Map.Entry<String, SpringCamelContext>> entries = map.entrySet();
227        Map<String, CamelContext> answer = new HashMap<>();
228        for (Map.Entry<String, SpringCamelContext> entry : entries) {
229            String name = entry.getKey();
230            CamelContext camelContext = entry.getValue();
231            answer.put(name, camelContext);
232        }
233        return answer;
234    }
235
236    protected AbstractApplicationContext createAdditionalLocationsFromClasspath() throws IOException {
237        Set<String> locations = new LinkedHashSet<>();
238        findLocations(locations, Main.class.getClassLoader());
239
240        if (!locations.isEmpty()) {
241            LOG.info("Found locations for additional Spring XML files: {}", locations);
242
243            String[] locs = locations.toArray(new String[locations.size()]);
244            return new ClassPathXmlApplicationContext(locs);
245        } else {
246            return null;
247        }
248    }
249
250    protected void findLocations(Set<String> locations, ClassLoader classLoader) throws IOException {
251        Enumeration<URL> resources = classLoader.getResources(LOCATION_PROPERTIES);
252        while (resources.hasMoreElements()) {
253            URL url = resources.nextElement();
254            BufferedReader reader = IOHelper.buffered(new InputStreamReader(url.openStream(), UTF8));
255            try {
256                while (true) {
257                    String line = reader.readLine();
258                    if (line == null) {
259                        break;
260                    }
261                    line = line.trim();
262                    if (line.startsWith("#") || line.length() == 0) {
263                        continue;
264                    }
265                    locations.add(line);
266                }
267            } finally {
268                IOHelper.close(reader, null, LOG);
269            }
270        }
271    }
272
273}