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     * @deprecated - use JmsComponent#setUsername(String)
108     * @see JmsComponent#setUsername(String)
109     */
110    public void setUserName(String userName) {
111        setUsername(userName);
112    }
113
114    public void setTrustAllPackages(boolean trustAllPackages) {
115        if (getConfiguration() instanceof ActiveMQConfiguration) {
116            ((ActiveMQConfiguration)getConfiguration()).setTrustAllPackages(trustAllPackages);
117        }
118    }
119
120    public boolean isExposeAllQueues() {
121        return exposeAllQueues;
122    }
123
124    /**
125     * If enabled this will cause all Queues in the ActiveMQ broker to be eagerly populated into the CamelContext
126     * so that they can be easily browsed by any Camel tooling. This option is disabled by default.
127     */
128    public void setExposeAllQueues(boolean exposeAllQueues) {
129        this.exposeAllQueues = exposeAllQueues;
130    }
131
132    /**
133     * Enables or disables whether a PooledConnectionFactory will be used so that when
134     * messages are sent to ActiveMQ from outside of a message consuming thread, pooling will be used rather
135     * than the default with the Spring {@link JmsTemplate} which will create a new connection, session, producer
136     * for each message then close them all down again.
137     * <p/>
138     * The default value is true. Note that this requires an extra dependency on commons-pool2.
139     */
140    public void setUsePooledConnection(boolean usePooledConnection) {
141        if (getConfiguration() instanceof ActiveMQConfiguration) {
142            ((ActiveMQConfiguration)getConfiguration()).setUsePooledConnection(usePooledConnection);
143        }
144    }
145
146    /**
147     * Enables or disables whether a Spring {@link SingleConnectionFactory} will be used so that when
148     * messages are sent to ActiveMQ from outside of a message consuming thread, pooling will be used rather
149     * than the default with the Spring {@link JmsTemplate} which will create a new connection, session, producer
150     * for each message then close them all down again.
151     * <p/>
152     * The default value is false and a pooled connection is used by default.
153     */
154    public void setUseSingleConnection(boolean useSingleConnection) {
155        if (getConfiguration() instanceof ActiveMQConfiguration) {
156            ((ActiveMQConfiguration)getConfiguration()).setUseSingleConnection(useSingleConnection);
157        }
158    }
159
160    protected void addPooledConnectionFactoryService(Service pooledConnectionFactoryService) {
161        pooledConnectionFactoryServiceList.add(pooledConnectionFactoryService);
162    }
163
164    protected void addSingleConnectionFactory(SingleConnectionFactory singleConnectionFactory) {
165        singleConnectionFactoryList.add(singleConnectionFactory);
166    }
167
168    @Override
169    @SuppressWarnings("unchecked")
170    protected String convertPathToActualDestination(String path, Map<String, Object> parameters) {
171        // support ActiveMQ destination options using the destination. prefix
172        // http://activemq.apache.org/destination-options.html
173        Map options = IntrospectionSupport.extractProperties(parameters, "destination.");
174
175        String query;
176        try {
177            query = URISupport.createQueryString(options);
178        } catch (URISyntaxException e) {
179            throw ObjectHelper.wrapRuntimeCamelException(e);
180        }
181
182        // if we have destination options then append them to the destination name
183        if (ObjectHelper.isNotEmpty(query)) {
184            return path + "?" + query;
185        } else {
186            return path;
187        }
188    }
189
190    @Override
191    protected void doStart() throws Exception {
192        super.doStart();
193
194        if (isExposeAllQueues()) {
195            createDestinationSource();
196            endpointLoader = new CamelEndpointLoader(getCamelContext(), source);
197            endpointLoader.afterPropertiesSet();
198        }
199
200        // use OriginalDestinationPropagateStrategy by default if no custom stategy has been set
201        if (getMessageCreatedStrategy() == null) {
202            setMessageCreatedStrategy(new OriginalDestinationPropagateStrategy());
203        }
204    }
205
206    protected void createDestinationSource() {
207        try {
208            if (source == null) {
209                if (connection == null) {
210                    Connection value = getConfiguration().getConnectionFactory().createConnection();
211                    if (value instanceof EnhancedConnection) {
212                        connection = (EnhancedConnection) value;
213                    } else {
214                        throw new IllegalArgumentException("Created JMS Connection is not an EnhancedConnection: " + value);
215                    }
216                    connection.start();
217                }
218                source = connection.getDestinationSource();
219            }
220        } catch (Throwable t) {
221            LOG.info("Can't get destination source, endpoint completer will not work", t);
222        }
223    }
224
225    @Override
226    protected void doStop() throws Exception {
227        if (source != null) {
228            source.stop();
229            source = null;
230        }
231        if (connection != null) {
232            connection.close();
233            connection = null;
234        }
235        for (Service s : pooledConnectionFactoryServiceList) {
236            s.stop();
237        }
238        pooledConnectionFactoryServiceList.clear();
239        for (SingleConnectionFactory s : singleConnectionFactoryList) {
240            s.destroy();
241        }
242        singleConnectionFactoryList.clear();
243        super.doStop();
244    }
245
246    @Override
247    public void setConfiguration(JmsConfiguration configuration) {
248        if (configuration instanceof ActiveMQConfiguration) {
249            ((ActiveMQConfiguration) configuration).setActiveMQComponent(this);
250        }
251        super.setConfiguration(configuration);
252    }
253
254    @Override
255    protected JmsConfiguration createConfiguration() {
256        ActiveMQConfiguration answer = new ActiveMQConfiguration();
257        answer.setActiveMQComponent(this);
258        return answer;
259    }
260
261    @Override
262    public List<String> completeEndpointPath(ComponentConfiguration componentConfiguration, String completionText) {
263        // try to initialize destination source only the first time
264        if (!sourceInitialized) {
265            createDestinationSource();
266            sourceInitialized = true;
267        }
268        ArrayList<String> answer = new ArrayList<String>();
269        if (source != null) {
270            Set candidates = source.getQueues();
271            String destinationName = completionText;
272            if (completionText.startsWith("topic:")) {
273                candidates = source.getTopics();
274                destinationName = completionText.substring(6);
275            } else if (completionText.startsWith("queue:")) {
276                destinationName = completionText.substring(6);
277            }
278
279            Iterator it = candidates.iterator();
280
281            while (it.hasNext()) {
282                ActiveMQDestination destination = (ActiveMQDestination) it.next();
283                if (destination.getPhysicalName().startsWith(destinationName)) {
284                    answer.add(destination.getPhysicalName());
285                }
286            }
287        }
288        return answer;
289    }
290
291}