58 * setting to make a bunch of invocations. So we'd like to take some hit |
58 * setting to make a bunch of invocations. So we'd like to take some hit |
59 * when the user actually sets a property to do some computation, |
59 * when the user actually sets a property to do some computation, |
60 * then use that computed value during a method invocation again and again. |
60 * then use that computed value during a method invocation again and again. |
61 * |
61 * |
62 * <p> |
62 * <p> |
63 * For this goal, we use {@link PropertySet} and implement some properties |
63 * For this goal, we use {@link com.sun.xml.internal.ws.api.PropertySet} and implement some properties |
64 * as virtual properties backed by methods. This allows us to do the computation |
64 * as virtual properties backed by methods. This allows us to do the computation |
65 * in the setter, and store it in a field. |
65 * in the setter, and store it in a field. |
66 * |
66 * |
67 * <p> |
67 * <p> |
68 * These fields are used by {@link Stub#process} to populate a {@link Packet}. |
68 * These fields are used by {@link Stub#process} to populate a {@link Packet}. |
69 * |
69 * |
70 * |
|
71 * |
|
72 * <h2>How it works?</h2> |
70 * <h2>How it works?</h2> |
73 * <p> |
71 * <p> |
74 * We make an assumption that a request context is mostly used to just |
72 * For better performance, we wan't use strongly typed field as much as possible |
75 * get and put values, not really for things like enumerating or size. |
73 * to avoid reflection and unnecessary collection iterations; |
76 * |
74 * |
77 * <p> |
75 * Using {@link com.oracle.webservices.internal.api.message.BasePropertySet.MapView} implementation allows client to use {@link Map} interface |
78 * So we start by maintaining state as a combination of {@link #others} |
76 * in a way that all the strongly typed properties are reflected to the fields |
79 * bag and strongly-typed fields. As long as the application uses |
77 * right away. Any additional (extending) properties can be added by client as well; |
80 * just {@link Map#put}, {@link Map#get}, and {@link Map#putAll}, we can |
78 * those would be processed using iterating the {@link MapView} and their processing, |
81 * do things in this way. In this mode a {@link Map} we return works as |
79 * of course, would be slower. |
82 * a view into {@link RequestContext}, and by itself it maintains no state. |
80 * <p> |
83 * |
81 * The previous implementation with fallback mode has been removed to simplify |
84 * <p> |
82 * the code and remove the bugs. |
85 * If {@link RequestContext} is in this mode, its state can be copied |
|
86 * efficiently into {@link Packet}. |
|
87 * |
|
88 * <p> |
|
89 * Once the application uses any other {@link Map} method, we move to |
|
90 * the "fallback" mode, where the data is actually stored in a {@link HashMap}, |
|
91 * this is necessary for implementing the map interface contract correctly. |
|
92 * |
|
93 * <p> |
|
94 * To be safe, once we fallback, we'll never come back to the efficient state. |
|
95 * |
|
96 * |
|
97 * |
|
98 * <h2>Caution</h2> |
|
99 * <p> |
|
100 * Once we are in the fallback mode, none of the strongly typed field will |
|
101 * be used, and they may contain stale values. So the only method |
|
102 * the code outside this class can safely use is {@link #copy()}, |
|
103 * {@link #fill(Packet)}, and constructors. Do not access the strongly |
|
104 * typed fields nor {@link #others} directly. |
|
105 * |
83 * |
106 * @author Kohsuke Kawaguchi |
84 * @author Kohsuke Kawaguchi |
107 */ |
85 */ |
108 @SuppressWarnings({"SuspiciousMethodCalls"}) |
86 @SuppressWarnings({"SuspiciousMethodCalls"}) |
109 public final class RequestContext extends DistributedPropertySet { |
87 public final class RequestContext extends BaseDistributedPropertySet { |
110 private static final Logger LOGGER = Logger.getLogger(RequestContext.class.getName()); |
88 private static final Logger LOGGER = Logger.getLogger(RequestContext.class.getName()); |
111 |
89 |
112 /** |
90 /** |
113 * The default value to be use for {@link #contentNegotiation} obtained |
91 * The default value to be use for {@link #contentNegotiation} obtained |
114 * from a system property. |
92 * from a system property. |
226 * |
206 * |
227 * This only control whether value of BindingProvider.SOAPACTION_URI_PROPERTY is used or not and not |
207 * This only control whether value of BindingProvider.SOAPACTION_URI_PROPERTY is used or not and not |
228 * if it can be sent if it can be obtained by other means such as WSDL binding |
208 * if it can be sent if it can be obtained by other means such as WSDL binding |
229 */ |
209 */ |
230 private Boolean soapActionUse; |
210 private Boolean soapActionUse; |
231 @Property(BindingProvider.SOAPACTION_USE_PROPERTY) |
211 |
232 public Boolean getSoapActionUse(){ |
212 @Property(SOAPACTION_USE_PROPERTY) |
|
213 public Boolean getSoapActionUse() { |
233 return soapActionUse; |
214 return soapActionUse; |
234 } |
215 } |
235 public void setSoapActionUse(Boolean sActionUse){ |
216 |
|
217 public void setSoapActionUse(Boolean sActionUse) { |
236 soapActionUse = sActionUse; |
218 soapActionUse = sActionUse; |
237 } |
219 } |
238 |
220 |
239 /** |
221 /** |
240 * {@link Map} exposed to the user application. |
|
241 */ |
|
242 private final MapView mapView = new MapView(); |
|
243 |
|
244 /** |
|
245 * Creates an empty {@link RequestContext}. |
222 * Creates an empty {@link RequestContext}. |
246 */ |
223 */ |
247 /*package*/ RequestContext() { |
224 RequestContext() { |
248 others = new HashMap<String, Object>(); |
|
249 } |
225 } |
250 |
226 |
251 /** |
227 /** |
252 * Copy constructor. |
228 * Copy constructor. |
253 */ |
229 */ |
254 private RequestContext(RequestContext that) { |
230 private RequestContext(RequestContext that) { |
255 others = new HashMap<String,Object>(that.others); |
231 for (Map.Entry<String, Object> entry : that.asMapLocal().entrySet()) { |
256 mapView.fallbackMap = that.mapView.fallbackMap != null ? |
232 if (!propMap.containsKey(entry.getKey())) { |
257 new HashMap<String, Object>(that.mapView.fallback()) : null; |
233 asMap().put(entry.getKey(), entry.getValue()); |
|
234 } |
|
235 } |
258 endpointAddress = that.endpointAddress; |
236 endpointAddress = that.endpointAddress; |
259 soapAction = that.soapAction; |
237 soapAction = that.soapAction; |
|
238 soapActionUse = that.soapActionUse; |
260 contentNegotiation = that.contentNegotiation; |
239 contentNegotiation = that.contentNegotiation; |
261 that.copySatelliteInto(this); |
240 that.copySatelliteInto(this); |
262 } |
241 } |
263 |
242 |
264 /** |
243 /** |
265 * The efficient get method that reads from {@link RequestContext}. |
244 * The efficient get method that reads from {@link RequestContext}. |
266 */ |
245 */ |
|
246 @Override |
267 public Object get(Object key) { |
247 public Object get(Object key) { |
268 if(super.supports(key)) |
248 if(supports(key)) { |
269 return super.get(key); |
249 return super.get(key); |
270 else |
250 } else { |
271 return others.get(key); |
251 // use mapView to get extending property |
|
252 return asMap().get(key); |
|
253 } |
272 } |
254 } |
273 |
255 |
274 /** |
256 /** |
275 * The efficient put method that updates {@link RequestContext}. |
257 * The efficient put method that updates {@link RequestContext}. |
276 */ |
258 */ |
|
259 @Override |
277 public Object put(String key, Object value) { |
260 public Object put(String key, Object value) { |
278 if(super.supports(key)) |
261 |
|
262 if(supports(key)) { |
279 return super.put(key,value); |
263 return super.put(key,value); |
280 else |
264 } else { |
281 return others.put(key,value); |
265 // use mapView to put extending property (if the map allows that) |
282 } |
266 return asMap().put(key, value); |
283 |
267 } |
284 /** |
|
285 * Gets the {@link Map} view of this request context. |
|
286 * |
|
287 * @return |
|
288 * Always same object. Returned map is live. |
|
289 */ |
|
290 public Map<String,Object> getMapView() { |
|
291 return mapView; |
|
292 } |
268 } |
293 |
269 |
294 /** |
270 /** |
295 * Fill a {@link Packet} with values of this {@link RequestContext}. |
271 * Fill a {@link Packet} with values of this {@link RequestContext}. |
296 */ |
272 * |
|
273 * @param packet to be filled with context values |
|
274 * @param isAddressingEnabled flag if addressing enabled (to provide warning if necessary) |
|
275 */ |
|
276 @SuppressWarnings("unchecked") |
297 public void fill(Packet packet, boolean isAddressingEnabled) { |
277 public void fill(Packet packet, boolean isAddressingEnabled) { |
298 if(mapView.fallbackMap==null) { |
278 |
299 if (endpointAddress != null) |
279 // handling as many properties as possible (all in propMap.keySet()) |
300 packet.endpointAddress = endpointAddress; |
280 // to avoid slow Packet.put() |
301 packet.contentNegotiation = contentNegotiation; |
281 if (endpointAddress != null) { |
302 |
282 packet.endpointAddress = endpointAddress; |
303 //JAX-WS-596: Check the semantics of SOAPACTION_USE_PROPERTY before using the SOAPACTION_URI_PROPERTY for |
283 } |
304 // SoapAction as specified in the javadoc of BindingProvider. The spec seems to be little contradicting with |
284 packet.contentNegotiation = contentNegotiation; |
305 // javadoc and says that the use property effects the sending of SOAPAction property. |
285 fillSOAPAction(packet, isAddressingEnabled); |
306 // Since the user has the capability to set the value as "" if needed, implement the javadoc behavior. |
286 mergeRequestHeaders(packet); |
307 |
287 |
308 if ((soapActionUse != null && soapActionUse) || (soapActionUse == null && isAddressingEnabled)) { |
288 Set<String> handlerScopeNames = new HashSet<String>(); |
309 if (soapAction != null) { |
289 |
310 packet.soapAction = soapAction; |
290 copySatelliteInto(packet); |
|
291 |
|
292 // extending properties ... |
|
293 for (String key : asMapLocal().keySet()) { |
|
294 |
|
295 //if it is not standard property it defaults to Scope.HANDLER |
|
296 if (!supportsLocal(key)) { |
|
297 handlerScopeNames.add(key); |
|
298 } |
|
299 |
|
300 // to avoid slow Packet.put(), handle as small number of props as possible |
|
301 // => only properties not from RequestContext object |
|
302 if (!propMap.containsKey(key)) { |
|
303 Object value = asMapLocal().get(key); |
|
304 if (packet.supports(key)) { |
|
305 // very slow operation - try to avoid it! |
|
306 packet.put(key, value); |
|
307 } else { |
|
308 packet.invocationProperties.put(key, value); |
311 } |
309 } |
312 } |
310 } |
313 |
311 } |
314 if((!isAddressingEnabled && (soapActionUse == null || !soapActionUse)) && soapAction != null) { |
312 |
315 LOGGER.warning("BindingProvider.SOAPACTION_URI_PROPERTY is set in the RequestContext but is ineffective," + |
313 if (!handlerScopeNames.isEmpty()) { |
316 " Either set BindingProvider.SOAPACTION_USE_PROPERTY to true or enable AddressingFeature"); |
314 packet.getHandlerScopePropertyNames(false).addAll(handlerScopeNames); |
317 } |
315 } |
318 |
316 } |
319 copySatelliteInto((DistributedPropertySet)packet); |
317 |
320 |
318 @SuppressWarnings("unchecked") |
321 if(!others.isEmpty()) { |
319 private void mergeRequestHeaders(Packet packet) { |
322 //for bug 12883765 |
320 //for bug 12883765 |
323 //retrieve headers which is set in soap message |
321 //retrieve headers which is set in soap message |
324 Headers headerFromPacketProperty = (Headers)packet.invocationProperties.get(MessageContext.HTTP_REQUEST_HEADERS); |
322 Headers packetHeaders = (Headers) packet.invocationProperties.get(HTTP_REQUEST_HEADERS); |
325 //retrieve headers from request context |
323 //retrieve headers from request context |
326 Map<String,List<String>> headerFromOthers =(Map<String,List<String>>) others.get(MessageContext.HTTP_REQUEST_HEADERS); |
324 Map<String, List<String>> myHeaders = (Map<String, List<String>>) asMap().get(HTTP_REQUEST_HEADERS); |
327 if((headerFromPacketProperty != null) && (headerFromOthers != null) ) { |
325 if ((packetHeaders != null) && (myHeaders != null)) { |
328 //update the headers set in soap message with those in request context |
326 //update the headers set in soap message with those in request context |
329 for(String key: headerFromOthers.keySet()) { |
327 for (Entry<String, List<String>> entry : myHeaders.entrySet()) { |
330 if(key!=null && key.trim().length()!=0) { |
328 String key = entry.getKey(); |
331 List<String> valueFromPacketProperty = headerFromPacketProperty.get(key); |
329 if (key != null && key.trim().length() != 0) { |
332 //if the two headers contain the same key, combine the value |
330 List<String> listFromPacket = packetHeaders.get(key); |
333 if(valueFromPacketProperty!=null) { |
331 //if the two headers contain the same key, combine the value |
334 valueFromPacketProperty.addAll(headerFromOthers.get(key)); |
332 if (listFromPacket != null) { |
335 }else{ |
333 listFromPacket.addAll(entry.getValue()); |
336 //add the headers in request context to those set in soap message |
334 } else { |
337 headerFromPacketProperty.put(key, headerFromOthers.get(key)); |
335 //add the headers in request context to those set in soap message |
338 } |
336 packetHeaders.put(key, myHeaders.get(key)); |
339 } |
|
340 } |
337 } |
341 // update headers in request context with those set in soap message since 'others' may contain other properties.. |
|
342 others.put(MessageContext.HTTP_REQUEST_HEADERS, headerFromPacketProperty); |
|
343 } |
338 } |
344 packet.invocationProperties.putAll(others); |
339 } |
345 //if it is not standard property it deafults to Scope.HANDLER |
340 // update headers in request context with those set in soap message since it may contain other properties.. |
346 packet.getHandlerScopePropertyNames(false).addAll(others.keySet()); |
341 asMap().put(HTTP_REQUEST_HEADERS, packetHeaders); |
347 } |
342 } |
348 } else { |
343 } |
349 Set<String> handlerScopePropertyNames = new HashSet<String>(); |
344 |
350 // fallback mode, simply copy map in a slow way |
345 private void fillSOAPAction(Packet packet, boolean isAddressingEnabled) { |
351 for (Entry<String,Object> entry : mapView.fallbackMap.entrySet()) { |
346 final boolean p = packet.packetTakesPriorityOverRequestContext; |
352 String key = entry.getKey(); |
347 final String localSoapAction = p ? packet.soapAction : soapAction; |
353 if(packet.supports(key)) |
348 final Boolean localSoapActionUse = p ? (Boolean) packet.invocationProperties.get(BindingProvider.SOAPACTION_USE_PROPERTY) |
354 packet.put(key,entry.getValue()); |
349 : soapActionUse; |
355 else |
350 |
356 packet.invocationProperties.put(key,entry.getValue()); |
351 //JAX-WS-596: Check the semantics of SOAPACTION_USE_PROPERTY before using the SOAPACTION_URI_PROPERTY for |
357 |
352 // SoapAction as specified in the javadoc of BindingProvider. The spec seems to be little contradicting with |
358 //if it is not standard property it deafults to Scope.HANDLER |
353 // javadoc and says that the use property effects the sending of SOAPAction property. |
359 if(!super.supports(key)) { |
354 // Since the user has the capability to set the value as "" if needed, implement the javadoc behavior. |
360 handlerScopePropertyNames.add(key); |
355 if ((localSoapActionUse != null && localSoapActionUse) || (localSoapActionUse == null && isAddressingEnabled)) { |
361 } |
356 if (localSoapAction != null) { |
362 } |
357 packet.soapAction = localSoapAction; |
363 |
358 } |
364 if(!handlerScopePropertyNames.isEmpty()) |
359 } |
365 packet.getHandlerScopePropertyNames(false).addAll(handlerScopePropertyNames); |
360 |
|
361 if ((!isAddressingEnabled && (localSoapActionUse == null || !localSoapActionUse)) && localSoapAction != null) { |
|
362 LOGGER.warning("BindingProvider.SOAPACTION_URI_PROPERTY is set in the RequestContext but is ineffective," + |
|
363 " Either set BindingProvider.SOAPACTION_USE_PROPERTY to true or enable AddressingFeature"); |
366 } |
364 } |
367 } |
365 } |
368 |
366 |
369 public RequestContext copy() { |
367 public RequestContext copy() { |
370 return new RequestContext(this); |
368 return new RequestContext(this); |
371 } |
369 } |
372 |
370 |
373 private final class MapView implements Map<String,Object> { |
371 @Override |
374 private Map<String,Object> fallbackMap; |
|
375 |
|
376 private Map<String,Object> fallback() { |
|
377 if(fallbackMap==null) { |
|
378 // has to fall back. fill in fallbackMap |
|
379 fallbackMap = new HashMap<String,Object>(others); |
|
380 // then put all known properties |
|
381 fallbackMap.putAll(createMapView()); |
|
382 } |
|
383 return fallbackMap; |
|
384 } |
|
385 |
|
386 public int size() { |
|
387 return fallback().size(); |
|
388 } |
|
389 |
|
390 public boolean isEmpty() { |
|
391 return fallback().isEmpty(); |
|
392 } |
|
393 |
|
394 public boolean containsKey(Object key) { |
|
395 return fallback().containsKey(key); |
|
396 } |
|
397 |
|
398 public boolean containsValue(Object value) { |
|
399 return fallback().containsValue(value); |
|
400 } |
|
401 |
|
402 public Object get(Object key) { |
|
403 if (fallbackMap ==null) { |
|
404 return RequestContext.this.get(key); |
|
405 } else { |
|
406 return fallback().get(key); |
|
407 } |
|
408 } |
|
409 |
|
410 public Object put(String key, Object value) { |
|
411 if(fallbackMap ==null) |
|
412 return RequestContext.this.put(key,value); |
|
413 else |
|
414 return fallback().put(key, value); |
|
415 } |
|
416 |
|
417 public Object remove(Object key) { |
|
418 if (fallbackMap ==null) { |
|
419 return RequestContext.this.remove(key); |
|
420 } else { |
|
421 return fallback().remove(key); |
|
422 } |
|
423 } |
|
424 |
|
425 public void putAll(Map<? extends String, ? extends Object> t) { |
|
426 for (Entry<? extends String, ? extends Object> e : t.entrySet()) { |
|
427 put(e.getKey(),e.getValue()); |
|
428 } |
|
429 } |
|
430 |
|
431 public void clear() { |
|
432 fallback().clear(); |
|
433 } |
|
434 |
|
435 public Set<String> keySet() { |
|
436 return fallback().keySet(); |
|
437 } |
|
438 |
|
439 public Collection<Object> values() { |
|
440 return fallback().values(); |
|
441 } |
|
442 |
|
443 public Set<Entry<String, Object>> entrySet() { |
|
444 return fallback().entrySet(); |
|
445 } |
|
446 } |
|
447 |
|
448 protected PropertyMap getPropertyMap() { |
372 protected PropertyMap getPropertyMap() { |
449 return propMap; |
373 return propMap; |
450 } |
374 } |
451 |
375 |
452 private static final PropertyMap propMap = parse(RequestContext.class); |
376 private static final PropertyMap propMap = parse(RequestContext.class); |
|
377 |
|
378 @Override |
|
379 protected boolean mapAllowsAdditionalProperties() { |
|
380 return true; |
|
381 } |
453 } |
382 } |