blob: 994285687bc85b27f65e1eb79add027209f4e346 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.xr;
// Javase imports
import static org.eclipse.persistence.internal.helper.ClassConstants.APBYTE;
import static org.eclipse.persistence.internal.helper.ClassConstants.BIGDECIMAL;
import static org.eclipse.persistence.internal.helper.ClassConstants.BIGINTEGER;
import static org.eclipse.persistence.internal.helper.ClassConstants.BOOLEAN;
import static org.eclipse.persistence.internal.helper.ClassConstants.BYTE;
import static org.eclipse.persistence.internal.helper.ClassConstants.CALENDAR;
import static org.eclipse.persistence.internal.helper.ClassConstants.DOUBLE;
import static org.eclipse.persistence.internal.helper.ClassConstants.FLOAT;
import static org.eclipse.persistence.internal.helper.ClassConstants.INTEGER;
import static org.eclipse.persistence.internal.helper.ClassConstants.LONG;
import static org.eclipse.persistence.internal.helper.ClassConstants.SHORT;
import static org.eclipse.persistence.internal.helper.ClassConstants.STRING;
import static org.eclipse.persistence.internal.oxm.Constants.ANY_SIMPLE_TYPE_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.BASE_64_BINARY_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.BOOLEAN_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.BYTE_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.DATE_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.DATE_TIME_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.DECIMAL_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.DOUBLE_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.DURATION_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.FLOAT_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.G_DAY_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.G_MONTH_DAY_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.G_MONTH_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.G_YEAR_MONTH_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.G_YEAR_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.HEX_BINARY_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.INTEGER_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.INT_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.LONG_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.QNAME_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.SHORT_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.STRING_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.TIME_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.UNSIGNED_BYTE_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.UNSIGNED_INT_QNAME;
import static org.eclipse.persistence.internal.oxm.Constants.UNSIGNED_SHORT_QNAME;
import static org.eclipse.persistence.internal.xr.sxf.SimpleXMLFormat.DEFAULT_SIMPLE_XML_FORMAT_TAG;
import java.sql.Types;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
// Java extension libraries
import javax.xml.namespace.QName;
// EclipseLink imports
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
import org.eclipse.persistence.platform.xml.XMLPlatform;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.w3c.dom.Document;
/**
* <p><b>INTERNAL</b>: provides useful constants, SQL Column &lt;-&gt; to XML name mapping and
* a few other misc. features
*
* @author Mike Norman - michael.norman@oracle.com
* @since EclipseLink 1.x
*/
@SuppressWarnings("serial")
public class Util {
public static final XMLPlatform XML_PLATFORM = XMLPlatformFactory.getInstance().getXMLPlatform();
public static final Document TEMP_DOC = XML_PLATFORM.createDocument();
public static final int OPAQUE = 2007;
public static final String DEFAULT_ATTACHMENT_MIMETYPE = "application/octet-stream";
public static final String WEB_INF_DIR = "WEB-INF/";
public static final String WSDL_DIR = "wsdl/";
public static final String[] META_INF_PATHS = { "META-INF/", "/META-INF/" };
public static final String DBWS_SERVICE_XML = "eclipselink-dbws.xml";
public static final String DBWS_OR_LABEL = "dbws-or";
public static final String DBWS_OX_LABEL = "dbws-ox";
public static final String DBWS_OR_XML = "eclipselink-" + DBWS_OR_LABEL + ".xml";
public static final String DBWS_OX_XML = "eclipselink-" + DBWS_OX_LABEL + ".xml";
public static final String DBWS_SCHEMA_XML = "eclipselink-dbws-schema.xsd";
public static final String DBWS_WSDL = "eclipselink-dbws.wsdl";
public static final String DBWS_SESSIONS_XML = "eclipselink-dbws-sessions.xml";
public static final String DBWS_OR_SESSION_NAME_SUFFIX = DBWS_OR_LABEL + "-session";
public static final String DBWS_OX_SESSION_NAME_SUFFIX = DBWS_OX_LABEL + "-session";
public static final String TARGET_NAMESPACE_PREFIX = "ns1";
public static final String SERVICE_NAMESPACE_PREFIX = "srvc";
public static final String SERVICE_SUFFIX = "Service";
public static final String ALL_QUERYNAME = "findAll";
public static final String PK_QUERYNAME = "findByPrimaryKey";
public static final String XMLTYPE_STR = "XMLTYPE";
public static final String DOT_STR = ".";
public static final String UNDERSCORE_STR = "_";
public static final String TYPE_STR = "Type";
public static final String COLLECTION_WRAPPER_STR = "CollectionWrapper";
public static final String DASH_STR = "-";
public static final String EMPTY_STR = "";
public static final char COLON_CHAR = ':';
public static final char SLASH_CHAR = '/';
/**
* Convert a SQL name to a valid XML name. Because not all characters that
* are valid in a SQL name is valid in an XML name, they need to be escaped
* using a special format. See the Oracle paper,"SQL/XML candidate base
* document", for more detail
*
* @param name
* the SQL name
*
* @return the escaped valid XML name
*/
public static String sqlToXmlName(String name) {
int length = name.length();
if (length == 0) {
return name;
}
StringBuilder xmlName = new StringBuilder();
int beginAt = 1;
char firstChar = name.charAt(0);
// escape : to _x003A_
if (firstChar == ':') {
xmlName.append("_x003A_");
}
// escape _ of _x to _x005F_
else if ((length >= 2) && name.substring(0, 2).equals("_x")) {
xmlName.append("_x005F_");
}
// check to see if it is a valid first character
else {
if ((firstChar >= 0xd800) && (firstChar < 0xdc00)) {
// surrogate
if (length > 1) {
xmlName.append(hexEscape((firstChar << 16) | (name.charAt(1) & 0xffff)));
beginAt = 2;
} else {
xmlName.append(hexEscape(firstChar));
}
} else if (isFirstNameChar(firstChar)) {
xmlName.append(firstChar);
} else {
xmlName.append(hexEscape(firstChar));
}
}
// check each following character to see if it is a valid NameChar
char c;
for (int x = beginAt; x < length; x++) {
c = name.charAt(x);
if ((c >= 0xd800) && (c < 0xdc00)) {
// surrogate
if ((x + 1) < length) {
xmlName.append(hexEscape((c << 16) | (name.charAt(x + 1) & 0xffff)));
x++;
} else {
xmlName.append(hexEscape(c));
}
} else if (!isNameChar(c)) {
// escape
xmlName.append(hexEscape(c));
} else {
xmlName.append(c);
}
}
return xmlName.toString();
}
/**
* Convert an escaped XML name back to the original SQL name
*
* @param name
* the escaped XML name
*
* @return the original SQL name
*/
public static String xmlToSqlName(String name) {
String sqlName = "";
int length = name.length();
boolean unescapeMode = false;
String hexString = null;
char c;
// step through each one
for (int x = 0; x < length; x++) {
c = name.charAt(x);
if (unescapeMode) {
// we are in unescape mode
if (((c >= 'A') && (c <= 'F')) || ((c >= '0') && (c <= '9'))) {
// gather the hex string from the escape sequence
hexString = hexString + c;
} else if (c == '_') {
// done with escape mode
unescapeMode = false;
if (hexString != null) {
int i;
int len;
if ((len = hexString.length()) > 4) {
char i1 = (char) (Integer.parseInt(hexString.substring(0, len - 4), 16));
char i2 = (char) (Integer.parseInt(hexString.substring(len - 4), 16));
sqlName += i1;
sqlName += i2;
} else {
try {
i = Integer.parseInt(hexString, 16);
if (i != 0xffff) {
sqlName += (char) i;
}
} catch (NumberFormatException nfe) {
throw new RuntimeException(nfe);
}
}
}
} else {
// invalid char in escape sequence! write everything into
// sqlName as is, or we could throw an exception here
// in the future
sqlName += ("_x" + hexString + c);
unescapeMode = false;
}
} else {
if ((c == '_') && ((x + 1) < length) && (name.charAt(x + 1) == 'x')) {
// found escape beginning _x
// go into unescape mode
unescapeMode = true;
hexString = "";
x++;
} else {
// just copy src char to destination
sqlName += c;
}
}
}
return sqlName;
}
public static String hexEscape(char c) {
String outPutString;
outPutString = Integer.toHexString(c);
switch (outPutString.length()) {
case 1:
outPutString = "_x000" + outPutString.toUpperCase(Locale.US) + "_";
break;
case 2:
outPutString = "_x00" + outPutString.toUpperCase(Locale.US) + "_";
break;
case 3:
outPutString = "_x0" + outPutString.toUpperCase(Locale.US) + "_";
break;
case 4:
outPutString = "_x" + outPutString.toUpperCase(Locale.US) + "_";
break;
}
return outPutString;
}
public static String hexEscape(int c) {
String outPutString;
outPutString = Integer.toHexString(c);
switch (outPutString.length()) {
case 1:
case 5:
outPutString = "_x000" + outPutString.toUpperCase(Locale.US) + "_";
break;
case 2:
case 6:
outPutString = "_x00" + outPutString.toUpperCase(Locale.US) + "_";
break;
case 3:
case 7:
outPutString = "_x0" + outPutString.toUpperCase(Locale.US) + "_";
break;
case 4:
case 8:
outPutString = "_x" + outPutString.toUpperCase(Locale.US) + "_";
break;
}
return outPutString;
}
/**
* return true if character can be part of a name
*
* @param c -
* char to be checked
* @return true/false
*/
public static boolean isNameChar(char c) {
// In most cases the character is less than 256, so this check
// is made fast. For the rest of the characters the check is
// based on the conformance tests. XML 1.1 has changed the list
// of chars allowed, and the check is quite simple. So the
// complete check based on XML 1.0 is not performed.
boolean res;
if (c < 256)
res = (chartype[c] & (FLETTER | FDIGIT | FMISCNAME)) != 0;
else {
// [#x2180-#x2182] | [#x3041-#x3094] | [#x30A1-#x30FA] |
// [#x3105-#x312C] | [#xAC00-#xD7A3]
// [#x0E47-#x0E4E]
if (((c >= 0x2180) && (c <= 0x2182)) || ((c >= 0x3041) && (c <= 0x3094))
|| ((c >= 0x30A1) && (c <= 0x30FA)) || ((c >= 0x3105) && (c <= 0x312C))
|| ((c >= 0xAC00) && (c <= 0xD7A3)) || ((c >= 0x0E47) && (c <= 0x0E4E)))
res = true;
else {
if ((c == 0x02FF) || (c == 0x0346) || (c == 0x0362) || (c == 0x0487)
|| (c == 0x05A2) || (c == 0x05BA) || (c == 0x05BE) || (c == 0x05C0)
|| (c == 0x05C3) || (c == 0x0653) || (c == 0x06B8) || (c == 0x06B9)
|| (c == 0x06E9) || (c == 0x06EE) || (c == 0x0904) || (c == 0x093B)
|| (c == 0x094E) || (c == 0x0955) || (c == 0x0964) || (c == 0x0984)
|| (c == 0x09C5) || (c == 0x09C9) || (c == 0x09CE) || (c == 0x09D8)
|| (c == 0x09E4) || (c == 0x0A03) || (c == 0x0A3D) || (c == 0x0A46)
|| (c == 0x0A49) || (c == 0x0A4E) || (c == 0x0A80) || (c == 0x0A84)
|| (c == 0x0ABB) || (c == 0x0AC6) || (c == 0x0ACA) || (c == 0x0ACE)
|| (c == 0x0B04) || (c == 0x0B3B) || (c == 0x0B44) || (c == 0x0B4A)
|| (c == 0x0B4E) || (c == 0x0B58) || (c == 0x0B84) || (c == 0x0BC3)
|| (c == 0x0BC9) || (c == 0x0BD6) || (c == 0x0C0D) || (c == 0x0C45)
|| (c == 0x0C49) || (c == 0x0C54) || (c == 0x0C81) || (c == 0x0C84)
|| (c == 0x0CC5) || (c == 0x0CC9) || (c == 0x0CD4) || (c == 0x0CD7)
|| (c == 0x0D04) || (c == 0x0D45) || (c == 0x0D49) || (c == 0x0D4E)
|| (c == 0x0D58) || (c == 0x0E3F) || (c == 0x0E3B) || (c == 0x0E4F)
|| (c == 0x0EBA) || (c == 0x0EBE) || (c == 0x0ECE) || (c == 0x0F1A)
|| (c == 0x0F36) || (c == 0x0F38) || (c == 0x0F3B) || (c == 0x0F3A)
|| (c == 0x0F70) || (c == 0x0F85) || (c == 0x0F8C) || (c == 0x0F96)
|| (c == 0x0F98) || (c == 0x0FB0) || (c == 0x0FB8) || (c == 0x0FBA)
|| (c == 0x20DD) || (c == 0x20E2) || (c == 0x3030) || (c == 0x309B)
|| (c == 0x066A) || (c == 0x06FA) || (c == 0x0970) || (c == 0x09F2)
|| (c == 0x0AF0) || (c == 0x0B70) || (c == 0x0C65) || (c == 0x0CE5)
|| (c == 0x0CF0) || (c == 0x0D70) || (c == 0x0E5A) || (c == 0x0EDA)
|| (c == 0x0F2A) || (c == 0x02D2) || (c == 0x03FE) || (c == 0x065F)
|| (c == 0x0E5C) || (c == 0x0C04))
res = false;
else {
// Character.isLetter(c) || Character.isDigit(c) || '-' || '-' || '.'
// is known to be true at this point
res = true;
}
}
}
return res;
}
/**
* return true if character can be part of a name
*
* @param c -
* char to be checked
* @return true/false
*/
public static boolean isFirstNameChar(char c) {
// In most cases the character is less than 256, so this check
// is made fast. For the rest of the characters the check is
// based on the conformance tests. XML 1.1 has changed the list
// of chars allowed, and the check is quite simple. So the
// complete check based on XML 1.0 is not performed.
boolean res;
if (c < 256)
res = (chartype[c] & (FLETTER | FSTARTNAME)) != 0;
else {
// [#x2180-#x2182] | [#x3041-#x3094] | [#x30A1-#x30FA] |
// [#x3105-#x312C] | [#xAC00-#xD7A3]
if (((c >= 0x2180) && (c <= 0x2182)) || (c == 0x3007)
|| ((c >= 0x3021) && (c <= 0x3029)) || ((c >= 0x3041) && (c <= 0x3094))
|| ((c >= 0x30A1) && (c <= 0x30FA)) || ((c >= 0x3105) && (c <= 0x312C))
|| ((c >= 0xAC00) && (c <= 0xD7A3)))
res = true;
else {
if ((c == 0x1101) || (c == 0x1104) || (c == 0x1108) || (c == 0x110A)
|| (c == 0x110D) || (c == 0x113B) || (c == 0x1141) || (c == 0x114D)
|| (c == 0x114F) || (c == 0x1151) || (c == 0x1156) || (c == 0x1162)
|| (c == 0x1164) || (c == 0x1166) || (c == 0x116B) || (c == 0x116F)
|| (c == 0x1174) || (c == 0x119F) || (c == 0x11AC) || (c == 0x11B6)
|| (c == 0x11B9) || (c == 0x11BB) || (c == 0x11C3) || (c == 0x11F1)
|| (c == 0x0132) || (c == 0x0133) || (c == 0x013F) || (c == 0x0140)
|| (c == 0x0149) || (c == 0x017F) || (c == 0x01C4) || (c == 0x01CC)
|| (c == 0x01F1) || (c == 0x01F3) || (c == 0x0E46) || (c == 0x113F)
|| (c == 0x01F6) || (c == 0x01F9) || (c == 0x0230) || (c == 0x03D7)
|| (c == 0x03DD) || (c == 0x03E1) || (c == 0x040D) || (c == 0x0450)
|| (c == 0x045D) || (c == 0x04EC) || (c == 0x04ED) || (c == 0x06B8)
|| (c == 0x06BF) || (c == 0x06CF) || (c == 0x0E2F) || (c == 0x0EAF)
|| (c == 0x0F6A) || (c == 0x4CFF) || (c == 0x212F) || (c == 0x0587)) {
res = false;
} else {
//Character.isLetter(c) || c == '_' is known to be true here
res = true;
}
}
}
return res;
}
/**
* Char type table
*/
static final int chartype[] = new int[256];
static final int FWHITESPACE = 1;
static final int FDIGIT = 2;
static final int FLETTER = 4;
static final int FMISCNAME = 8;
static final int FSTARTNAME = 16;
static {
for (int i = 0; i < 256; i++) {
char c = (char) i;
chartype[i] = 0;
if ((c == 32) || (c == 9) || (c == 13) || (c == 10))
chartype[i] = FWHITESPACE;
if (Character.isLetter(c))
chartype[i] |= FLETTER;
if (Character.isDigit(c))
chartype[i] |= FDIGIT;
}
chartype['.'] |= FMISCNAME;
chartype['-'] |= FMISCNAME;
chartype['_'] |= FMISCNAME | FSTARTNAME;
chartype[0xb7] |= FMISCNAME; // Extender
}
public static Class<?> getClassFromJDBCType(String typeName, DatabasePlatform databasePlatform) {
Class<?> clz = databasePlatform.getClassTypes().get(typeName);
if (clz == null) {
return CoreClassConstants.OBJECT;
}
return clz;
}
public static final QName SXF_QNAME = new QName("", DEFAULT_SIMPLE_XML_FORMAT_TAG);
public static final Map<QName, Class<?>> SCHEMA_2_CLASS;
static {
SCHEMA_2_CLASS = Collections.unmodifiableMap(new HashMap<QName, Class<?>>() {{
put(ANY_SIMPLE_TYPE_QNAME, CoreClassConstants.OBJECT);
put(BASE_64_BINARY_QNAME, APBYTE);
put(BOOLEAN_QNAME, BOOLEAN);
put(BYTE_QNAME, BYTE);
//put(DATE_QNAME, SQLDATE);
put(DATE_QNAME, CALENDAR);
//put(DATE_TIME_QNAME, TIMESTAMP);
put(DATE_TIME_QNAME, CALENDAR);
put(DECIMAL_QNAME, BIGDECIMAL);
put(DOUBLE_QNAME, DOUBLE);
put(DURATION_QNAME, STRING);
put(FLOAT_QNAME, FLOAT);
put(G_YEAR_MONTH_QNAME, STRING);
put(G_YEAR_QNAME, STRING);
put(G_MONTH_QNAME, STRING);
put(G_MONTH_DAY_QNAME, STRING);
put(G_DAY_QNAME, STRING);
put(HEX_BINARY_QNAME, APBYTE);
put(INT_QNAME, INTEGER);
put(INTEGER_QNAME, BIGINTEGER);
put(LONG_QNAME, LONG);
put(QNAME_QNAME, QName.class);
put(SHORT_QNAME, SHORT);
put(STRING_QNAME, STRING);
//put(TIME_QNAME, TIME);
put(TIME_QNAME, CALENDAR);
put(UNSIGNED_BYTE_QNAME, SHORT);
put(UNSIGNED_INT_QNAME, LONG);
put(UNSIGNED_SHORT_QNAME, INTEGER);
}});
}
/**
* Return the type name to be used for a given JDBC type. This will
* typically be used when setting the SQL type and type name on a
* stored function/procedure argument. Currently, the only case
* we need to handle in this manner is oracle.xdb.XMLType - here
* we may set 2007 (OPAQUE) or 2009 (SQLXML).
*
* In the future this method may be required to return more types.
*/
public static String getTypeNameForJDBCType(int jdbcType) {
String typeName = null;
switch (jdbcType) {
case OPAQUE:
case Types.SQLXML:
typeName = XMLTYPE_STR;
break;
default:
break;
}
return typeName;
}
public static int getJDBCTypeForTypeName(String typeName) {
int typeCode = -1;
if (typeName.equals(XMLTYPE_STR)) {
// we currently use oracle.sql.OPAQUE for XMLType
typeCode = OPAQUE;
}
return typeCode;
}
}