You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by da...@apache.org on 2021/10/21 15:55:18 UTC

[openjpa] branch 2.2.x updated: OPENJPA-2882: Exception passing javax.persistence.* String values to createEntityManager(Map)

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 <da...@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 <da...@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.
      *