xref: /trunk/main/connectivity/java/sdbc_jdbc/src/main/java/com/sun/star/comp/sdbc/Tools.java (revision bb3a5a855831b861943c55afcadf2a8cf42eff01)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 package com.sun.star.comp.sdbc;
22 
23 import java.io.UnsupportedEncodingException;
24 import java.net.MalformedURLException;
25 import java.net.URL;
26 import java.net.URLDecoder;
27 import java.util.Collection;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30 
31 import org.apache.openoffice.comp.sdbc.dbtools.comphelper.ResourceBasedEventLogger;
32 import org.apache.openoffice.comp.sdbc.dbtools.util.StandardSQLState;
33 
34 import com.sun.star.beans.NamedValue;
35 import com.sun.star.beans.PropertyValue;
36 import com.sun.star.lang.IllegalArgumentException;
37 import com.sun.star.logging.LogLevel;
38 import com.sun.star.sdbc.SQLException;
39 import com.sun.star.uno.Any;
40 import com.sun.star.uno.AnyConverter;
41 
42 public class Tools {
43     private static final int MAX_EXCEPTION_NESTING = 8;
44 
45     private static final Logger LOGGER = Logger.getLogger(Tools.class.getName());
46 
toUnoException(Object source, Throwable throwable)47     public static SQLException toUnoException(Object source, Throwable throwable) {
48         return toUnoException(source, throwable, 0);
49     }
50 
toUnoException(Object source, Throwable throwable, int nesting)51     private static SQLException toUnoException(Object source, Throwable throwable, int nesting) {
52         // FIXME: use SQLException.getNextException() instead of getCause()?
53         // There are up to 3 dimensions of exception chaining of warnings in Java,
54         // getCause(), getNextException(), and getNextWarning().
55         // The C++ implementation used getNextException() only,
56         // but I am using the widely used and more helpful getCause().
57         Throwable cause = throwable.getCause();
58         Object unoCause = Any.VOID;
59         // Avoid loops and limit recursion to prevent stack overflows:
60         if (cause != null && cause != throwable && nesting < MAX_EXCEPTION_NESTING) {
61             unoCause = toUnoException(source, throwable, nesting + 1);
62         }
63         if (throwable instanceof SQLException) {
64             return (SQLException)throwable;
65         } else if (throwable instanceof java.sql.SQLException) {
66             java.sql.SQLException sqlException = (java.sql.SQLException) throwable;
67             return new SQLException(sqlException.getMessage(), source,
68                     sqlException.getSQLState(), sqlException.getErrorCode(), unoCause);
69         } else if (throwable instanceof com.sun.star.uno.Exception) {
70             // General UNO exception. Wrap in an SQLException and rethrow:
71             com.sun.star.uno.Exception exception = (com.sun.star.uno.Exception) throwable;
72             return new SQLException(exception.getMessage(), source, StandardSQLState.SQL_GENERAL_ERROR.text(),
73                     0, exception);
74         } else {
75             // General Java exception. We can't pass this to UNO, so convert it to an UNO SQLException:
76             String message = throwable.getMessage();
77             if (message.isEmpty()) {
78                 message = throwable.getLocalizedMessage();
79             }
80             if (message.isEmpty()) {
81                 message = throwable.toString();
82             }
83             return new SQLException(message, source, "", -1, unoCause);
84         }
85     }
86 
toUnoExceptionLogged(Object source, ResourceBasedEventLogger logger, Throwable throwable)87     public static SQLException toUnoExceptionLogged(Object source, ResourceBasedEventLogger logger, Throwable throwable) {
88 
89         SQLException exception = toUnoException(source, throwable);
90         logger.log(LogLevel.SEVERE, exception);
91         return exception;
92     }
93 
getOrDefault(PropertyValue[] properties, String name, String defaultValue)94     public static String getOrDefault(PropertyValue[] properties, String name, String defaultValue) throws IllegalArgumentException {
95         String ret = defaultValue;
96         for (PropertyValue property : properties) {
97             if (property.Name.equals(name)) {
98                 ret = AnyConverter.toString(property.Value);
99                 break;
100             }
101         }
102         return ret;
103     }
104 
getOrDefault(PropertyValue[] properties, String name, boolean defaultValue)105     public static boolean getOrDefault(PropertyValue[] properties, String name, boolean defaultValue) throws IllegalArgumentException {
106         boolean ret = defaultValue;
107         for (PropertyValue property : properties) {
108             if (property.Name.equals(name)) {
109                 ret = AnyConverter.toBoolean(property.Value);
110                 break;
111             }
112         }
113         return ret;
114     }
115 
getOrDefault(PropertyValue[] properties, String name, Object defaultValue)116     public static Object getOrDefault(PropertyValue[] properties, String name, Object defaultValue) throws IllegalArgumentException {
117         Object ret = defaultValue;
118         for (PropertyValue property : properties) {
119             if (property.Name.equals(name)) {
120                 ret = property.Value;
121                 break;
122             }
123         }
124         return ret;
125     }
126 
getOrDefault(PropertyValue[] properties, String name, NamedValue[] defaultValue)127     public static NamedValue[] getOrDefault(PropertyValue[] properties, String name, NamedValue[] defaultValue) throws IllegalArgumentException {
128         NamedValue[] ret = defaultValue;
129         for (PropertyValue property : properties) {
130             if (property.Name.equals(name)) {
131                 Object[] array = (Object[]) AnyConverter.toArray(property.Value);
132                 ret = new NamedValue[array.length];
133                 for (int i = 0; i < array.length; i++) {
134                     ret[i] = (NamedValue) array[i];
135                 }
136                 break;
137             }
138         }
139         return ret;
140     }
141 
142     /**
143      * Appends a class path entry to the list of URLs used to build a class loader.
144      *
145      * <p>Only a file: entry naming a path on this machine is added.
146      * A malformed or non-local entry is logged and skipped.</p>
147      *
148      * @param urls the list of class path URLs to append to
149      * @param url  the class path entry to parse and validate
150      */
addClassPathURL(Collection<URL> urls, String url)151     public static void addClassPathURL(Collection<URL> urls, String url) {
152         URL javaURL;
153         try {
154             javaURL = new URL(url);
155         } catch (MalformedURLException e) {
156             LOGGER.log(Level.WARNING, e, () -> "Skipping malformed class path entry: " + url);
157             return;
158         }
159         if (isLocalFileUrl(javaURL)) {
160             LOGGER.fine(() -> "Adding class path entry: " + url);
161             urls.add(javaURL);
162         } else {
163             LOGGER.warning(() -> "Skipping non-local class path entry: " + url);
164         }
165     }
166 
167     /**
168      * Tells whether a class path entry is a file: URL naming a path on this machine.
169      *
170      * <p>jvmaccess/source/classpath.cxx enforces the same check in C++; keep the two in sync.</p>
171      *
172      * @param url the class path entry
173      * @return whether the URL names a local path
174      */
isLocalFileUrl(URL url)175     private static boolean isLocalFileUrl(URL url) {
176         if (!"file".equals(url.getProtocol())) {
177             return false;
178         }
179         // The authority, if any, must be empty or localhost.
180         String host = url.getHost();
181         if (host != null && !host.isEmpty() && !"localhost".equalsIgnoreCase(host)) {
182             return false;
183         }
184         // The decoded path must start with exactly one slash,
185         // which also rules out a path without a leading slash,
186         // and must contain no backslash at all.
187         String path;
188         try {
189             // URLDecoder decodes form data, so a literal '+' is escaped to keep it a '+'.
190             path = URLDecoder.decode(url.getPath().replace("+", "%2B"), "UTF-8");
191         } catch (UnsupportedEncodingException | java.lang.IllegalArgumentException e) {
192             // A malformed escape, which the file: handler cannot decode either.
193             return false;
194         }
195         return path.length() >= 2 && path.charAt(0) == '/' && path.charAt(1) != '/'
196                 && path.indexOf('\\') == -1;
197     }
198 }
199