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.impl.DefaultProducer;
024    import org.jsmpp.DefaultPDUReader;
025    import org.jsmpp.DefaultPDUSender;
026    import org.jsmpp.SynchronizedPDUSender;
027    import org.jsmpp.bean.Alphabet;
028    import org.jsmpp.bean.BindType;
029    import org.jsmpp.bean.ESMClass;
030    import org.jsmpp.bean.GeneralDataCoding;
031    import org.jsmpp.bean.MessageClass;
032    import org.jsmpp.bean.NumberingPlanIndicator;
033    import org.jsmpp.bean.RegisteredDelivery;
034    import org.jsmpp.bean.SubmitSm;
035    import org.jsmpp.bean.TypeOfNumber;
036    import org.jsmpp.extra.SessionState;
037    import org.jsmpp.session.BindParameter;
038    import org.jsmpp.session.SMPPSession;
039    import org.jsmpp.session.SessionStateListener;
040    import org.jsmpp.util.DefaultComposer;
041    import org.slf4j.Logger;
042    import org.slf4j.LoggerFactory;
043    
044    /**
045     * An implementation of @{link Producer} which use the SMPP protocol
046     */
047    public class SmppProducer extends DefaultProducer {
048    
049        private static final transient Logger LOG = LoggerFactory.getLogger(SmppProducer.class);
050    
051        private SmppConfiguration configuration;
052        private SMPPSession session;
053        private SessionStateListener sessionStateListener;
054        private final ReentrantLock connectLock = new ReentrantLock();
055    
056        public SmppProducer(SmppEndpoint endpoint, SmppConfiguration config) {
057            super(endpoint);
058            this.configuration = config;
059            this.sessionStateListener = new SessionStateListener() {
060                public void onStateChange(SessionState newState, SessionState oldState, Object source) {
061                    if (newState.equals(SessionState.CLOSED)) {
062                        LOG.warn("Lost connection to: " + getEndpoint().getConnectionString() + " - trying to reconnect...");
063                        closeSession();
064                        reconnect(configuration.getInitialReconnectDelay());
065                    }
066                }
067            };
068        }
069    
070        @Override
071        protected void doStart() throws Exception {
072            super.doStart();
073            
074            if (!getConfiguration().isLazySessionCreation()) {
075                if (connectLock.tryLock()) {
076                    try {
077                        session = createSession();
078                    } finally {
079                        connectLock.unlock();
080                    }
081                }
082            }
083        }
084        
085        private SMPPSession createSession() throws IOException {
086            LOG.debug("Connecting to: " + getEndpoint().getConnectionString() + "...");
087            
088            SMPPSession session = createSMPPSession();
089            session.setEnquireLinkTimer(this.configuration.getEnquireLinkTimer());
090            session.setTransactionTimer(this.configuration.getTransactionTimer());
091            session.addSessionStateListener(sessionStateListener);
092            session.connectAndBind(
093                    this.configuration.getHost(),
094                    this.configuration.getPort(),
095                    new BindParameter(
096                            BindType.BIND_TX,
097                            this.configuration.getSystemId(),
098                            this.configuration.getPassword(), 
099                            this.configuration.getSystemType(),
100                            TypeOfNumber.valueOf(configuration.getTypeOfNumber()),
101                            NumberingPlanIndicator.valueOf(configuration.getNumberingPlanIndicator()),
102                            ""));
103            
104            LOG.info("Connected to: " + getEndpoint().getConnectionString());
105            
106            return session;
107        }
108        
109        /**
110         * Factory method to easily instantiate a mock SMPPSession
111         * 
112         * @return the SMPPSession
113         */
114        SMPPSession createSMPPSession() {
115            if (configuration.getUsingSSL()) {
116                return new SMPPSession(new SynchronizedPDUSender(new DefaultPDUSender(new DefaultComposer())),
117                                       new DefaultPDUReader(), SmppSSLConnectionFactory.getInstance());
118            } else {
119                return new SMPPSession();
120            }
121        }
122    
123        public void process(Exchange exchange) throws Exception {
124            if (session == null) {
125                if (getConfiguration().isLazySessionCreation()) {
126                    if (connectLock.tryLock()) {
127                        try {
128                            if (session == null) {
129                                session = createSession();
130                            }
131                        } finally {
132                            connectLock.unlock();
133                        }
134                    }
135                }
136            }
137            
138            LOG.debug("Sending a short message for exchange id '{}'...", exchange.getExchangeId());
139            
140            // only possible by trying to reconnect 
141            if (this.session == null) {
142                throw new IOException("Lost connection to " + getEndpoint().getConnectionString() + " and yet not reconnected");
143            }
144    
145            SubmitSm submitSm = getEndpoint().getBinding().createSubmitSm(exchange);
146            String messageId = session.submitShortMessage(
147                    submitSm.getServiceType(), 
148                    TypeOfNumber.valueOf(submitSm.getSourceAddrTon()),
149                    NumberingPlanIndicator.valueOf(submitSm.getSourceAddrNpi()),
150                    submitSm.getSourceAddr(),
151                    TypeOfNumber.valueOf(submitSm.getDestAddrTon()),
152                    NumberingPlanIndicator.valueOf(submitSm.getDestAddrNpi()),
153                    submitSm.getDestAddress(),
154                    new ESMClass(),
155                    submitSm.getProtocolId(),
156                    submitSm.getPriorityFlag(),
157                    submitSm.getScheduleDeliveryTime(),
158                    submitSm.getValidityPeriod(),
159                    new RegisteredDelivery(submitSm.getRegisteredDelivery()),
160                    submitSm.getReplaceIfPresent(),
161                    new GeneralDataCoding(
162                            false,
163                            true,
164                            MessageClass.CLASS1,
165                            Alphabet.valueOf(submitSm.getDataCoding())),
166                    (byte) 0,
167                    submitSm.getShortMessage(),
168                    submitSm.getOptionalParametes());
169    
170            LOG.debug("Sent a short message for exchange id '{}' and received message id '{}'",
171                    exchange.getExchangeId(), messageId);
172    
173            if (exchange.getPattern().isOutCapable()) {
174                LOG.debug("Exchange is out capable, setting headers on out exchange...");
175                exchange.getOut().setHeader(SmppBinding.ID, messageId);
176            } else {
177                LOG.debug("Exchange is not out capable, setting headers on in exchange...");
178                exchange.getIn().setHeader(SmppBinding.ID, messageId);
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 (connectLock.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                    connectLock.unlock();
248                }
249            }
250        }
251        
252        @Override
253        public SmppEndpoint getEndpoint() {
254            return (SmppEndpoint) super.getEndpoint();
255        }
256    
257        /**
258         * Returns the smppConfiguration for this producer
259         * 
260         * @return the configuration
261         */
262        public SmppConfiguration getConfiguration() {
263            return configuration;
264        }
265    
266        @Override
267        public String toString() {
268            return "SmppProducer[" + getEndpoint().getConnectionString() + "]";
269        }
270    }