Tue, 09 Apr 2013 14:51:13 +0100
8010393: Update JAX-WS RI to 2.2.9-b12941
Reviewed-by: alanb, erikj
Contributed-by: miroslav.kos@oracle.com, martin.grebac@oracle.com
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
5 *
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common Development
8 * and Distribution License("CDDL") (collectively, the "License"). You
9 * may not use this file except in compliance with the License. You can
10 * obtain a copy of the License at
11 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
12 * or packager/legal/LICENSE.txt. See the License for the specific
13 * language governing permissions and limitations under the License.
14 *
15 * When distributing the software, include this License Header Notice in each
16 * file and include the License file at packager/legal/LICENSE.txt.
17 *
18 * GPL Classpath Exception:
19 * Oracle designates this particular file as subject to the "Classpath"
20 * exception as provided by Oracle in the GPL Version 2 section of the License
21 * file that accompanied this code.
22 *
23 * Modifications:
24 * If applicable, add the following below the License Header, with the fields
25 * enclosed by brackets [] replaced by your own identifying information:
26 * "Portions Copyright [year] [name of copyright owner]"
27 *
28 * Contributor(s):
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
41 package com.oracle.webservices.internal.api.message;
43 import com.sun.istack.internal.NotNull;
44 import com.sun.istack.internal.Nullable;
46 import java.lang.reflect.Field;
47 import java.lang.reflect.InvocationTargetException;
48 import java.lang.reflect.Method;
49 import java.security.AccessController;
50 import java.security.PrivilegedAction;
51 import java.util.AbstractMap;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.Map;
55 import java.util.Map.Entry;
56 import java.util.Set;
59 /**
60 * A set of "properties" that can be accessed via strongly-typed fields
61 * as well as reflexibly through the property name.
62 *
63 * @author Kohsuke Kawaguchi
64 */
65 @SuppressWarnings("SuspiciousMethodCalls")
66 public abstract class BasePropertySet implements PropertySet {
68 /**
69 * Creates a new instance of TypedMap.
70 */
71 protected BasePropertySet() {
72 }
74 private Map<String,Object> mapView;
76 /**
77 * Represents the list of strongly-typed known properties
78 * (keyed by property names.)
79 *
80 * <p>
81 * Just giving it an alias to make the use of this class more fool-proof.
82 */
83 protected static class PropertyMap extends HashMap<String,Accessor> {
85 // the entries are often being iterated through so performance can be improved
86 // by their caching instead of iterating through the original (immutable) map each time
87 transient PropertyMapEntry[] cachedEntries = null;
89 PropertyMapEntry[] getPropertyMapEntries() {
90 if (cachedEntries == null) {
91 cachedEntries = createPropertyMapEntries();
92 }
93 return cachedEntries;
94 }
96 private PropertyMapEntry[] createPropertyMapEntries() {
97 final PropertyMapEntry[] modelEntries = new PropertyMapEntry[size()];
98 int i = 0;
99 for (final Entry<String, Accessor> e : entrySet()) {
100 modelEntries[i++] = new PropertyMapEntry(e.getKey(), e.getValue());
101 }
102 return modelEntries;
103 }
105 }
107 /**
108 * PropertyMapEntry represents a Map.Entry in the PropertyMap with more efficient access.
109 */
110 static public class PropertyMapEntry {
111 public PropertyMapEntry(String k, Accessor v) {
112 key = k; value = v;
113 }
114 String key;
115 Accessor value;
116 }
118 /**
119 * Map representing the Fields and Methods annotated with {@link PropertySet.Property}.
120 * Model of {@link PropertySet} class.
121 *
122 * <p>
123 * At the end of the derivation chain this method just needs to be implemented
124 * as:
125 *
126 * <pre>
127 * private static final PropertyMap model;
128 * static {
129 * model = parse(MyDerivedClass.class);
130 * }
131 * protected PropertyMap getPropertyMap() {
132 * return model;
133 * }
134 * </pre>
135 */
136 protected abstract PropertyMap getPropertyMap();
138 /**
139 * This method parses a class for fields and methods with {@link PropertySet.Property}.
140 */
141 protected static PropertyMap parse(final Class clazz) {
142 // make all relevant fields and methods accessible.
143 // this allows runtime to skip the security check, so they runs faster.
144 return AccessController.doPrivileged(new PrivilegedAction<PropertyMap>() {
145 @Override
146 public PropertyMap run() {
147 PropertyMap props = new PropertyMap();
148 for (Class c=clazz; c!=null; c=c.getSuperclass()) {
149 for (Field f : c.getDeclaredFields()) {
150 Property cp = f.getAnnotation(Property.class);
151 if(cp!=null) {
152 for(String value : cp.value()) {
153 props.put(value, new FieldAccessor(f, value));
154 }
155 }
156 }
157 for (Method m : c.getDeclaredMethods()) {
158 Property cp = m.getAnnotation(Property.class);
159 if(cp!=null) {
160 String name = m.getName();
161 assert name.startsWith("get") || name.startsWith("is");
163 String setName = name.startsWith("is") ? "set"+name.substring(2) : // isFoo -> setFoo
164 's' +name.substring(1); // getFoo -> setFoo
165 Method setter;
166 try {
167 setter = clazz.getMethod(setName,m.getReturnType());
168 } catch (NoSuchMethodException e) {
169 setter = null; // no setter
170 }
171 for(String value : cp.value()) {
172 props.put(value, new MethodAccessor(m, setter, value));
173 }
174 }
175 }
176 }
178 return props;
179 }
180 });
181 }
183 /**
184 * Represents a typed property defined on a {@link PropertySet}.
185 */
186 protected interface Accessor {
187 String getName();
188 boolean hasValue(PropertySet props);
189 Object get(PropertySet props);
190 void set(PropertySet props, Object value);
191 }
193 static final class FieldAccessor implements Accessor {
194 /**
195 * Field with the annotation.
196 */
197 private final Field f;
199 /**
200 * One of the values in {@link Property} annotation on {@link #f}.
201 */
202 private final String name;
204 protected FieldAccessor(Field f, String name) {
205 this.f = f;
206 f.setAccessible(true);
207 this.name = name;
208 }
210 @Override
211 public String getName() {
212 return name;
213 }
215 @Override
216 public boolean hasValue(PropertySet props) {
217 return get(props)!=null;
218 }
220 @Override
221 public Object get(PropertySet props) {
222 try {
223 return f.get(props);
224 } catch (IllegalAccessException e) {
225 throw new AssertionError();
226 }
227 }
229 @Override
230 public void set(PropertySet props, Object value) {
231 try {
232 f.set(props,value);
233 } catch (IllegalAccessException e) {
234 throw new AssertionError();
235 }
236 }
237 }
239 static final class MethodAccessor implements Accessor {
240 /**
241 * Getter method.
242 */
243 private final @NotNull Method getter;
244 /**
245 * Setter method.
246 * Some property is read-only.
247 */
248 private final @Nullable Method setter;
250 /**
251 * One of the values in {@link Property} annotation on {@link #getter}.
252 */
253 private final String name;
255 protected MethodAccessor(Method getter, Method setter, String value) {
256 this.getter = getter;
257 this.setter = setter;
258 this.name = value;
259 getter.setAccessible(true);
260 if (setter!=null) {
261 setter.setAccessible(true);
262 }
263 }
265 @Override
266 public String getName() {
267 return name;
268 }
270 @Override
271 public boolean hasValue(PropertySet props) {
272 return get(props)!=null;
273 }
275 @Override
276 public Object get(PropertySet props) {
277 try {
278 return getter.invoke(props);
279 } catch (IllegalAccessException e) {
280 throw new AssertionError();
281 } catch (InvocationTargetException e) {
282 handle(e);
283 return 0; // never reach here
284 }
285 }
287 @Override
288 public void set(PropertySet props, Object value) {
289 if(setter==null) {
290 throw new ReadOnlyPropertyException(getName());
291 }
292 try {
293 setter.invoke(props,value);
294 } catch (IllegalAccessException e) {
295 throw new AssertionError();
296 } catch (InvocationTargetException e) {
297 handle(e);
298 }
299 }
301 /**
302 * Since we don't expect the getter/setter to throw a checked exception,
303 * it should be possible to make the exception propagation transparent.
304 * That's what we are trying to do here.
305 */
306 private Exception handle(InvocationTargetException e) {
307 Throwable t = e.getTargetException();
308 if (t instanceof Error) {
309 throw (Error)t;
310 }
311 if (t instanceof RuntimeException) {
312 throw (RuntimeException)t;
313 }
314 throw new Error(e);
315 }
316 }
319 /**
320 * Class allowing to work with PropertySet object as with a Map; it doesn't only allow to read properties from
321 * the map but also to modify the map in a way it is in sync with original strongly typed fields. It also allows
322 * (if necessary) to store additional properties those can't be found in strongly typed fields.
323 *
324 * @see com.sun.xml.internal.ws.api.PropertySet#asMap() method
325 */
326 final class MapView extends HashMap<String, Object> {
328 // flag if it should allow store also different properties
329 // than the from strongly typed fields
330 boolean extensible;
332 MapView(boolean extensible) {
333 super(getPropertyMap().getPropertyMapEntries().length);
334 this.extensible = extensible;
335 initialize();
336 }
338 public void initialize() {
339 // iterate (cached) array instead of map to speed things up ...
340 PropertyMapEntry[] entries = getPropertyMap().getPropertyMapEntries();
341 for (PropertyMapEntry entry : entries) {
342 super.put(entry.key, entry.value);
343 }
344 }
346 @Override
347 public Object get(Object key) {
348 Object o = super.get(key);
349 if (o instanceof Accessor) {
350 return ((Accessor) o).get(BasePropertySet.this);
351 } else {
352 return o;
353 }
354 }
356 @Override
357 public Set<Entry<String, Object>> entrySet() {
358 Set<Entry<String, Object>> entries = new HashSet<Entry<String, Object>>();
359 for (String key : keySet()) {
360 entries.add(new SimpleImmutableEntry<String, Object>(key, get(key)));
361 }
362 return entries;
363 }
365 @Override
366 public Object put(String key, Object value) {
368 Object o = super.get(key);
369 if (o != null && o instanceof Accessor) {
371 Object oldValue = ((Accessor) o).get(BasePropertySet.this);
372 ((Accessor) o).set(BasePropertySet.this, value);
373 return oldValue;
375 } else {
377 if (extensible) {
378 return super.put(key, value);
379 } else {
380 throw new IllegalStateException("Unknown property [" + key + "] for PropertySet [" +
381 BasePropertySet.this.getClass().getName() + "]");
382 }
383 }
384 }
386 @Override
387 public void clear() {
388 for (String key : keySet()) {
389 remove(key);
390 }
391 }
393 @Override
394 public Object remove(Object key) {
395 Object o;
396 o = super.get(key);
397 if (o instanceof Accessor) {
398 ((Accessor)o).set(BasePropertySet.this, null);
399 }
400 return super.remove(key);
401 }
402 }
404 @Override
405 public boolean containsKey(Object key) {
406 Accessor sp = getPropertyMap().get(key);
407 if (sp != null) {
408 return sp.get(this) != null;
409 }
410 return false;
411 }
413 /**
414 * Gets the name of the property.
415 *
416 * @param key
417 * This field is typed as {@link Object} to follow the {@link Map#get(Object)}
418 * convention, but if anything but {@link String} is passed, this method
419 * just returns null.
420 */
421 @Override
422 public Object get(Object key) {
423 Accessor sp = getPropertyMap().get(key);
424 if (sp != null) {
425 return sp.get(this);
426 }
427 throw new IllegalArgumentException("Undefined property "+key);
428 }
430 /**
431 * Sets a property.
432 *
433 * <h3>Implementation Note</h3>
434 * This method is slow. Code inside JAX-WS should define strongly-typed
435 * fields in this class and access them directly, instead of using this.
436 *
437 * @throws ReadOnlyPropertyException
438 * if the given key is an alias of a strongly-typed field,
439 * and if the name object given is not assignable to the field.
440 *
441 * @see Property
442 */
443 @Override
444 public Object put(String key, Object value) {
445 Accessor sp = getPropertyMap().get(key);
446 if(sp!=null) {
447 Object old = sp.get(this);
448 sp.set(this,value);
449 return old;
450 } else {
451 throw new IllegalArgumentException("Undefined property "+key);
452 }
453 }
455 /**
456 * Checks if this {@link PropertySet} supports a property of the given name.
457 */
458 @Override
459 public boolean supports(Object key) {
460 return getPropertyMap().containsKey(key);
461 }
463 @Override
464 public Object remove(Object key) {
465 Accessor sp = getPropertyMap().get(key);
466 if(sp!=null) {
467 Object old = sp.get(this);
468 sp.set(this,null);
469 return old;
470 } else {
471 throw new IllegalArgumentException("Undefined property "+key);
472 }
473 }
475 /**
476 * Creates a {@link Map} view of this {@link PropertySet}.
477 *
478 * <p>
479 * This map is partially live, in the sense that values you set to it
480 * will be reflected to {@link PropertySet}.
481 *
482 * <p>
483 * However, this map may not pick up changes made
484 * to {@link PropertySet} after the view is created.
485 *
486 * @deprecated use newer implementation {@link PropertySet#asMap()} which produces
487 * readwrite {@link Map}
488 *
489 * @return
490 * always non-null valid instance.
491 */
492 @Deprecated
493 @Override
494 public final Map<String,Object> createMapView() {
495 final Set<Entry<String,Object>> core = new HashSet<Entry<String,Object>>();
496 createEntrySet(core);
498 return new AbstractMap<String, Object>() {
499 @Override
500 public Set<Entry<String,Object>> entrySet() {
501 return core;
502 }
503 };
504 }
506 /**
507 * Creates a modifiable {@link Map} view of this {@link PropertySet}.
508 * <p/>
509 * Changes done on this {@link Map} or on {@link PropertySet} object work in both directions - values made to
510 * {@link Map} are reflected to {@link PropertySet} and changes done using getters/setters on {@link PropertySet}
511 * object are automatically reflected in this {@link Map}.
512 * <p/>
513 * If necessary, it also can hold other values (not present on {@link PropertySet}) -
514 * {@see PropertySet#mapAllowsAdditionalProperties}
515 *
516 * @return always non-null valid instance.
517 */
518 @Override
519 public Map<String, Object> asMap() {
520 if (mapView == null) {
521 mapView = createView();
522 }
523 return mapView;
524 }
526 protected Map<String, Object> createView() {
527 return new MapView(mapAllowsAdditionalProperties());
528 }
530 /**
531 * Used when constructing the {@link MapView} for this object - it controls if the {@link MapView} servers only to
532 * access strongly typed values or allows also different values
533 *
534 * @return true if {@link Map} should allow also properties not defined as strongly typed fields
535 */
536 protected boolean mapAllowsAdditionalProperties() {
537 return false;
538 }
540 protected void createEntrySet(Set<Entry<String,Object>> core) {
541 for (final Entry<String, Accessor> e : getPropertyMap().entrySet()) {
542 core.add(new Entry<String, Object>() {
543 @Override
544 public String getKey() {
545 return e.getKey();
546 }
548 @Override
549 public Object getValue() {
550 return e.getValue().get(BasePropertySet.this);
551 }
553 @Override
554 public Object setValue(Object value) {
555 Accessor acc = e.getValue();
556 Object old = acc.get(BasePropertySet.this);
557 acc.set(BasePropertySet.this,value);
558 return old;
559 }
560 });
561 }
562 }
563 }