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
26package com.sun.xml.internal.ws.policy.privateutil;
27
28import com.sun.xml.internal.ws.policy.PolicyException;
29import java.io.Closeable;
30import java.io.IOException;
31import java.io.UnsupportedEncodingException;
32import java.lang.reflect.InvocationTargetException;
33import java.lang.reflect.Method;
34import java.net.URL;
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.Collection;
38import java.util.Comparator;
39import java.util.LinkedList;
40import java.util.List;
41import java.util.Queue;
42import java.util.logging.Level;
43import javax.xml.namespace.QName;
44import javax.xml.stream.XMLStreamException;
45import 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 */
53public 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}
486