001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */package org.apache.cxf.ws.addressing;
019:
020: import java.text.MessageFormat;
021: import java.util.Collection;
022: import java.util.HashMap;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.ResourceBundle;
027: import java.util.concurrent.ConcurrentHashMap;
028: import java.util.logging.Level;
029: import java.util.logging.Logger;
030:
031: import javax.wsdl.extensions.ExtensibilityElement;
032: import javax.xml.namespace.QName;
033:
034: import org.apache.cxf.common.logging.LogUtils;
035: import org.apache.cxf.endpoint.Endpoint;
036: import org.apache.cxf.message.Exchange;
037: import org.apache.cxf.message.FaultMode;
038: import org.apache.cxf.message.Message;
039: import org.apache.cxf.phase.AbstractPhaseInterceptor;
040: import org.apache.cxf.phase.Phase;
041: import org.apache.cxf.service.model.EndpointInfo;
042: import org.apache.cxf.transport.Conduit;
043: import org.apache.cxf.transport.Destination;
044: import org.apache.cxf.ws.addressing.policy.MetadataConstants;
045: import org.apache.cxf.ws.policy.AssertionInfo;
046: import org.apache.cxf.ws.policy.AssertionInfoMap;
047:
048: /**
049: * Logical Handler responsible for aggregating the Message Addressing
050: * Properties for outgoing messages.
051: */
052: public class MAPAggregator extends AbstractPhaseInterceptor<Message> {
053:
054: private static final Logger LOG = LogUtils
055: .getL7dLogger(MAPAggregator.class);
056: private static final ResourceBundle BUNDLE = LOG
057: .getResourceBundle();
058:
059: /**
060: * REVISIT: map usage implies that the *same* interceptor instance
061: * is used in all chains.
062: */
063: protected final Map<String, String> messageIDs = new HashMap<String, String>();
064:
065: /**
066: * Whether the endpoint supports WS-Addressing.
067: */
068:
069: private Map<Endpoint, Boolean> usingAddressing = new ConcurrentHashMap<Endpoint, Boolean>();
070: private boolean usingAddressingAdvisory;
071:
072: private boolean allowDuplicates = true;
073:
074: /**
075: * Constructor.
076: */
077: public MAPAggregator() {
078: super (Phase.PRE_LOGICAL);
079: }
080:
081: /**
082: * Indicates if duplicate messageIDs are allowed.
083: * @return true iff duplicate messageIDs are allowed
084: */
085: public boolean allowDuplicates() {
086: return allowDuplicates;
087: }
088:
089: /**
090: * Allows/disallows duplicate messageIdDs.
091: * @param ad whether duplicate messageIDs are allowed
092: */
093: public void setAllowDuplicates(boolean ad) {
094: allowDuplicates = ad;
095: }
096:
097: /**
098: * Whether the presence of the <wsaw:UsingAddressing> element
099: * in the WSDL is purely advisory, i.e. its absence doesn't prevent
100: * the encoding of WS-A headers.
101: *
102: * @return true if the presence of the <wsaw:UsingAddressing> element is
103: * advisory
104: */
105: public boolean isUsingAddressingAdvisory() {
106: return usingAddressingAdvisory;
107: }
108:
109: /**
110: * Controls whether the presence of the <wsaw:UsingAddressing> element
111: * in the WSDL is purely advisory, i.e. its absence doesn't prevent
112: * the encoding of WS-A headers.
113: *
114: * @param advisory true if the presence of the <wsaw:UsingAddressing>
115: * element is to be advisory
116: */
117: public void setUsingAddressingAdvisory(boolean advisory) {
118: usingAddressingAdvisory = advisory;
119: }
120:
121: /**
122: * Invoked for normal processing of inbound and outbound messages.
123: *
124: * @param message the current message
125: */
126: public void handleMessage(Message message) {
127: mediate(message, ContextUtils.isFault(message));
128: }
129:
130: /**
131: * Invoked when unwinding normal interceptor chain when a fault occurred.
132: *
133: * @param message the current message
134: */
135: public void handleFault(Message message) {
136: }
137:
138: /**
139: * Determine if addressing is being used
140: *
141: * @param message the current message
142: * @pre message is outbound
143: */
144: private boolean usingAddressing(Message message) {
145: boolean ret = false;
146: if (ContextUtils.isRequestor(message)) {
147: ret = usingAddressingAdvisory
148: || WSAContextUtils.retrieveUsingAddressing(message)
149: || hasUsingAddressing(message)
150: || hasAddressingAssertion(message)
151: || hasUsingAddressingAssertion(message);
152: } else {
153: ret = getMAPs(message, false, false) != null;
154: }
155: return ret;
156: }
157:
158: /**
159: * Determine if the use of addressing is indicated by the presence of a
160: * the usingAddressing attribute.
161: *
162: * @param message the current message
163: * @pre message is outbound
164: * @pre requestor role
165: */
166: private boolean hasUsingAddressing(Message message) {
167: boolean ret = false;
168: Endpoint endpoint = message.getExchange().get(Endpoint.class);
169: if (null != endpoint) {
170: Boolean b = usingAddressing.get(endpoint);
171: if (null == b) {
172: EndpointInfo endpointInfo = endpoint.getEndpointInfo();
173: List<ExtensibilityElement> endpointExts = endpointInfo != null ? endpointInfo
174: .getExtensors(ExtensibilityElement.class)
175: : null;
176: List<ExtensibilityElement> bindingExts = endpointInfo != null
177: && endpointInfo.getBinding() != null ? endpointInfo
178: .getBinding().getExtensors(
179: ExtensibilityElement.class)
180: : null;
181: List<ExtensibilityElement> serviceExts = endpointInfo != null
182: && endpointInfo.getService() != null ? endpointInfo
183: .getService().getExtensors(
184: ExtensibilityElement.class)
185: : null;
186: ret = hasUsingAddressing(endpointExts)
187: || hasUsingAddressing(bindingExts)
188: || hasUsingAddressing(serviceExts);
189: b = ret ? Boolean.TRUE : Boolean.FALSE;
190: usingAddressing.put(endpoint, b);
191: } else {
192: ret = b.booleanValue();
193: }
194: }
195: return ret;
196: }
197:
198: /**
199: * Determine if the use of addressing is indicated by an Addressing assertion in the
200: * alternative chosen for the current message.
201: *
202: * @param message the current message
203: * @pre message is outbound
204: * @pre requestor role
205: */
206: private boolean hasAddressingAssertion(Message message) {
207: AssertionInfoMap aim = message.get(AssertionInfoMap.class);
208: if (null == aim) {
209: return false;
210:
211: }
212: Collection<AssertionInfo> ais = aim
213: .get(MetadataConstants.ADDRESSING_ASSERTION_QNAME);
214: if (null == ais || ais.size() == 0) {
215: return false;
216: }
217: // no need to analyse the content of the Addressing assertion here
218:
219: return true;
220: }
221:
222: /**
223: * Determine if the use of addressing is indicated by a UsingAddressing in the
224: * alternative chosen for the current message.
225: *
226: * @param message the current message
227: * @pre message is outbound
228: * @pre requestor role
229: */
230: private boolean hasUsingAddressingAssertion(Message message) {
231: AssertionInfoMap aim = message.get(AssertionInfoMap.class);
232: if (null == aim) {
233: return false;
234:
235: }
236: Collection<AssertionInfo> ais = aim
237: .get(MetadataConstants.USING_ADDRESSING_2004_QNAME);
238: if (null != ais || ais.size() > 0) {
239: return true;
240: }
241: ais = aim.get(MetadataConstants.USING_ADDRESSING_2005_QNAME);
242: if (null != ais || ais.size() > 0) {
243: return true;
244: }
245: ais = aim.get(MetadataConstants.USING_ADDRESSING_2006_QNAME);
246: if (null != ais || ais.size() > 0) {
247: return true;
248: }
249: return false;
250: }
251:
252: /**
253: * Asserts all Addressing assertions for the current message, regardless their nested
254: * Policies.
255: * @param message the current message
256: */
257: private void assertAddressing(Message message) {
258: AssertionInfoMap aim = message.get(AssertionInfoMap.class);
259: if (null == aim) {
260: return;
261:
262: }
263: QName[] types = new QName[] {
264: MetadataConstants.ADDRESSING_ASSERTION_QNAME,
265: MetadataConstants.USING_ADDRESSING_2004_QNAME,
266: MetadataConstants.USING_ADDRESSING_2005_QNAME,
267: MetadataConstants.USING_ADDRESSING_2006_QNAME };
268:
269: for (QName type : types) {
270: Collection<AssertionInfo> ais = aim.get(type);
271: if (null != ais) {
272: for (AssertionInfo ai : ais) {
273: ai.setAsserted(true);
274: }
275: }
276: }
277: }
278:
279: /**
280: * @param exts list of extension elements
281: * @return true iff the UsingAddressing element is found
282: */
283: private boolean hasUsingAddressing(List<ExtensibilityElement> exts) {
284: boolean found = false;
285: if (exts != null) {
286: Iterator<ExtensibilityElement> extensionElements = exts
287: .iterator();
288: while (extensionElements.hasNext() && !found) {
289: ExtensibilityElement ext = (ExtensibilityElement) extensionElements
290: .next();
291: found = Names.WSAW_USING_ADDRESSING_QNAME.equals(ext
292: .getElementType());
293: }
294: }
295: return found;
296: }
297:
298: /**
299: * Mediate message flow.
300: *
301: * @param message the current message
302: * @param isFault true if a fault is being mediated
303: * @return true if processing should continue on dispatch path
304: */
305: protected boolean mediate(Message message, boolean isFault) {
306: boolean continueProcessing = true;
307: if (ContextUtils.isOutbound(message)) {
308: if (usingAddressing(message)) {
309: // request/response MAPs must be aggregated
310: aggregate(message, isFault);
311: }
312: } else if (!ContextUtils.isRequestor(message)) {
313: // responder validates incoming MAPs
314: AddressingPropertiesImpl maps = getMAPs(message, false,
315: false);
316: if (null == maps) {
317: return false;
318: }
319: boolean isOneway = message.getExchange().isOneWay();
320: continueProcessing = validateIncomingMAPs(maps, message);
321: if (continueProcessing) {
322: // any faults thrown from here on can be correlated with this message
323: message.put(FaultMode.class,
324: FaultMode.LOGICAL_RUNTIME_FAULT);
325: if (isOneway
326: || !ContextUtils.isGenericAddress(maps
327: .getReplyTo())) {
328: ContextUtils.rebaseResponse(maps.getReplyTo(),
329: maps, message);
330: }
331: if (!isOneway) {
332: // ensure the inbound MAPs are available in both the full & fault
333: // response messages (used to determine relatesTo etc.)
334: ContextUtils.propogateReceivedMAPs(maps, message
335: .getExchange());
336: }
337: } else {
338: // validation failure => dispatch is aborted, response MAPs
339: // must be aggregated
340: aggregate(message, isFault);
341: }
342: }
343: if (null != ContextUtils.retrieveMAPs(message, false,
344: ContextUtils.isOutbound(message))) {
345: assertAddressing(message);
346: }
347: return continueProcessing;
348: }
349:
350: /**
351: * Perform MAP aggregation.
352: *
353: * @param message the current message
354: * @param isFault true if a fault is being mediated
355: */
356: private void aggregate(Message message, boolean isFault) {
357: AddressingPropertiesImpl maps = assembleGeneric(message);
358: boolean isRequestor = ContextUtils.isRequestor(message);
359: addRoleSpecific(maps, message, isRequestor, isFault);
360: // outbound property always used to store MAPs, as this handler
361: // aggregates only when either:
362: // a) message really is outbound
363: // b) message is currently inbound, but we are about to abort dispatch
364: // due to an incoming MAPs validation failure, so the dispatch
365: // will shortly traverse the outbound path
366: ContextUtils.storeMAPs(maps, message, true, isRequestor);
367: }
368:
369: /**
370: * Assemble the generic MAPs (for both requests and responses).
371: *
372: * @param message the current message
373: * @return AddressingProperties containing the generic MAPs
374: */
375: private AddressingPropertiesImpl assembleGeneric(Message message) {
376: AddressingPropertiesImpl maps = getMAPs(message, true, true);
377: // MessageID
378: if (maps.getMessageID() == null) {
379: String messageID = ContextUtils.generateUUID();
380: maps.setMessageID(ContextUtils.getAttributedURI(messageID));
381: }
382: // Action
383: if (ContextUtils.hasEmptyAction(maps)) {
384: maps.setAction(ContextUtils.getAction(message));
385: }
386: return maps;
387: }
388:
389: /**
390: * Add MAPs which are specific to the requestor or responder role.
391: *
392: * @param maps the MAPs being assembled
393: * @param message the current message
394: * @param isRequestor true iff the current messaging role is that of
395: * requestor
396: * @param isFault true if a fault is being mediated
397: */
398: private void addRoleSpecific(AddressingPropertiesImpl maps,
399: Message message, boolean isRequestor, boolean isFault) {
400: if (isRequestor) {
401: Exchange exchange = message.getExchange();
402:
403: // add request-specific MAPs
404: boolean isOneway = exchange.isOneWay();
405: boolean isOutbound = ContextUtils.isOutbound(message);
406: Conduit conduit = null;
407:
408: // To
409: if (maps.getTo() == null) {
410: if (isOutbound) {
411: conduit = ContextUtils.getConduit(conduit, message);
412: }
413: EndpointReferenceType reference = conduit != null ? conduit
414: .getTarget()
415: : ContextUtils.getNoneEndpointReference();
416: maps.setTo(reference);
417: }
418:
419: // ReplyTo, set if null in MAPs or if set to a generic address
420: // (anonymous or none) that may not be appropriate for the
421: // current invocation
422: EndpointReferenceType replyTo = maps.getReplyTo();
423: if (ContextUtils.isGenericAddress(replyTo)) {
424: conduit = ContextUtils.getConduit(conduit, message);
425: if (conduit != null) {
426: Destination backChannel = conduit.getBackChannel();
427: if (backChannel != null) {
428: replyTo = backChannel.getAddress();
429: }
430: }
431: if (replyTo == null
432: || (isOneway && (replyTo.getAddress() == null || !Names.WSA_NONE_ADDRESS
433: .equals(replyTo.getAddress().getValue())))) {
434: AttributedURIType address = ContextUtils
435: .getAttributedURI(isOneway ? Names.WSA_NONE_ADDRESS
436: : Names.WSA_ANONYMOUS_ADDRESS);
437: replyTo = ContextUtils.WSA_OBJECT_FACTORY
438: .createEndpointReferenceType();
439: replyTo.setAddress(address);
440: }
441: maps.setReplyTo(replyTo);
442: }
443:
444: // FaultTo
445: if (maps.getFaultTo() == null) {
446: maps.setFaultTo(maps.getReplyTo());
447: } else if (maps.getFaultTo().getAddress() == null) {
448: maps.setFaultTo(null);
449: }
450: } else {
451: // add response-specific MAPs
452: AddressingPropertiesImpl inMAPs = getMAPs(message, false,
453: false);
454: maps.exposeAs(inMAPs.getNamespaceURI());
455: // To taken from ReplyTo or FaultTo in incoming MAPs (depending
456: // on the fault status of the response)
457: if (isFault && inMAPs.getFaultTo() != null) {
458: maps.setTo(inMAPs.getFaultTo());
459: } else if (maps.getTo() == null
460: && inMAPs.getReplyTo() != null) {
461: maps.setTo(inMAPs.getReplyTo());
462: }
463:
464: // RelatesTo taken from MessageID in incoming MAPs
465: if (inMAPs.getMessageID() != null
466: && !Boolean.TRUE.equals(message
467: .get(Message.PARTIAL_RESPONSE_MESSAGE))) {
468: String inMessageID = inMAPs.getMessageID().getValue();
469: maps.setRelatesTo(ContextUtils
470: .getRelatesTo(inMessageID));
471: }
472:
473: // fallback fault action
474: if (isFault && maps.getAction() == null) {
475: maps
476: .setAction(ContextUtils
477: .getAttributedURI(Names.WSA_DEFAULT_FAULT_ACTION));
478: }
479:
480: if (isFault
481: && !ContextUtils.isGenericAddress(inMAPs
482: .getFaultTo())) {
483: ContextUtils.rebaseResponse(inMAPs.getFaultTo(),
484: inMAPs, message);
485: }
486: }
487: }
488:
489: /**
490: * Get the starting point MAPs (either empty or those set explicitly
491: * by the application on the binding provider request context).
492: *
493: * @param message the current message
494: * @param isProviderContext true if the binding provider request context
495: * available to the client application as opposed to the message context
496: * visible to handlers
497: * @param isOutbound true iff the message is outbound
498: * @return AddressingProperties retrieved MAPs
499: */
500: private AddressingPropertiesImpl getMAPs(Message message,
501: boolean isProviderContext, boolean isOutbound) {
502:
503: AddressingPropertiesImpl maps = null;
504: maps = ContextUtils.retrieveMAPs(message, isProviderContext,
505: isOutbound);
506: LOG.log(Level.INFO, "MAPs retrieved from message {0}", maps);
507:
508: if (maps == null && isProviderContext) {
509: maps = new AddressingPropertiesImpl();
510: }
511: return maps;
512: }
513:
514: /**
515: * Validate incoming MAPs
516: * @param maps the incoming MAPs
517: * @param message the current message
518: * @return true if incoming MAPs are valid
519: * @pre inbound message, not requestor
520: */
521: private boolean validateIncomingMAPs(AddressingProperties maps,
522: Message message) {
523: boolean valid = true;
524: if (!allowDuplicates && maps != null) {
525: AttributedURIType messageID = maps.getMessageID();
526: if (messageID != null
527: && messageIDs.put(messageID.getValue(), messageID
528: .getValue()) != null) {
529: LOG.log(Level.WARNING, "DUPLICATE_MESSAGE_ID_MSG",
530: messageID.getValue());
531: String reason = BUNDLE
532: .getString("DUPLICATE_MESSAGE_ID_MSG");
533: String l7dReason = MessageFormat.format(reason,
534: messageID.getValue());
535: ContextUtils.storeMAPFaultName(
536: Names.DUPLICATE_MESSAGE_ID_NAME, message);
537: ContextUtils.storeMAPFaultReason(l7dReason, message);
538: valid = false;
539: }
540: }
541: return valid;
542: }
543: }
|