This is an automated email from the ASF dual-hosted git repository. dazeydev pushed a commit to branch 2.2.x in repository https://gitbox.apache.org/repos/asf/openjpa.git
The following commit(s) were added to refs/heads/2.2.x by this push: new 4510ac0 OPENJPA-2882: Exception passing javax.persistence.* String values to createEntityManager(Map) 4510ac0 is described below commit 4510ac06a71b2b8714d87550b68777746d56546b Author: Will Dazey <dazeyde...@gmail.com> AuthorDate: Mon Oct 18 12:49:33 2021 -0500 OPENJPA-2882: Exception passing javax.persistence.* String values to createEntityManager(Map) Signed-off-by: Will Dazey <dazeyde...@gmail.com> --- .../org/apache/openjpa/lib/util/StringUtil.java | 457 +++++++++++++++++++++ .../persistence/property/TestEMProperties.java | 81 ++++ .../apache/openjpa/persistence/JPAProperties.java | 21 +- 3 files changed, 554 insertions(+), 5 deletions(-) diff --git a/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/StringUtil.java b/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/StringUtil.java new file mode 100644 index 0000000..a02141e --- /dev/null +++ b/openjpa-lib/src/main/java/org/apache/openjpa/lib/util/StringUtil.java @@ -0,0 +1,457 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.openjpa.lib.util; + +import java.util.ArrayList; +import java.util.List; + + + +public final class StringUtil { + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + + private static final Byte BYTE_ZERO = (byte) 0; + private static final Character CHAR_ZERO = (char) 0; + private static final Double DOUBLE_ZERO = 0.0d; + private static final Float FLOAT_ZERO = 0.0f; + private static final Integer INTEGER_ZERO = 0; + private static final Long LONG_ZERO = 0L; + private static final Short SHORT_ZERO = (short) 0; + + private StringUtil() { + } + + /** + * @return {@code true} if the given string is null or empty. + */ + public static boolean isEmpty(String val) { + return val == null || val.isEmpty(); + } + + public static boolean isNotEmpty(String val) { + return !isEmpty(val); + } + + /** + * <p>Checks if a CharSequence is whitespace, empty ("") or null.</p> + * + * <pre> + * StringUtils.isBlank(null) = true + * StringUtils.isBlank("") = true + * StringUtils.isBlank(" ") = true + * StringUtils.isBlank("bob") = false + * StringUtils.isBlank(" bob ") = false + * </pre> + * + * Ported over from Apache commons-lang3 + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is null, empty or whitespace + */ + public static boolean isBlank(final CharSequence cs) { + int strLen; + if (cs == null || (strLen = cs.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(cs.charAt(i))) { + return false; + } + } + return true; + } + + /** + * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p> + * + * <pre> + * StringUtils.isNotBlank(null) = false + * StringUtils.isNotBlank("") = false + * StringUtils.isNotBlank(" ") = false + * StringUtils.isNotBlank("bob") = true + * StringUtils.isNotBlank(" bob ") = true + * </pre> + * + * Ported over from Apache commons-lang3 + * + * @param cs the CharSequence to check, may be null + * @return {@code true} if the CharSequence is not empty and not null and not whitespace + */ + public static boolean isNotBlank(final CharSequence cs) { + return !isBlank(cs); + } + + + /** + * @param val the string to search in + * @param charToSearchFor the character to search for + * @return {@code true} if the charToSearchFor is contained in the String val + */ + public static boolean contains(String val, char charToSearchFor) { + return val != null && val.indexOf(charToSearchFor) > -1; + } + + + public static boolean equalsIgnoreCase(String str1, String str2) { + if (str1 == null || str2 == null) { + return str1 == str2; + } + else if (str1 == str2) { + return true; + } + + return str1.equalsIgnoreCase(str2); + } + + /** + * Splits the given string on the given token. Follows the semantics + * of the Java 1.4 {@link String#split(String, int)} method, but does + * not treat the given token as a regular expression. + */ + public static String[] split(String str, String token, int max) { + if (str == null || str.length() == 0) { + return EMPTY_STRING_ARRAY; + } + if (token == null || token.length() == 0) { + throw new IllegalArgumentException("token: [" + token + "]"); + } + + // split on token + List<String> ret = new ArrayList<String>(); + int start = 0; + int len = str.length(); + int tlen = token.length(); + + int pos = 0; + while (pos != -1) { + pos = str.indexOf(token, start); + if (pos != -1) { + ret.add(str.substring(start, pos)); + start = pos + tlen; + } + } + if (start < len) { + ret.add(str.substring(start)); + } + else if (start == len) { + ret.add(""); + } + + + // now take max into account; this isn't the most efficient way + // of doing things since we split the maximum number of times + // regardless of the given parameters, but it makes things easy + if (max == 0) { + int size = ret.size(); + // discard any trailing empty splits + while (ret.get(--size).isEmpty()) { + ret.remove(size); + } + } + else if (max > 0 && ret.size() > max) { + // move all splits over max into the last split + StringBuilder sb = new StringBuilder(256); + sb.append(ret.get(max - 1)); + ret.remove(max - 1); + while (ret.size() >= max) { + sb.append(token).append(ret.get(max - 1)); + ret.remove(max - 1); + } + ret.add(sb.toString()); + } + return ret.toArray(new String[ret.size()]); + } + + /** + * Replace all instances of <code>from</code> in <code>str</code> + * with <code>to</code>. + * + * @param str the candidate string to replace + * @param from the token to replace + * @param to the new token + * @return the string with all the replacements made + */ + public static String replace(String str, String from, String to) { + if (from.equals(to)) { + return str; + } + String[] split = split(str, from, Integer.MAX_VALUE); + return join(split, to); + } + + + /** + * Null-safe {@link String#trim()} + */ + public static String trim(final String str) { + return str == null ? null : str.trim(); + } + + /** + * @return the trimmed string str or {@code null} if the trimmed string would be empty. + */ + public static String trimToNull(String str) { + if (str == null || str.isEmpty()) { + return null; + } + str = str.trim(); + if (str.isEmpty()) { + return null; + } + return str; + } + + public static String join(Object[] values, String joinToken) { + if (values == null) { + return null; + } + if (values.length == 0) { + return ""; + } + if (values.length == 1) { + return values[0].toString(); + } + if (joinToken == null) { + joinToken = "null"; // backward compat with commons-lang StringUtils... + } + + StringBuilder sb = new StringBuilder(values.length * (16 + joinToken.length())); + sb.append(values[0]); + for (int i = 1; i < values.length; i++) { + sb.append(joinToken).append(values[i]); + } + return sb.toString(); + } + + + + /** + * Parse the given + * + * @param val value to parse + * @param type the target type of the the parsed value + * @return the converted value + */ + public static <T> T parse(String val, Class<T> type) { + if (type == null) { + throw new NullPointerException("target type must not be null"); + } + + // handle primitives + if (type == byte.class) { + return (T) (val == null ? BYTE_ZERO : Byte.valueOf(val)); + } + if (type == char.class) { + return (T) (val == null ? CHAR_ZERO : parseCharString(val)); + } + if (type == double.class) { + return (T) (val == null ? DOUBLE_ZERO : Double.valueOf(val)); + } + if (type == float.class) { + return (T) (val == null ? FLOAT_ZERO : Float.valueOf(val)); + } + if (type == int.class) { + return (T) (val == null ? INTEGER_ZERO : Integer.valueOf(val)); + } + if (type == long.class) { + return (T) (val == null ? LONG_ZERO : Long.valueOf(val)); + } + if (type == short.class) { + return (T) (val == null ? SHORT_ZERO : Short.valueOf(val)); + } + if (type == boolean.class) { + return (T) (val == null ? Boolean.FALSE : Boolean.valueOf(val)); + } + if (type == void.class) { + throw new IllegalStateException("Cannot parse void type"); + } + + // handle wrapper types + if (type == Byte.class) { + return (T) (val == null ? null : Byte.valueOf(val)); + } + if (type == Character.class) { + return (T) (val == null ? null : parseCharString(val)); + } + if (type == Double.class) { + return (T) (val == null ? null : Double.valueOf(val)); + } + if (type == Float.class) { + return (T) (val == null ? null : Float.valueOf(val)); + } + if (type == Integer.class) { + return (T) (val == null ? null : Integer.valueOf(val)); + } + if (type == Long.class) { + return (T) (val == null ? null : Long.valueOf(val)); + } + if (type == Short.class) { + return (T) (val == null ? null : Short.valueOf(val)); + } + if (type == Boolean.class) { + return (T) (val == null ? null : Boolean.valueOf(val)); + } + + throw new IllegalArgumentException("Unsupported type: " + type.getCanonicalName()); + } + + /** + * <p>Capitalizes a String changing the first letter to title case as + * per {@link Character#toTitleCase(char)}. No other letters are changed.</p> + * + * + * <pre> + * StringUtil.capitalize(null) = null + * StringUtil.capitalize("") = "" + * StringUtil.capitalize("cat") = "Cat" + * StringUtil.capitalize("cAt") = "CAt" + * </pre> + * + * Ported over from Apache commons-lang3 + * + * @param str the String to capitalize, may be null + * @return the capitalized String, {@code null} if null String input + * @see #uncapitalize(String) + */ + public static String capitalize(final String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return str; + } + + final char firstChar = str.charAt(0); + if (Character.isTitleCase(firstChar)) { + // already capitalized + return str; + } + + return new StringBuilder(strLen) + .append(Character.toTitleCase(firstChar)) + .append(str.substring(1)) + .toString(); + } + + /** + * <p>Uncapitalizes a String changing the first letter to title case as + * per {@link Character#toLowerCase(char)}. No other letters are changed.</p> + * + * <pre> + * StringUtil.uncapitalize(null) = null + * StringUtil.uncapitalize("") = "" + * StringUtil.uncapitalize("Cat") = "cat" + * StringUtil.uncapitalize("CAT") = "cAT" + * </pre> + * + * Ported over from Apache commons-lang3 + * + * @param str the String to uncapitalize, may be null + * @return the uncapitalized String, {@code null} if null String input + * @see #capitalize(String) + */ + public static String uncapitalize(final String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return str; + } + + final char firstChar = str.charAt(0); + if (Character.isLowerCase(firstChar)) { + // already uncapitalized + return str; + } + + return new StringBuilder(strLen) + .append(Character.toLowerCase(firstChar)) + .append(str.substring(1)) + .toString(); + } + + public static boolean endsWithIgnoreCase(String str, String suffix) { + if (str == null || suffix == null) { + return str == null && suffix == null; + } + int strlen = str.length(); + if (suffix.length() > strlen) { + return false; + } + + return str.substring(str.length() - suffix.length(), strlen).equalsIgnoreCase(suffix); + } + + + /** + * <p>Strips any of a set of characters from the end of a String.</p> + * + * <p>A {@code null} input String returns {@code null}. + * An empty string ("") input returns the empty string.</p> + * + * <p>If the stripChars String is {@code null}, whitespace is + * stripped as defined by {@link Character#isWhitespace(char)}.</p> + * + * <pre> + * StringUtils.stripEnd(null, *) = null + * StringUtils.stripEnd("", *) = "" + * StringUtils.stripEnd("abc", "") = "abc" + * StringUtils.stripEnd("abc", null) = "abc" + * StringUtils.stripEnd(" abc", null) = " abc" + * StringUtils.stripEnd("abc ", null) = "abc" + * StringUtils.stripEnd(" abc ", null) = " abc" + * StringUtils.stripEnd(" abcyx", "xyz") = " abc" + * StringUtils.stripEnd("120.00", ".0") = "12" + * </pre> + * + * Ported over from Apache commons-lang3 + * + * @param str the String to remove characters from, may be null + * @param stripChars the set of characters to remove, null treated as whitespace + * @return the stripped String, {@code null} if null String input + */ + public static String stripEnd(final String str, final String stripChars) { + int end; + if (str == null || (end = str.length()) == 0) { + return str; + } + + if (stripChars == null) { + while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) { + end--; + } + } else if (stripChars.isEmpty()) { + return str; + } else { + while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != -1) { + end--; + } + } + return str.substring(0, end); + } + + + + private static Character parseCharString(String val) { + if (val.length() == 0) { + return (char) 0; + } + if (val.length() == 1) { + return val.charAt(0); + } + throw new IllegalArgumentException("'" + val + "' is longer than one character."); + } + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/property/TestEMProperties.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/property/TestEMProperties.java new file mode 100644 index 0000000..bcff7b1 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/property/TestEMProperties.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agEmployee_Last_Name to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.property; + +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.EntityManager; + +import org.apache.openjpa.persistence.OpenJPAPersistence; +import org.apache.openjpa.persistence.OpenJPAQuery; +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +/** + * <b>TestEMProperties</b> is used to test various persistence properties set through EntityManager.setProperty() API + * to ensure no errors are thrown. + */ +public class TestEMProperties extends SingleEMFTestCase { + + @Override + public void setUp() { + setUp(EntityContact.class, + EmbeddableAddress.class, + DROP_TABLES, "javax.persistence.query.timeout", 23456); + } + + public void testQueryTimeoutPropertyDefault() { + EntityManager em = emf.createEntityManager(); + + String sql = "select * from EntityContact"; + OpenJPAQuery<?> query = OpenJPAPersistence.cast(em.createNativeQuery(sql)); + assertEquals(23456, query.getFetchPlan().getQueryTimeout()); + + em.clear(); + em.close(); + } + + public void testQueryTimeoutPropertyOnEntityManagerCreation() { + Map<String, Object> properties = new HashMap<String, Object>(); + properties.put("javax.persistence.query.timeout", "12345"); + // Setting a value of type String should convert if possible and not return an error + EntityManager em = emf.createEntityManager(properties); + + String sql = "select * from EntityContact"; + OpenJPAQuery<?> query = OpenJPAPersistence.cast(em.createNativeQuery(sql)); + assertEquals(12345, query.getFetchPlan().getQueryTimeout()); + + em.clear(); + em.close(); + } + + public void testQueryTimeoutPropertySetOnEntityManager() { + EntityManager em = emf.createEntityManager(); + + // Setting a value of type String should convert if possible and not return an error + em.setProperty("javax.persistence.query.timeout", "12345"); + + String sql = "select * from EntityContact"; + OpenJPAQuery<?> query = OpenJPAPersistence.cast(em.createNativeQuery(sql)); + assertEquals(12345, query.getFetchPlan().getQueryTimeout()); + + em.clear(); + em.close(); + } +} diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java index 1da06ae..790df37 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java @@ -18,17 +18,18 @@ */ package org.apache.openjpa.persistence; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.HashMap; import java.util.Map; import javax.persistence.CacheRetrieveMode; import javax.persistence.CacheStoreMode; -import javax.persistence.SharedCacheMode; import org.apache.commons.lang.StringUtils; -import org.apache.openjpa.datacache.DataCacheMode; import org.apache.openjpa.kernel.DataCacheRetrieveMode; import org.apache.openjpa.kernel.DataCacheStoreMode; +import org.apache.openjpa.lib.util.StringUtil; /** * Enumerates configuration property keys defined in JPA 2.0 Specification. @@ -119,7 +120,7 @@ public class JPAProperties { } return buf.toString(); } - + /** * Convert the given user value to a value consumable by OpenJPA kernel constructs. * @@ -135,10 +136,20 @@ public class JPAProperties { } else if (value instanceof CacheStoreMode || (value instanceof String && CACHE_STORE_MODE.equals(key))) { return (T)DataCacheStoreMode.valueOf(value.toString().trim().toUpperCase()); } + + // If the value doesn't match the result type, attempt to convert + if(resultType != null && !resultType.isAssignableFrom(value.getClass())) { + if (value instanceof String) { + if ("null".equals(value)) { + return null; + } + return StringUtil.parse((String) value, resultType); + } + } } - return (T)value; + return (T) value; } - + /** * Convert the given kernel value to a value visible to the user. *