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