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