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.activemq.camel.component;
018
019import java.net.URISyntaxException;
020import java.util.*;
021import java.util.concurrent.CopyOnWriteArrayList;
022
023import org.apache.activemq.EnhancedConnection;
024import org.apache.activemq.Service;
025import org.apache.activemq.advisory.DestinationSource;
026import org.apache.activemq.command.ActiveMQDestination;
027import org.apache.camel.CamelContext;
028import org.apache.camel.ComponentConfiguration;
029import org.apache.camel.component.jms.JmsComponent;
030import org.apache.camel.component.jms.JmsConfiguration;
031import org.apache.camel.spi.EndpointCompleter;
032import org.apache.camel.util.IntrospectionSupport;
033import org.apache.camel.util.ObjectHelper;
034import org.apache.camel.util.URISupport;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037import org.springframework.jms.connection.SingleConnectionFactory;
038import org.springframework.jms.core.JmsTemplate;
039
040import javax.jms.Connection;
041
042/**
043 * The <a href="http://activemq.apache.org/camel/activemq.html">ActiveMQ Component</a>
044 */
045public class ActiveMQComponent extends JmsComponent implements EndpointCompleter {
046    private final CopyOnWriteArrayList<SingleConnectionFactory> singleConnectionFactoryList =
047        new CopyOnWriteArrayList<SingleConnectionFactory>();
048    private final CopyOnWriteArrayList<Service> pooledConnectionFactoryServiceList =
049        new CopyOnWriteArrayList<Service>();
050    private static final transient Logger LOG = LoggerFactory.getLogger(ActiveMQComponent.class);
051    private boolean exposeAllQueues;
052    private CamelEndpointLoader endpointLoader;
053
054    private EnhancedConnection connection;
055    DestinationSource source;
056    boolean sourceInitialized = false;
057
058    /**
059     * Creates an <a href="http://camel.apache.org/activemq.html">ActiveMQ Component</a>
060     *
061     * @return the created component
062     */
063    public static ActiveMQComponent activeMQComponent() {
064        return new ActiveMQComponent();
065    }
066
067    /**
068     * Creates an <a href="http://camel.apache.org/activemq.html">ActiveMQ Component</a>
069     * connecting to the given <a href="http://activemq.apache.org/configuring-transports.html">broker URL</a>
070     *
071     * @param brokerURL the URL to connect to
072     * @return the created component
073     */
074    public static ActiveMQComponent activeMQComponent(String brokerURL) {
075        ActiveMQComponent answer = new ActiveMQComponent();
076        if (answer.getConfiguration() instanceof ActiveMQConfiguration) {
077            ((ActiveMQConfiguration) answer.getConfiguration())
078                    .setBrokerURL(brokerURL);
079        }
080
081        return answer;
082    }
083
084    public ActiveMQComponent() {
085    }
086
087    public ActiveMQComponent(CamelContext context) {
088        super(context);
089    }
090
091    public ActiveMQComponent(ActiveMQConfiguration configuration) {
092        super();
093        setConfiguration(configuration);
094    }
095
096    /**
097     * Sets the broker URL to use to connect to ActiveMQ using the
098     * <a href="http://activemq.apache.org/configuring-transports.html">ActiveMQ URI format</a>
099     */
100    public void setBrokerURL(String brokerURL) {
101        if (getConfiguration() instanceof ActiveMQConfiguration) {
102            ((ActiveMQConfiguration)getConfiguration()).setBrokerURL(brokerURL);
103        }
104    }
105
106    /**
107     * Sets the username to be used to login to ActiveMQ
108     */
109    public void setUserName(String userName) {
110        if (getConfiguration() instanceof ActiveMQConfiguration) {
111            ((ActiveMQConfiguration)getConfiguration()).setUserName(userName);
112        }
113    }
114
115    /**
116     * Sets the password/passcode used to login to ActiveMQ
117     */
118    public void setPassword(String password) {
119        if (getConfiguration() instanceof ActiveMQConfiguration) {
120            ((ActiveMQConfiguration)getConfiguration()).setPassword(password);
121        }
122    }
123
124    public void setTrustAllPackages(boolean trustAllPackages) {
125        if (getConfiguration() instanceof ActiveMQConfiguration) {
126            ((ActiveMQConfiguration)getConfiguration()).setTrustAllPackages(trustAllPackages);
127        }
128    }
129
130    public boolean isExposeAllQueues() {
131        return exposeAllQueues;
132    }
133
134    /**
135     * If enabled this will cause all Queues in the ActiveMQ broker to be eagerly populated into the CamelContext
136     * so that they can be easily browsed by any Camel tooling. This option is disabled by default.
137     */
138    public void setExposeAllQueues(boolean exposeAllQueues) {
139        this.exposeAllQueues = exposeAllQueues;
140    }
141
142    /**
143     * Enables or disables whether a PooledConnectionFactory will be used so that when
144     * messages are sent to ActiveMQ from outside of a message consuming thread, pooling will be used rather
145     * than the default with the Spring {@link JmsTemplate} which will create a new connection, session, producer
146     * for each message then close them all down again.
147     * <p/>
148     * The default value is true. Note that this requires an extra dependency on commons-pool2.
149     */
150    public void setUsePooledConnection(boolean usePooledConnection) {
151        if (getConfiguration() instanceof ActiveMQConfiguration) {
152            ((ActiveMQConfiguration)getConfiguration()).setUsePooledConnection(usePooledConnection);
153        }
154    }
155
156    /**
157     * Enables or disables whether a Spring {@link SingleConnectionFactory} will be used so that when
158     * messages are sent to ActiveMQ from outside of a message consuming thread, pooling will be used rather
159     * than the default with the Spring {@link JmsTemplate} which will create a new connection, session, producer
160     * for each message then close them all down again.
161     * <p/>
162     * The default value is false and a pooled connection is used by default.
163     */
164    public void setUseSingleConnection(boolean useSingleConnection) {
165        if (getConfiguration() instanceof ActiveMQConfiguration) {
166            ((ActiveMQConfiguration)getConfiguration()).setUseSingleConnection(useSingleConnection);
167        }
168    }
169
170    protected void addPooledConnectionFactoryService(Service pooledConnectionFactoryService) {
171        pooledConnectionFactoryServiceList.add(pooledConnectionFactoryService);
172    }
173
174    protected void addSingleConnectionFactory(SingleConnectionFactory singleConnectionFactory) {
175        singleConnectionFactoryList.add(singleConnectionFactory);
176    }
177
178    @Override
179    @SuppressWarnings("unchecked")
180    protected String convertPathToActualDestination(String path, Map<String, Object> parameters) {
181        // support ActiveMQ destination options using the destination. prefix
182        // http://activemq.apache.org/destination-options.html
183        Map options = IntrospectionSupport.extractProperties(parameters, "destination.");
184
185        String query;
186        try {
187            query = URISupport.createQueryString(options);
188        } catch (URISyntaxException e) {
189            throw ObjectHelper.wrapRuntimeCamelException(e);
190        }
191
192        // if we have destination options then append them to the destination name
193        if (ObjectHelper.isNotEmpty(query)) {
194            return path + "?" + query;
195        } else {
196            return path;
197        }
198    }
199
200    @Override
201    protected void doStart() throws Exception {
202        super.doStart();
203
204        if (isExposeAllQueues()) {
205            createDestinationSource();
206            endpointLoader = new CamelEndpointLoader(getCamelContext(), source);
207            endpointLoader.afterPropertiesSet();
208        }
209
210        // use OriginalDestinationPropagateStrategy by default if no custom stategy has been set
211        if (getMessageCreatedStrategy() == null) {
212            setMessageCreatedStrategy(new OriginalDestinationPropagateStrategy());
213        }
214    }
215
216    protected void createDestinationSource() {
217        try {
218            if (source == null) {
219                if (connection == null) {
220                    Connection value = getConfiguration().getConnectionFactory().createConnection();
221                    if (value instanceof EnhancedConnection) {
222                        connection = (EnhancedConnection) value;
223                    } else {
224                        throw new IllegalArgumentException("Created JMS Connection is not an EnhancedConnection: " + value);
225                    }
226                    connection.start();
227                }
228                source = connection.getDestinationSource();
229            }
230        } catch (Throwable t) {
231            LOG.info("Can't get destination source, endpoint completer will not work", t);
232        }
233    }
234
235    @Override
236    protected void doStop() throws Exception {
237        if (source != null) {
238            source.stop();
239            source = null;
240        }
241        if (connection != null) {
242            connection.close();
243            connection = null;
244        }
245        for (Service s : pooledConnectionFactoryServiceList) {
246            s.stop();
247        }
248        pooledConnectionFactoryServiceList.clear();
249        for (SingleConnectionFactory s : singleConnectionFactoryList) {
250            s.destroy();
251        }
252        singleConnectionFactoryList.clear();
253        super.doStop();
254    }
255
256    @Override
257    public void setConfiguration(JmsConfiguration configuration) {
258        if (configuration instanceof ActiveMQConfiguration) {
259            ((ActiveMQConfiguration) configuration).setActiveMQComponent(this);
260        }
261        super.setConfiguration(configuration);
262    }
263
264    @Override
265    protected JmsConfiguration createConfiguration() {
266        ActiveMQConfiguration answer = new ActiveMQConfiguration();
267        answer.setActiveMQComponent(this);
268        return answer;
269    }
270
271    @Override
272    public List<String> completeEndpointPath(ComponentConfiguration componentConfiguration, String completionText) {
273        // try to initialize destination source only the first time
274        if (!sourceInitialized) {
275            createDestinationSource();
276            sourceInitialized = true;
277        }
278        ArrayList<String> answer = new ArrayList<String>();
279        if (source != null) {
280            Set candidates = source.getQueues();
281            String destinationName = completionText;
282            if (completionText.startsWith("topic:")) {
283                candidates = source.getTopics();
284                destinationName = completionText.substring(6);
285            } else if (completionText.startsWith("queue:")) {
286                destinationName = completionText.substring(6);
287            }
288
289            Iterator it = candidates.iterator();
290
291            while (it.hasNext()) {
292                ActiveMQDestination destination = (ActiveMQDestination) it.next();
293                if (destination.getPhysicalName().startsWith(destinationName)) {
294                    answer.add(destination.getPhysicalName());
295                }
296            }
297        }
298        return answer;
299    }
300
301    /**
302     * We don't want to ever auto-wire the connection factory from the spring app context (requires Camel 2.18 onwards)
303     *
304     * @return false
305     */
306    public boolean isAllowAutoWiredConnectionFactory() {
307        return false;
308    }
309
310}