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     */
017    package org.apache.camel.component.cometd;
018    
019    import java.net.URL;
020    import java.util.ArrayList;
021    import java.util.LinkedHashMap;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.camel.Endpoint;
026    import org.apache.camel.impl.DefaultComponent;
027    import org.cometd.bayeux.server.BayeuxServer;
028    import org.cometd.bayeux.server.SecurityPolicy;
029    import org.cometd.server.BayeuxServerImpl;
030    import org.cometd.server.CometdServlet;
031    import org.eclipse.jetty.server.Connector;
032    import org.eclipse.jetty.server.Server;
033    import org.eclipse.jetty.server.handler.ContextHandlerCollection;
034    import org.eclipse.jetty.server.nio.SelectChannelConnector;
035    import org.eclipse.jetty.server.session.HashSessionManager;
036    import org.eclipse.jetty.server.session.SessionHandler;
037    import org.eclipse.jetty.server.ssl.SslSocketConnector;
038    import org.eclipse.jetty.servlet.ServletContextHandler;
039    import org.eclipse.jetty.servlet.ServletHolder;
040    import org.eclipse.jetty.util.resource.Resource;
041    import org.slf4j.Logger;
042    import org.slf4j.LoggerFactory;
043    
044    
045    /**
046     * Component for Jetty Cometd
047     */
048    public class CometdComponent extends DefaultComponent {
049        private static final transient Logger LOG = LoggerFactory.getLogger(CometdComponent.class);
050    
051        private final Map<String, ConnectorRef> connectors = new LinkedHashMap<String, ConnectorRef>();
052       
053        private String sslKeyPassword;
054        private String sslPassword;
055        private String sslKeystore;
056        private SslSocketConnector sslSocketConnector;
057        private SecurityPolicy securityPolicy;
058        private List<BayeuxServer.Extension> extensions;
059    
060        class ConnectorRef {
061            Connector connector;
062            CometdServlet servlet;
063            Server server;
064            int refCount;
065    
066            public ConnectorRef(Connector connector, CometdServlet servlet, Server server) {
067                this.connector = connector;
068                this.servlet = servlet;
069                this.server = server;
070                increment();
071            }
072    
073            public int increment() {
074                return ++refCount;
075            }
076    
077            public int decrement() {
078                return --refCount;
079            }
080        }
081    
082        public CometdComponent() {
083        }
084    
085        @Override
086        protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
087            setProperties(this, parameters);
088            return new CometdEndpoint(this, uri, remaining, parameters);
089        }
090    
091        /**
092         * Connects the URL specified on the endpoint to the specified processor.
093         */
094        public void connect(CometdProducerConsumer prodcon) throws Exception {
095            // Make sure that there is a connector for the requested endpoint.
096            CometdEndpoint endpoint = (CometdEndpoint) prodcon.getEndpoint();
097            String connectorKey = endpoint.getProtocol() + ":" + endpoint.getUri().getHost() + ":" + endpoint.getPort();
098    
099            synchronized (connectors) {
100                ConnectorRef connectorRef = connectors.get(connectorKey);
101                if (connectorRef == null) {
102                    Connector connector;
103                    if ("cometds".equals(endpoint.getProtocol())) {
104                        connector = getSslSocketConnector();
105                    } else {
106                        connector = new SelectChannelConnector();
107                    }
108                    connector.setPort(endpoint.getPort());
109                    connector.setHost(endpoint.getUri().getHost());
110                    if ("localhost".equalsIgnoreCase(endpoint.getUri().getHost())) {
111                        LOG.warn("You use localhost interface! It means that no external connections will be available."
112                                + " Don't you want to use 0.0.0.0 instead (all network interfaces)?");
113                    }
114                    Server server = createServer();
115                    server.addConnector(connector);
116    
117                    CometdServlet servlet = createServletForConnector(server, connector, endpoint);
118                    connectorRef = new ConnectorRef(connector, servlet, server);
119                    server.start();
120    
121                    connectors.put(connectorKey, connectorRef);
122                } else {
123                    connectorRef.increment();
124                }
125    
126                BayeuxServerImpl bayeux = connectorRef.servlet.getBayeux();
127    
128                if (securityPolicy != null) {
129                    bayeux.setSecurityPolicy(securityPolicy);
130                }
131                if (extensions != null) {
132                    for (BayeuxServer.Extension extension : extensions) {
133                        bayeux.addExtension(extension);
134                    }
135                }
136                prodcon.setBayeux(bayeux);
137            }
138        }
139    
140        /**
141         * Disconnects the URL specified on the endpoint from the specified
142         * processor.
143         */
144        public void disconnect(CometdProducerConsumer prodcon) throws Exception {
145            CometdEndpoint endpoint = prodcon.getEndpoint();
146    
147            String connectorKey = endpoint.getProtocol() + ":" + endpoint.getUri().getHost() + ":" + endpoint.getPort();
148    
149            synchronized (connectors) {
150                ConnectorRef connectorRef = connectors.get(connectorKey);
151                if (connectorRef != null) {
152                    if (connectorRef.decrement() == 0) {
153                        connectorRef.server.removeConnector(connectorRef.connector);
154                        connectorRef.connector.stop();
155                        connectorRef.server.stop();
156                        connectors.remove(connectorKey);
157                    }
158                }
159            }
160        }
161    
162        protected CometdServlet createServletForConnector(Server server, Connector connector, CometdEndpoint endpoint) throws Exception {
163            CometdServlet servlet = new CometdServlet();
164    
165            ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
166            context.setConnectorNames(new String[]{connector.getName()});
167    
168            ServletHolder holder = new ServletHolder();
169            holder.setServlet(servlet);
170            holder.setAsyncSupported(true);
171    
172            // Use baseResource to pass as a parameter the url
173            // pointing to by example classpath:webapp
174            if (endpoint.getBaseResource() != null) {
175                String[] resources = endpoint.getBaseResource().split(":");
176                if (LOG.isDebugEnabled()) {
177                    LOG.debug(">>> Protocol found: " + resources[0] + ", and resource: " + resources[1]);
178                }
179    
180                if (resources[0].equals("file")) {
181                    context.setBaseResource(Resource.newResource(resources[1]));
182                } else if (resources[0].equals("classpath")) {
183                    // Create a URL handler using classpath protocol
184                    URL url = this.getCamelContext().getClassResolver().loadResourceAsURL(resources[1]);
185                    context.setBaseResource(Resource.newResource(url));
186                }
187            }
188    
189            context.addServlet(holder, "/cometd/*");
190            context.addServlet("org.eclipse.jetty.servlet.DefaultServlet", "/");
191            context.setSessionHandler(new SessionHandler(new HashSessionManager()));
192    
193            holder.setInitParameter("timeout", Integer.toString(endpoint.getTimeout()));
194            holder.setInitParameter("interval", Integer.toString(endpoint.getInterval()));
195            holder.setInitParameter("maxInterval", Integer.toString(endpoint.getMaxInterval()));
196            holder.setInitParameter("multiFrameInterval", Integer.toString(endpoint.getMultiFrameInterval()));
197            holder.setInitParameter("JSONCommented", Boolean.toString(endpoint.isJsonCommented()));
198            holder.setInitParameter("logLevel", Integer.toString(endpoint.getLogLevel()));
199    
200            return servlet;
201        }
202    
203        public synchronized SslSocketConnector getSslSocketConnector() {
204            if (sslSocketConnector == null) {
205                sslSocketConnector = new SslSocketConnector();
206                // with default null values, jetty ssl system properties
207                // and console will be read by jetty implementation
208                sslSocketConnector.getSslContextFactory().setKeyManagerPassword(sslPassword);
209                sslSocketConnector.getSslContextFactory().setKeyStorePassword(sslKeyPassword);
210                if (sslKeystore != null) {
211                    sslSocketConnector.getSslContextFactory().setKeyStore(sslKeystore);
212                }
213            }
214            return sslSocketConnector;
215        }
216    
217        public String getSslKeyPassword() {
218            return sslKeyPassword;
219        }
220    
221        public String getSslPassword() {
222            return sslPassword;
223        }
224    
225        public String getSslKeystore() {
226            return sslKeystore;
227        }
228    
229        public void setSslKeyPassword(String sslKeyPassword) {
230            this.sslKeyPassword = sslKeyPassword;
231        }
232    
233        public void setSslPassword(String sslPassword) {
234            this.sslPassword = sslPassword;
235        }
236    
237        public void setSslKeystore(String sslKeystore) {
238            this.sslKeystore = sslKeystore;
239        }
240    
241        public void setSecurityPolicy(SecurityPolicy securityPolicy) {
242            this.securityPolicy = securityPolicy;
243        }
244    
245        public SecurityPolicy getSecurityPolicy() {
246            return securityPolicy;
247        }
248    
249        public List<BayeuxServer.Extension> getExtensions() {
250            return extensions;
251        }
252    
253        public void setExtensions(List<BayeuxServer.Extension> extensions) {
254            this.extensions = extensions;
255        }
256    
257        public void addExtension(BayeuxServer.Extension extension) {
258            if (extensions == null) {
259                extensions = new ArrayList<BayeuxServer.Extension>();
260            }
261            extensions.add(extension);
262        }
263    
264        protected Server createServer() throws Exception {
265            Server server = new Server();
266            ContextHandlerCollection collection = new ContextHandlerCollection();
267            server.setHandler(collection);
268            return server;
269        }
270    
271        @Override
272        protected void doStop() throws Exception {
273            for (ConnectorRef connectorRef : connectors.values()) {
274                connectorRef.connector.stop();
275            }
276            connectors.clear();
277           
278            super.doStop();
279        }
280    
281        @Override
282        protected void doStart() throws Exception {
283            super.doStart();
284        }
285    }