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.servicemix.camel;
018    
019    import java.net.URI;
020    import java.net.URISyntaxException;
021    
022    import javax.jbi.messaging.InOptionalOut;
023    import javax.jbi.messaging.InOut;
024    import javax.jbi.messaging.MessageExchange;
025    import javax.jbi.messaging.MessageExchangeFactory;
026    import javax.jbi.messaging.MessagingException;
027    import javax.jbi.messaging.NormalizedMessage;
028    import javax.jbi.messaging.RobustInOnly;
029    import javax.security.auth.Subject;
030    import javax.xml.namespace.QName;
031    import javax.xml.transform.Source;
032    
033    import org.apache.camel.CamelContext;
034    import org.apache.camel.Exchange;
035    import org.apache.camel.ExchangePattern;
036    import org.apache.camel.Message;
037    import org.apache.camel.NoTypeConversionAvailableException;
038    import org.apache.camel.spi.HeaderFilterStrategy;
039    import org.apache.commons.logging.Log;
040    import org.apache.commons.logging.LogFactory;
041    import org.apache.servicemix.camel.util.BasicSerializationHeaderFilterStrategy;
042    import org.apache.servicemix.camel.util.HeaderFilterStrategies;
043    import org.apache.servicemix.camel.util.StrictSerializationHeaderFilterStrategy;
044    import org.apache.servicemix.jbi.FaultException;
045    
046    /**
047     * The binding of how Camel messages get mapped to JBI and back again
048     *
049     * @version $Revision: 563665 $
050     */
051    public class JbiBinding {
052    
053        public static final String MESSAGE_EXCHANGE = "JbiMessageExchange";
054        public static final String OPERATION = "JbiOperation";
055        public static final String SECURITY_SUBJECT = "JbiSecuritySubject";
056        
057        @Deprecated
058        public static final String OPERATION_STRING = "jbi.operation";
059        
060        private static final Log LOG = LogFactory.getLog(JbiBinding.class);
061    
062        private final CamelContext context;
063    
064        private HeaderFilterStrategies strategies = new HeaderFilterStrategies();
065        private boolean convertExceptions;
066    
067        /**
068         * Create the binding instance for a given CamelContext
069         *
070         * @param context the CamelContext
071         */
072        public JbiBinding(CamelContext context) {
073            this(context, false);    
074        }
075    
076        public JbiBinding(CamelContext context, boolean strictSerialization) {
077            this.context = context;
078            if (strictSerialization) {
079                strategies.add(new StrictSerializationHeaderFilterStrategy());
080            } else {
081                strategies.add(new BasicSerializationHeaderFilterStrategy());
082            }
083        }
084    
085        public void addHeaderFilterStrategy(HeaderFilterStrategy strategy) {
086            strategies.add(strategy);
087        }
088    
089        public void setConvertExceptions(boolean convertExceptions) {
090            this.convertExceptions = convertExceptions;
091        }
092    
093        public MessageExchange makeJbiMessageExchange(Exchange camelExchange,
094                                                      MessageExchangeFactory exchangeFactory,
095                                                      String defaultMep)
096            throws MessagingException, URISyntaxException {
097            
098            MessageExchange jbiExchange = createJbiMessageExchange(camelExchange, exchangeFactory, defaultMep);
099            NormalizedMessage normalizedMessage = jbiExchange.getMessage("in");
100            if (normalizedMessage == null) {
101                normalizedMessage = jbiExchange.createMessage();
102                jbiExchange.setMessage(normalizedMessage, "in");
103            }
104            copyFromCamelToJbi(camelExchange.getIn(), normalizedMessage);
105            return jbiExchange;
106        }
107    
108        protected MessageExchange createJbiMessageExchange(Exchange camelExchange,
109            MessageExchangeFactory exchangeFactory, String defaultMep)
110            throws MessagingException, URISyntaxException {
111    
112            // option 1 -- use the MEP that was configured on the endpoint URI
113            ExchangePattern mep = ExchangePattern.fromWsdlUri(defaultMep);
114            if (mep == null) {
115                // option 2 -- use the MEP from the Camel Exchange
116                mep = camelExchange.getPattern();
117            }
118            MessageExchange answer = null;
119            if (mep != null) {
120                if (mep == ExchangePattern.InOnly) {
121                    answer = exchangeFactory.createInOnlyExchange();
122                } else if (mep == ExchangePattern.InOptionalOut) {
123                    answer = exchangeFactory.createInOptionalOutExchange();
124                } else if (mep == ExchangePattern.InOut) {
125                    answer = exchangeFactory.createInOutExchange();
126                } else if (mep == ExchangePattern.RobustInOnly) {
127                    answer = exchangeFactory.createRobustInOnlyExchange();
128                } else {
129                    answer = exchangeFactory.createExchange(new URI(mep.toString()));
130                }
131            }
132            // TODO: this is not really usefull as the out will not be
133            // TODO: populated at that time
134            if (answer == null) {
135                // lets try choose the best MEP based on the camel message
136                Message out = camelExchange.getOut(false);
137                if (out == null || out.getBody() == null) {
138                    answer = exchangeFactory.createInOnlyExchange();
139                } else {
140                    answer = exchangeFactory.createInOutExchange();
141                }
142            }
143    
144            if (getOperation(camelExchange) != null) {
145                answer.setOperation(getOperation(camelExchange));
146            }
147            
148            // let's try to use the old way too
149            if (answer.getOperation() == null && camelExchange.getProperties().containsKey(JbiBinding.OPERATION_STRING)) {
150                answer.setOperation(QName.valueOf(camelExchange.getProperty(JbiBinding.OPERATION_STRING, String.class)));
151            }
152    
153            return answer;
154        }
155    
156        public Exchange createExchange(MessageExchange exchange) {
157            Exchange result = new JbiExchange(context, this);
158            result.setProperty(MESSAGE_EXCHANGE, exchange);
159            result.setPattern(getExchangePattern(exchange));
160            if (exchange.getOperation() != null) {
161                result.setProperty(OPERATION, exchange.getOperation());
162                result.setProperty(OPERATION_STRING, exchange.getOperation().toString());
163            }
164            if (exchange.getMessage("in") != null) {
165                copyJbiToCamel(exchange.getMessage("in"), result.getIn());
166            }
167            return result;
168        }
169    
170        /*
171         * Get the corresponding Camel ExchangePattern for a given JBI Exchange
172         */
173        private ExchangePattern getExchangePattern(MessageExchange exchange) {
174            if (exchange instanceof InOut) {
175                return ExchangePattern.InOut;
176            } else if (exchange instanceof InOptionalOut) {
177                return ExchangePattern.InOptionalOut;
178            } else if (exchange instanceof RobustInOnly) {
179                return ExchangePattern.RobustInOnly;
180            } else {
181                return ExchangePattern.InOnly;
182            }
183        }
184    
185        private void copyJbiToCamel(NormalizedMessage from, Message to) {
186            to.setBody(from.getContent());
187            if (from.getSecuritySubject() != null) {
188                to.setHeader(SECURITY_SUBJECT, from.getSecuritySubject());
189            }
190            for (Object key : from.getPropertyNames()) {
191                to.setHeader(key.toString(), from.getProperty(key.toString()));
192            }
193            for (Object id : from.getAttachmentNames()) {
194                to.addAttachment(id.toString(), from.getAttachment(id.toString()));
195            }
196        }
197    
198        public void copyFromCamelToJbi(Message message, NormalizedMessage normalizedMessage) throws MessagingException {
199            try {
200                normalizedMessage.setContent(message.getBody(Source.class));
201            } catch (NoTypeConversionAvailableException e) {
202                LOG.warn("Unable to convert message body of type " + message.getBody().getClass() + " into an XML Source");
203            }
204            
205            if (getSecuritySubject(message) != null) {
206                normalizedMessage.setSecuritySubject(getSecuritySubject(message));
207            }
208            
209            for (String key : message.getHeaders().keySet()) {
210                Object value = message.getHeader(key);
211                if (!strategies.applyFilterToCamelHeaders(key, value)) {
212                    normalizedMessage.setProperty(key, value);
213                }
214            }
215            
216            for (String id : message.getAttachmentNames()) {
217                normalizedMessage.addAttachment(id, message.getAttachment(id));
218            }
219        }
220    
221        public void copyFromCamelToJbi(Exchange exchange, MessageExchange messageExchange) throws MessagingException {
222            NormalizedMessage in = messageExchange.getMessage("in");
223            for (String key : exchange.getIn().getHeaders().keySet()) {
224                in.setProperty(key, exchange.getIn().getHeader(key));
225            }        
226            
227            if (isOutCapable(messageExchange)) {
228                if (exchange.getOut(false) == null) {
229                    //JBI MEP requires a reply and the Camel exchange failed to produce one -- echoing back the request
230                    NormalizedMessage out = messageExchange.createMessage();
231                    copyFromCamelToJbi(exchange.getIn(), out);
232                    messageExchange.setMessage(out, "out");
233                } else {
234                    NormalizedMessage out = messageExchange.createMessage();
235                    copyFromCamelToJbi(exchange.getOut(), out);
236                    messageExchange.setMessage(out, "out");
237                }
238            }
239        }
240    
241    
242        /**
243         * Extract an Exception from the exchange.  This method will always return a
244         *
245         * @param exchange the Camel exchange
246         * @return an exception
247         */
248        public Exception extractException(Exchange exchange) {
249            Throwable e  = exchange.getException();
250            if (!(e instanceof Exception) || convertExceptions) {
251                e = context.getTypeConverter().convertTo(FaultException.class, exchange);
252            }
253            return (Exception) e;
254        }
255    
256        private boolean isOutCapable(MessageExchange exchange) {
257            return exchange instanceof InOut || exchange instanceof InOptionalOut;
258        }
259    
260        /**
261         * Access the JBI MessageExchange that has been stored on the Camel Exchange
262         * @return the JBI MessageExchange
263         */
264        public static MessageExchange getMessageExchange(Exchange exchange) {
265            return exchange.getProperty(MESSAGE_EXCHANGE, MessageExchange.class);
266        }
267        
268        /**
269         * Access the JBI Operation that has been stored on a Camel Exchange
270         * @param exchange the Camel Exchange
271         * @return the JBI Operation as a QName
272         */
273        public static QName getOperation(Exchange exchange) {
274            return exchange.getProperty(OPERATION, QName.class);
275        }
276        
277        /**
278         * Access the security subject that has been stored on the Camel Message
279         * @param message the Camel message
280         * @return the Subject or <code>null</code> is no Subject is available in the headers
281         */
282        public static Subject getSecuritySubject(Message message) {
283            if (message.getHeader(SECURITY_SUBJECT) != null) {
284                return message.getHeader(SECURITY_SUBJECT, Subject.class);
285            }
286            return null;
287        }
288    }