1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.ws.policy.privateutil;
  27 
  28 import com.sun.xml.internal.ws.policy.PolicyException;
  29 import java.io.Closeable;
  30 import java.io.IOException;
  31 import java.io.UnsupportedEncodingException;
  32 import java.lang.reflect.InvocationTargetException;
  33 import java.lang.reflect.Method;
  34 import java.net.URL;
  35 import java.util.ArrayList;
  36 import java.util.Arrays;
  37 import java.util.Collection;
  38 import java.util.Comparator;
  39 import java.util.LinkedList;
  40 import java.util.List;
  41 import java.util.Queue;
  42 import java.util.logging.Level;
  43 import javax.xml.namespace.QName;
  44 import javax.xml.stream.XMLStreamException;
  45 import javax.xml.stream.XMLStreamReader;
  46 
  47 /**
  48  * This is a wrapper class for various utilities that may be reused within Policy API implementation.
  49  * The class is not part of public Policy API. Do not use it from your client code!
  50  *
  51  * @author Marek Potociar
  52  */
  53 public final class PolicyUtils {
  54     private PolicyUtils() { }
  55 
  56     public static class Commons {
  57         /**
  58          * Method returns the name of the method that is on the {@code methodIndexInStack}
  59          * position in the call stack of the current {@link Thread}.
  60          *
  61          * @param methodIndexInStack index to the call stack to get the method name for.
  62          * @return the name of the method that is on the {@code methodIndexInStack}
  63          *         position in the call stack of the current {@link Thread}.
  64          */
  65         public static String getStackMethodName(final int methodIndexInStack) {
  66             final String methodName;
  67 
  68             final StackTraceElement[] stack = Thread.currentThread().getStackTrace();
  69             if (stack.length > methodIndexInStack + 1) {
  70                 methodName = stack[methodIndexInStack].getMethodName();
  71             } else {
  72                 methodName = "UNKNOWN METHOD";
  73             }
  74 
  75             return methodName;
  76         }
  77 
  78         /**
  79          * Function returns the name of the caller method for the method executing this
  80          * function.
  81          *
  82          * @return caller method name from the call stack of the current {@link Thread}.
  83          */
  84         public static String getCallerMethodName() {
  85             String result = getStackMethodName(5);
  86             if (result.equals("invoke0")) {
  87                 // We are likely running on Mac OS X, which returns a shorter stack trace
  88                 result = getStackMethodName(4);
  89             }
  90             return result;
  91         }
  92     }
  93 
  94     public static class IO {
  95         private static final PolicyLogger LOGGER = PolicyLogger.getLogger(PolicyUtils.IO.class);
  96 
  97         /**
  98          * If the {@code resource} is not {@code null}, this method will try to close the
  99          * {@code resource} instance and log warning about any unexpected
 100          * {@link IOException} that may occur.
 101          *
 102          * @param resource resource to be closed
 103          */
 104         public static void closeResource(Closeable resource) {
 105             if (resource != null) {
 106                 try {
 107                     resource.close();
 108                 } catch (IOException e) {
 109                     LOGGER.warning(LocalizationMessages.WSP_0023_UNEXPECTED_ERROR_WHILE_CLOSING_RESOURCE(resource.toString()), e);
 110                 }
 111             }
 112         }
 113 
 114         /**
 115          * If the {@code reader} is not {@code null}, this method will try to close the
 116          * {@code reader} instance and log warning about any unexpected
 117          * {@link IOException} that may occur.
 118          *
 119          * @param reader resource to be closed
 120          */
 121         public static void closeResource(XMLStreamReader reader) {
 122             if (reader != null) {
 123                 try {
 124                     reader.close();
 125                 } catch (XMLStreamException e) {
 126                     LOGGER.warning(LocalizationMessages.WSP_0023_UNEXPECTED_ERROR_WHILE_CLOSING_RESOURCE(reader.toString()), e);
 127                 }
 128             }
 129         }
 130     }
 131 
 132     /**
 133      * Text utilities wrapper.
 134      */
 135     public static class Text {
 136         /**
 137          * System-specific line separator character retrieved from the Java system property
 138          * <code>line.separator</code>
 139          */
 140         public final static String NEW_LINE = System.getProperty("line.separator");
 141 
 142         /**
 143          * Method creates indent string consisting of as many {@code TAB} characters as specified by {@code indentLevel} parameter
 144          *
 145          * @param indentLevel indentation level
 146          * @return indentation string as specified by indentation level
 147          *
 148          */
 149         public static String createIndent(final int indentLevel) {
 150             final char[] charData = new char[indentLevel * 4];
 151             Arrays.fill(charData, ' ');
 152             return String.valueOf(charData);
 153         }
 154     }
 155 
 156     public static class Comparison {
 157         /**
 158          * The comparator comapres QName objects according to their publicly accessible attributes, in the following
 159          * order of attributes:
 160          *
 161          * 1. namespace (not null String)
 162          * 2. local name (not null String)
 163          */
 164         public static final Comparator<QName> QNAME_COMPARATOR = new Comparator<QName>() {
 165             public int compare(final QName qn1, final QName qn2) {
 166                 if (qn1 == qn2 || qn1.equals(qn2)) {
 167                     return 0;
 168                 }
 169 
 170                 int result;
 171 
 172                 result = qn1.getNamespaceURI().compareTo(qn2.getNamespaceURI());
 173                 if (result != 0) {
 174                     return result;
 175                 }
 176 
 177                 return qn1.getLocalPart().compareTo(qn2.getLocalPart());
 178             }
 179         };
 180 
 181         /**
 182          * Compares two boolean values in the following way: {@code false < true}
 183          *
 184          * @return {@code -1} if {@code b1 < b2}, {@code 0} if {@code b1 == b2}, {@code 1} if {@code b1 > b2}
 185          */
 186         public static int compareBoolean(final boolean b1, final boolean b2) {
 187             final int i1 = (b1) ? 1 : 0;
 188             final int i2 = (b2) ? 1 : 0;
 189 
 190             return i1 - i2;
 191         }
 192 
 193         /**
 194          * Compares two String values, that may possibly be null in the following way: {@code null < "string value"}
 195          *
 196          * @return {@code -1} if {@code s1 < s2}, {@code 0} if {@code s1 == s2}, {@code 1} if {@code s1 > s2}
 197          */
 198         public static int compareNullableStrings(final String s1, final String s2) {
 199             return ((s1 == null) ? ((s2 == null) ? 0 : -1) : ((s2 == null) ? 1 : s1.compareTo(s2)));
 200         }
 201     }
 202 
 203     public static class Collections {
 204         private static final PolicyLogger LOGGER = PolicyLogger.getLogger(PolicyUtils.Collections.class);
 205         /**
 206          * TODO javadocs
 207          *
 208          * @param initialBase the combination base that will be present in each combination. May be {@code null} or empty.
 209          * @param options options that should be combined. May be {@code null} or empty.
 210          * @param ignoreEmptyOption flag identifies whether empty options should be ignored or whether the method should halt
 211          *        processing and return {@code null} when an empty option is encountered
 212          * @return TODO
 213          */
 214         public static <E, T extends Collection<? extends E>, U extends Collection<? extends E>> Collection<Collection<E>> combine(final U initialBase, final Collection<T> options, final boolean ignoreEmptyOption) {
 215             List<Collection<E>> combinations = null;
 216             if (options == null || options.isEmpty()) {
 217                 // no combination creation needed
 218                 if (initialBase != null) {
 219                     combinations = new ArrayList<Collection<E>>(1);
 220                     combinations.add(new ArrayList<E>(initialBase));
 221                 }
 222                 return combinations;
 223             }
 224 
 225             // creating defensive and modifiable copy of the base
 226             final Collection<E> base = new LinkedList<E>();
 227             if (initialBase != null && !initialBase.isEmpty()) {
 228                 base.addAll(initialBase);
 229             }
 230             /**
 231              * now we iterate over all options and build up an option processing queue:
 232              *   1. if ignoreEmptyOption flag is not set and we found an empty option, we are going to stop processing and return null. Otherwise we
 233              *      ignore the empty option.
 234              *   2. if the option has one child only, we add the child directly to the base.
 235              *   3. if there are more children in examined node, we add it to the queue for further processing and precoumpute the final size of
 236              *      resulting collection of combinations.
 237              */
 238             int finalCombinationsSize = 1;
 239             final Queue<T> optionProcessingQueue = new LinkedList<T>();
 240             for (T option : options) {
 241                 final int optionSize =  option.size();
 242 
 243                 if (optionSize == 0) {
 244                     if (!ignoreEmptyOption) {
 245                         return null;
 246                     }
 247                 } else if (optionSize == 1) {
 248                     base.addAll(option);
 249                 } else {
 250                     boolean entered = optionProcessingQueue.offer(option);
 251                     if (!entered) {
 252                         throw LOGGER.logException(new RuntimePolicyUtilsException(LocalizationMessages.WSP_0096_ERROR_WHILE_COMBINE(option)), false, Level.WARNING);
 253                     }
 254                     finalCombinationsSize *= optionSize;
 255                 }
 256             }
 257 
 258             // creating final combinations
 259             combinations = new ArrayList<Collection<E>>(finalCombinationsSize);
 260             combinations.add(base);
 261             if (finalCombinationsSize > 1) {
 262                 T processedOption;
 263                 while ((processedOption = optionProcessingQueue.poll()) != null) {
 264                     final int actualSemiCombinationCollectionSize = combinations.size();
 265                     final int newSemiCombinationCollectionSize = actualSemiCombinationCollectionSize * processedOption.size();
 266 
 267                     int semiCombinationIndex = 0;
 268                     for (E optionElement : processedOption) {
 269                         for (int i = 0; i < actualSemiCombinationCollectionSize; i++) {
 270                             final Collection<E> semiCombination = combinations.get(semiCombinationIndex); // unfinished combination
 271 
 272                             if (semiCombinationIndex + actualSemiCombinationCollectionSize < newSemiCombinationCollectionSize) {
 273                                 // this is not the last optionElement => we create a new combination copy for the next child
 274                                 combinations.add(new LinkedList<E>(semiCombination));
 275                             }
 276 
 277                             semiCombination.add(optionElement);
 278                             semiCombinationIndex++;
 279                         }
 280                     }
 281                 }
 282             }
 283             return combinations;
 284         }
 285     }
 286 
 287     /**
 288      * Reflection utilities wrapper
 289      */
 290     static class Reflection {
 291         private static final PolicyLogger LOGGER = PolicyLogger.getLogger(PolicyUtils.Reflection.class);
 292 
 293         /**
 294          * Reflectively invokes specified method on the specified target
 295          */
 296         static <T> T invoke(final Object target, final String methodName,
 297                 final Class<T> resultClass, final Object... parameters) throws RuntimePolicyUtilsException {
 298             Class[] parameterTypes;
 299             if (parameters != null && parameters.length > 0) {
 300                 parameterTypes = new Class[parameters.length];
 301                 int i = 0;
 302                 for (Object parameter : parameters) {
 303                     parameterTypes[i++] = parameter.getClass();
 304                 }
 305             } else {
 306                 parameterTypes = null;
 307             }
 308 
 309             return invoke(target, methodName, resultClass, parameters, parameterTypes);
 310         }
 311 
 312         /**
 313          * Reflectively invokes specified method on the specified target
 314          */
 315         public static <T> T invoke(final Object target, final String methodName, final Class<T> resultClass,
 316                 final Object[] parameters, final Class[] parameterTypes) throws RuntimePolicyUtilsException {
 317             try {
 318                 final Method method = target.getClass().getMethod(methodName, parameterTypes);
 319                 final Object result = MethodUtil.invoke(target, method,parameters);
 320 
 321                 return resultClass.cast(result);
 322             } catch (IllegalArgumentException e) {
 323                 throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e));
 324             } catch (InvocationTargetException e) {
 325                 throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e));
 326             } catch (IllegalAccessException e) {
 327                 throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e.getCause()));
 328             } catch (SecurityException e) {
 329                 throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e));
 330             } catch (NoSuchMethodException e) {
 331                 throw LOGGER.logSevereException(new RuntimePolicyUtilsException(createExceptionMessage(target, parameters, methodName), e));
 332             }
 333         }
 334 
 335         private static String createExceptionMessage(final Object target, final Object[] parameters, final String methodName) {
 336             return LocalizationMessages.WSP_0061_METHOD_INVOCATION_FAILED(target.getClass().getName(), methodName,
 337                     parameters == null ? null : Arrays.asList(parameters).toString());
 338         }
 339     }
 340 
 341     public static class ConfigFile {
 342         /**
 343          * Generates a config file resource name from provided config file identifier.
 344          * The generated file name can be transformed into a URL instance using
 345          * {@link #loadFromContext(String, Object)} or {@link #loadFromClasspath(String)}
 346          * method.
 347          *
 348          * @param configFileIdentifier the string used to generate the config file URL that will be parsed. Each WSIT config
 349          *        file is in form of <code>wsit-<i>{configFileIdentifier}</i>.xml</code>. Must not be {@code null}.
 350          * @return generated config file resource name
 351          * @throw PolicyException If configFileIdentifier is null.
 352          */
 353         public static String generateFullName(final String configFileIdentifier) throws PolicyException {
 354             if (configFileIdentifier != null) {
 355                 final StringBuffer buffer = new StringBuffer("wsit-");
 356                 buffer.append(configFileIdentifier).append(".xml");
 357                 return buffer.toString();
 358             } else {
 359                 throw new PolicyException(LocalizationMessages.WSP_0080_IMPLEMENTATION_EXPECTED_NOT_NULL());
 360             }
 361         }
 362 
 363         /**
 364          * Returns a URL pointing to the given config file. The file name is
 365          * looked up as a resource from a ServletContext.
 366          *
 367          * May return null if the file can not be found.
 368          *
 369          * @param configFileName The name of the file resource
 370          * @param context A ServletContext object. May not be null.
 371          */
 372         public static URL loadFromContext(final String configFileName, final Object context) {
 373             return Reflection.invoke(context, "getResource", URL.class, configFileName);
 374         }
 375 
 376         /**
 377          * Returns a URL pointing to the given config file. The file is looked up as
 378          * a resource on the classpath.
 379          *
 380          * May return null if the file can not be found.
 381          *
 382          * @param configFileName the name of the file resource. May not be {@code null}.
 383          */
 384         public static URL loadFromClasspath(final String configFileName) {
 385             final ClassLoader cl = Thread.currentThread().getContextClassLoader();
 386             if (cl == null) {
 387                 return ClassLoader.getSystemResource(configFileName);
 388             } else {
 389                 return cl.getResource(configFileName);
 390             }
 391         }
 392     }
 393 
 394     /**
 395      * Wrapper for ServiceFinder class which is not part of the Java SE yet.
 396      */
 397     public static class ServiceProvider {
 398         /**
 399          * Locates and incrementally instantiates the available providers of a
 400          * given service using the given class loader.
 401          * <p/>
 402          * <p> This method transforms the name of the given service class into a
 403          * provider-configuration filename as described above and then uses the
 404          * {@code getResources} method of the given class loader to find all
 405          * available files with that name.  These files are then read and parsed to
 406          * produce a list of provider-class names. Eventually each provider class is
 407          * instantiated and array of those instances is returned.
 408          * <p/>
 409          * <p> Because it is possible for extensions to be installed into a running
 410          * Java virtual machine, this method may return different results each time
 411          * it is invoked. <p>
 412          *
 413          * @param serviceClass The service's abstract service class. Must not be {@code null}.
 414          * @param loader  The class loader to be used to load provider-configuration files
 415          *                and instantiate provider classes, or {@code null} if the system
 416          *                class loader (or, failing that the bootstrap class loader) is to
 417          *                be used
 418          * @throws NullPointerException in case {@code service} input parameter is {@code null}.
 419          * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
 420          *                                   or names a provider class that cannot be found and instantiated
 421          * @see #load(Class)
 422          */
 423         public static <T> T[] load(final Class<T> serviceClass, final ClassLoader loader) {
 424             return ServiceFinder.find(serviceClass, loader).toArray();
 425         }
 426 
 427         /**
 428          * Locates and incrementally instantiates the available providers of a
 429          * given service using the context class loader.  This convenience method
 430          * is equivalent to
 431          * <p/>
 432          * <pre>
 433          *   ClassLoader cl = Thread.currentThread().getContextClassLoader();
 434          *   return PolicyUtils.ServiceProvider.load(service, cl);
 435          * </pre>
 436          *
 437          * @param serviceClass The service's abstract service class. Must not be {@code null}.
 438          *
 439          * @throws NullPointerException in case {@code service} input parameter is {@code null}.
 440          * @throws ServiceConfigurationError If a provider-configuration file violates the specified format
 441          *                                   or names a provider class that cannot be found and instantiated
 442          * @see #load(Class, ClassLoader)
 443          */
 444         public static <T> T[] load(final Class<T> serviceClass) {
 445             return ServiceFinder.find(serviceClass).toArray();
 446         }
 447     }
 448 
 449     public static class Rfc2396 {
 450 
 451         private static final PolicyLogger LOGGER = PolicyLogger.getLogger(PolicyUtils.Reflection.class);
 452 
 453         // converts "hello%20world" into "hello world"
 454         public static String unquote(final String quoted) {
 455             if (null == quoted) {
 456                 return null;
 457             }
 458             final byte[] unquoted = new byte[quoted.length()]; // result cannot be longer than original string
 459             int newLength = 0;
 460             char c;
 461             int hi, lo;
 462             for (int i=0; i < quoted.length(); i++) {    // iterarate over all chars in the input
 463                 c = quoted.charAt(i);
 464                 if ('%' == c) {                         // next escape sequence found
 465                     if ((i + 2) >= quoted.length()) {
 466                         throw LOGGER.logSevereException(new RuntimePolicyUtilsException(LocalizationMessages.WSP_0079_ERROR_WHILE_RFC_2396_UNESCAPING(quoted)), false);
 467                     }
 468                     hi = Character.digit(quoted.charAt(++i), 16);
 469                     lo = Character.digit(quoted.charAt(++i), 16);
 470                     if ((0 > hi) || (0 > lo)) {
 471                         throw LOGGER.logSevereException(new RuntimePolicyUtilsException(LocalizationMessages.WSP_0079_ERROR_WHILE_RFC_2396_UNESCAPING(quoted)), false);
 472                     }
 473                     unquoted[newLength++] = (byte) (hi * 16 + lo);
 474                 } else { // regular character found
 475                     unquoted[newLength++] = (byte) c;
 476                 }
 477             }
 478             try {
 479                 return new String(unquoted, 0, newLength, "utf-8");
 480             } catch (UnsupportedEncodingException uee) {
 481                 throw LOGGER.logSevereException(new RuntimePolicyUtilsException(LocalizationMessages.WSP_0079_ERROR_WHILE_RFC_2396_UNESCAPING(quoted), uee));
 482             }
 483         }
 484     }
 485 }