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.smpp;
018    
019    import java.io.IOException;
020    import java.util.concurrent.locks.ReentrantLock;
021    
022    import org.apache.camel.Exchange;
023    import org.apache.camel.Processor;
024    import org.apache.camel.impl.DefaultConsumer;
025    import org.jsmpp.DefaultPDUReader;
026    import org.jsmpp.DefaultPDUSender;
027    import org.jsmpp.SynchronizedPDUSender;
028    import org.jsmpp.bean.AlertNotification;
029    import org.jsmpp.bean.BindType;
030    import org.jsmpp.bean.DataSm;
031    import org.jsmpp.bean.DeliverSm;
032    import org.jsmpp.bean.NumberingPlanIndicator;
033    import org.jsmpp.bean.TypeOfNumber;
034    import org.jsmpp.extra.ProcessRequestException;
035    import org.jsmpp.extra.SessionState;
036    import org.jsmpp.session.BindParameter;
037    import org.jsmpp.session.DataSmResult;
038    import org.jsmpp.session.MessageReceiverListener;
039    import org.jsmpp.session.SMPPSession;
040    import org.jsmpp.session.Session;
041    import org.jsmpp.session.SessionStateListener;
042    import org.jsmpp.util.DefaultComposer;
043    import org.jsmpp.util.MessageIDGenerator;
044    import org.jsmpp.util.MessageId;
045    import org.jsmpp.util.RandomMessageIDGenerator;
046    import org.slf4j.Logger;
047    import org.slf4j.LoggerFactory;
048    
049    /**
050     * An implementation of @{link Consumer} which use the SMPP protocol
051     * 
052     * @version 
053     */
054    public class SmppConsumer extends DefaultConsumer {
055    
056        private static final transient Logger LOG = LoggerFactory.getLogger(SmppConsumer.class);
057    
058        private SmppConfiguration configuration;
059        private SMPPSession session;
060        private MessageReceiverListener messageReceiverListener;
061        private SessionStateListener sessionStateListener;
062        private final ReentrantLock reconnectLock = new ReentrantLock();
063    
064        /**
065         * The constructor which gets a smpp endpoint, a smpp configuration and a
066         * processor
067         */
068        public SmppConsumer(SmppEndpoint endpoint, SmppConfiguration config, Processor processor) {
069            super(endpoint, processor);
070    
071            this.configuration = config;
072            this.sessionStateListener = new SessionStateListener() {
073                public void onStateChange(SessionState newState, SessionState oldState, Object source) {
074                    if (newState.equals(SessionState.CLOSED)) {
075                        LOG.warn("Lost connection to: " + getEndpoint().getConnectionString()
076                                + " - trying to reconnect...");
077                        closeSession();
078                        reconnect(configuration.getInitialReconnectDelay());
079                    }
080                }
081            };
082            this.messageReceiverListener = new MessageReceiverListener() {
083                private final MessageIDGenerator messageIDGenerator = new RandomMessageIDGenerator();
084    
085                public void onAcceptAlertNotification(AlertNotification alertNotification) {
086                    if (LOG.isDebugEnabled()) {
087                        LOG.debug("Received an alertNotification " + alertNotification);
088                    }
089    
090                    try {
091                        Exchange exchange = getEndpoint().createOnAcceptAlertNotificationExchange(
092                                alertNotification);
093    
094                        LOG.trace("Processing the new smpp exchange...");
095                        getProcessor().process(exchange);
096                        LOG.trace("Processed the new smpp exchange");
097                    } catch (Exception e) {
098                        getExceptionHandler().handleException(e);
099                    }
100                }
101    
102                public void onAcceptDeliverSm(DeliverSm deliverSm) {
103                    if (LOG.isDebugEnabled()) {
104                        LOG.debug("Received a deliverSm " + deliverSm);
105                    }
106    
107                    try {
108                        Exchange exchange = getEndpoint().createOnAcceptDeliverSmExchange(deliverSm);
109    
110                        LOG.trace("processing the new smpp exchange...");
111                        getProcessor().process(exchange);
112                        LOG.trace("processed the new smpp exchange");
113                    } catch (Exception e) {
114                        getExceptionHandler().handleException(e);
115                    }
116                }
117    
118                public DataSmResult onAcceptDataSm(DataSm dataSm, Session session)
119                    throws ProcessRequestException {
120                    if (LOG.isDebugEnabled()) {
121                        LOG.debug("Received a dataSm " + dataSm);
122                    }
123    
124                    MessageId newMessageId = messageIDGenerator.newMessageId();
125    
126                    try {
127                        Exchange exchange = getEndpoint().createOnAcceptDataSm(dataSm,
128                                newMessageId.getValue());
129    
130                        LOG.trace("processing the new smpp exchange...");
131                        getProcessor().process(exchange);
132                        LOG.trace("processed the new smpp exchange");
133                    } catch (Exception e) {
134                        getExceptionHandler().handleException(e);
135                        throw new ProcessRequestException(e.getMessage(), 255, e);
136                    }
137    
138                    return new DataSmResult(newMessageId, dataSm.getOptionalParametes());
139                }
140            };
141        }
142    
143        @Override
144        protected void doStart() throws Exception {
145            LOG.debug("Connecting to: " + getEndpoint().getConnectionString() + "...");
146    
147            super.doStart();
148            session = createSession();
149    
150            LOG.info("Connected to: " + getEndpoint().getConnectionString());
151        }
152    
153        private SMPPSession createSession() throws IOException {
154            SMPPSession session = createSMPPSession();
155            session.setEnquireLinkTimer(configuration.getEnquireLinkTimer());
156            session.setTransactionTimer(configuration.getTransactionTimer());
157            session.addSessionStateListener(sessionStateListener);
158            session.setMessageReceiverListener(messageReceiverListener);
159            session.connectAndBind(this.configuration.getHost(), this.configuration.getPort(),
160                    new BindParameter(BindType.BIND_RX, this.configuration.getSystemId(),
161                            this.configuration.getPassword(), this.configuration.getSystemType(),
162                            TypeOfNumber.UNKNOWN, NumberingPlanIndicator.UNKNOWN, ""));
163            
164            return session;
165        }
166        
167        /**
168         * Factory method to easily instantiate a mock SMPPSession
169         * 
170         * @return the SMPPSession
171         */
172        SMPPSession createSMPPSession() {
173            if (configuration.getUsingSSL()) {
174                return new SMPPSession(new SynchronizedPDUSender(new DefaultPDUSender(
175                        new DefaultComposer())), new DefaultPDUReader(), SmppSSLConnectionFactory
176                        .getInstance());
177            } else {
178                return new SMPPSession();
179            }
180        }
181    
182        @Override
183        protected void doStop() throws Exception {
184            LOG.debug("Disconnecting from: " + getEndpoint().getConnectionString() + "...");
185    
186            super.doStop();
187            closeSession();
188    
189            LOG.info("Disconnected from: " + getEndpoint().getConnectionString());
190        }
191    
192        private void closeSession() {
193            if (session != null) {
194                session.removeSessionStateListener(this.sessionStateListener);
195                // remove this hack after http://code.google.com/p/jsmpp/issues/detail?id=93 is fixed
196                try {
197                    Thread.sleep(1000);
198                    session.unbindAndClose();
199                } catch (Exception e) {
200                    LOG.warn("Could not close session " + session);
201                }
202                session = null;
203            }
204        }
205    
206        private void reconnect(final long initialReconnectDelay) {
207            if (reconnectLock.tryLock()) {
208                try {
209                    Runnable r = new Runnable() {
210                        public void run() {
211                            boolean reconnected = false;
212                            
213                            LOG.info("Schedule reconnect after " + initialReconnectDelay + " millis");
214                            try {
215                                Thread.sleep(initialReconnectDelay);
216                            } catch (InterruptedException e) {
217                            }
218    
219                            int attempt = 0;
220                            while (!(isStopping() || isStopped()) && (session == null || session.getSessionState().equals(SessionState.CLOSED))) {
221                                try {
222                                    LOG.info("Trying to reconnect to " + getEndpoint().getConnectionString() + " - attempt #" + (++attempt) + "...");
223                                    session = createSession();
224                                    reconnected = true;
225                                } catch (IOException e) {
226                                    LOG.info("Failed to reconnect to " + getEndpoint().getConnectionString());
227                                    closeSession();
228                                    try {
229                                        Thread.sleep(configuration.getReconnectDelay());
230                                    } catch (InterruptedException ee) {
231                                    }
232                                }
233                            }
234                            
235                            if (reconnected) {
236                                LOG.info("Reconnected to " + getEndpoint().getConnectionString());                        
237                            }
238                        }
239                    };
240                    
241                    Thread t = new Thread(r);
242                    t.start(); 
243                    t.join();
244                } catch (InterruptedException e) {
245                    // noop
246                }  finally {
247                    reconnectLock.unlock();
248                }
249            }
250        }
251    
252        @Override
253        public String toString() {
254            return "SmppConsumer[" + getEndpoint().getConnectionString() + "]";
255        }
256    
257        @Override
258        public SmppEndpoint getEndpoint() {
259            return (SmppEndpoint) super.getEndpoint();
260        }
261    
262        /**
263         * Returns the smpp configuration
264         * 
265         * @return the configuration
266         */
267        public SmppConfiguration getConfiguration() {
268            return configuration;
269        }
270    }