Index: test/java/javax/jdo/spi/JDOImplHelperTest.java =================================================================== --- test/java/javax/jdo/spi/JDOImplHelperTest.java (revision 1201431) +++ test/java/javax/jdo/spi/JDOImplHelperTest.java (working copy) @@ -18,7 +18,11 @@ package javax.jdo.spi; import java.util.Collection; +import java.util.Properties; +import javax.jdo.Constants; +import javax.jdo.JDOHelper; +import javax.jdo.JDOUserException; import javax.jdo.pc.PCPoint; import javax.jdo.util.AbstractTest; import javax.jdo.util.BatchTestRunner; @@ -193,6 +197,95 @@ } } + /** + * Test that an unknown standard property causes JDOUserException. + */ + public void testUnknownStandardProperty() { + Properties p = new Properties(); + p.setProperty("javax.jdo.unknown.standard.property", "value"); + + JDOUserException x = null; + + try { + JDOImplHelper.assertOnlyKnownStandardProperties(p); + fail("testUnknownStandardProperty should result in JDOUserException. " + + "No exception was thrown."); + } catch (JDOUserException thrown) { + if (verbose) + println("Caught expected exception " + thrown); + x = thrown; + } + assertNull("should have had no nested exceptions", + x.getNestedExceptions()); + } + + /** + * Test that unknown standard properties cause JDOUserException w/nested + * exceptions. + */ + public void testUnknownStandardProperties() { + Properties p = new Properties(); + p.setProperty("javax.jdo.unknown.standard.property.1", "value"); + p.setProperty("javax.jdo.unknown.standard.property.2", "value"); + + JDOUserException x = null; + + try { + JDOImplHelper.assertOnlyKnownStandardProperties(p); + fail("testUnknownStandardProperties should result in JDOUserException. " + + "No exception was thrown."); + } catch (JDOUserException thrown) { + if (verbose) + println("Caught expected exception " + thrown); + x = thrown; + } + + Throwable[] nesteds = x.getNestedExceptions(); + + assertNotNull(nesteds); + assertEquals("should have been 2 nested exceptions", 2, nesteds.length); + for (int i = 0; i < nesteds.length; i++) { + Throwable t = nesteds[i]; + assertTrue("nested exception " + i + + " should have been JDOUserException", + t instanceof JDOUserException); + } + } + + /** + * Test that unknown non-standard properties & well-formed listener + * properties don't cause JDOUserException. + */ + public void testUnknownNonStandardPropertiesAndListeners() { + Properties p = new Properties(); + p.put("unknown.property", "value"); + p.put(new Object(), "value"); + p.put(Constants.PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER + + "unknown.listener", "value"); + JDOImplHelper.assertOnlyKnownStandardProperties(p); + } + + /** + * Test that all JDO standard properties don't cause JDOUserException. + */ + public void testOnlyStandardProperties() { + Properties props = new Properties(); + for (String p : JDOImplHelper.USER_CONFIGURABLE_STANDARD_PROPERTIES) { + props.setProperty(p, p); + } + JDOImplHelper.assertOnlyKnownStandardProperties(props); + } + + /** + * Test that an known standard property in mixed case succeeds. + */ + public void testKnownStandardPropertyThatDiffersInCaseOnly() { + Properties p = new Properties(); + p.setProperty("JaVaX.jDo.oPtIoN.CoNNectionDRiVerNamE", "value"); + p.setProperty("jAvAx.JdO.lIsTeNeR.InstaNceLifeCycleLisTener.foo.Bar", ""); + JDOImplHelper.assertOnlyKnownStandardProperties(p); + } + /** */ class SimpleListener implements RegisterClassListener { Index: test/java/javax/jdo/JDOHelperTest.java =================================================================== --- test/java/javax/jdo/JDOHelperTest.java (revision 1201431) +++ test/java/javax/jdo/JDOHelperTest.java (working copy) @@ -25,6 +25,7 @@ import java.util.Properties; import javax.jdo.pc.PCPoint; +import javax.jdo.spi.JDOImplHelper; import javax.jdo.util.AbstractTest; import javax.jdo.util.BatchTestRunner; @@ -566,7 +567,55 @@ println("Caught expected exception " + ex); } } + + /** Test that an unknown standard property cause JDOUserException. + */ + public void testUnknownStandardProperty() { + Properties p = new Properties(); + p.setProperty("javax.jdo.unknown.standard.property", "value"); + try { + JDOHelper.getPersistenceManagerFactory(p); + fail("testUnknownStandardProperties should result in JDOUserException. " + + "No exception was thrown."); + } catch (JDOUserException x) { + if (verbose) + println("Caught expected exception " + x); + } + } + /** + * Test that unknown standard properties cause JDOUserException w/nested + * exceptions. + */ + public void testUnknownStandardProperties() { + Properties p = new Properties(); + p.setProperty("javax.jdo.unknown.standard.property.1", "value"); + p.setProperty("javax.jdo.unknown.standard.property.2", "value"); + + JDOUserException x = null; + + try { + JDOHelper.getPersistenceManagerFactory(p); + fail("testUnknownStandardProperties should result in JDOUserException. " + + "No exception was thrown."); + } catch (JDOUserException thrown) { + if (verbose) + println("Caught expected exception " + thrown); + x = thrown; + } + + Throwable[] nesteds = x.getNestedExceptions(); + + assertNotNull(nesteds); + assertEquals("should have been 2 nested exceptions", 2, nesteds.length); + for (int i = 0; i < nesteds.length; i++) { + Throwable t = nesteds[i]; + assertTrue("nested exception " + i + + " should have been JDOUserException", + t instanceof JDOUserException); + } + } + private Context getInitialContext() { try { return new InitialContext(); Index: src/java/javax/jdo/spi/JDOImplHelper.java =================================================================== --- src/java/javax/jdo/spi/JDOImplHelper.java (revision 1201431) +++ src/java/javax/jdo/spi/JDOImplHelper.java (working copy) @@ -44,11 +44,14 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.WeakHashMap; +import javax.jdo.Constants; import javax.jdo.JDOException; import javax.jdo.JDOFatalInternalException; import javax.jdo.JDOFatalUserException; +import javax.jdo.JDOHelper; import javax.jdo.JDOUserException; import javax.xml.parsers.DocumentBuilderFactory; @@ -124,6 +127,68 @@ */ private static ErrorHandler errorHandler; + /** + * JDO standard properties that the user can configure. + */ + public static final Set USER_CONFIGURABLE_STANDARD_PROPERTIES = createUserConfigurableStandardProperties(); + + private static Set createUserConfigurableStandardProperties() { + Set props = new HashSet(); + + props.add(Constants.PROPERTY_CONNECTION_DRIVER_NAME); + props.add(Constants.PROPERTY_CONNECTION_FACTORY2_NAME); + props.add(Constants.PROPERTY_CONNECTION_FACTORY_NAME); + props.add(Constants.PROPERTY_CONNECTION_PASSWORD); + props.add(Constants.PROPERTY_CONNECTION_URL); + props.add(Constants.PROPERTY_CONNECTION_USER_NAME); + props.add(Constants.PROPERTY_COPY_ON_ATTACH); + props.add(Constants.PROPERTY_DATASTORE_READ_TIMEOUT_MILLIS); + props.add(Constants.PROPERTY_DATASTORE_WRITE_TIMEOUT_MILLIS); + props.add(Constants.PROPERTY_DETACH_ALL_ON_COMMIT); + props.add(Constants.PROPERTY_IGNORE_CACHE); + props.add(Constants.PROPERTY_INSTANCE_LIFECYCLE_LISTENER); + props.add(Constants.PROPERTY_MAPPING); + props.add(Constants.PROPERTY_MAPPING_CATALOG); + props.add(Constants.PROPERTY_MAPPING_SCHEMA); + props.add(Constants.PROPERTY_MULTITHREADED); + props.add(Constants.PROPERTY_NAME); + props.add(Constants.PROPERTY_NONTRANSACTIONAL_READ); + props.add(Constants.PROPERTY_NONTRANSACTIONAL_WRITE); + props.add(Constants.PROPERTY_OPTIMISTIC); + props.add(Constants.PROPERTY_PERSISTENCE_MANAGER_FACTORY_CLASS); + props.add(Constants.PROPERTY_PERSISTENCE_UNIT_NAME); + props.add(Constants.PROPERTY_READONLY); + props.add(Constants.PROPERTY_RESTORE_VALUES); + props.add(Constants.PROPERTY_RETAIN_VALUES); + props.add(Constants.PROPERTY_SERVER_TIME_ZONE_ID); + props.add(Constants.PROPERTY_SPI_RESOURCE_NAME); + props.add(Constants.PROPERTY_TRANSACTION_ISOLATION_LEVEL); + props.add(Constants.PROPERTY_TRANSACTION_TYPE); + + return Collections.unmodifiableSet(props); + } + + static final String JAVAX_JDO_PREFIX_LOWER_CASED = + Constants.JAVAX_JDO_PREFIX.toLowerCase(); + + static final String PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER_LOWER_CASED = + Constants.PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER.toLowerCase(); + + static final Set USER_CONFIGURABLE_STANDARD_PROPERTIES_LOWER_CASED = + createUserConfigurableStandardPropertiesLowerCased(); + + static Set createUserConfigurableStandardPropertiesLowerCased() { + Set mixedCased = createUserConfigurableStandardProperties(); + Set lowerCased = + new HashSet(mixedCased.size()); + + for (String propertyName : mixedCased) { + lowerCased.add(propertyName.toLowerCase()); + } + return Collections.unmodifiableSet(lowerCased); + } + + /** Register the default DateFormat instance. */ static { @@ -133,7 +198,7 @@ /** Creates new JDOImplHelper */ private JDOImplHelper() { } - + /** Get an instance of JDOImplHelper. This method * checks that the caller is authorized for * JDOPermission("getMetadata"), and if not, throws @@ -1079,4 +1144,59 @@ */ public Object get(Object pc, StateInterrogation si); } + + /** + * Examines the given map for keys beginning with the JDO standard prefix, + * {@link Constants#JAVAX_JDO_PREFIX}. If any property keys are found with + * that prefix but are unknown to this version of the JDO standard, a + * JDOUserException is thrown with a message indicating the unknown + * property. Keys that are not strings are ignored, as are string keys + * beginning with + * {@link Constants#PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER} or not + * beginning with {@link Constants#JAVAX_JDO_PREFIX}. + * + * @param properties + * The properties to examine. + * + * @see Constants#JAVAX_JDO_PREFIX + * @see JDOHelper#USER_CONFIGURABLE_STANDARD_PROPERTIES + * @since 3.1 + */ + public static void assertOnlyKnownStandardProperties(Map properties) { + if (properties == null || properties.isEmpty()) + return; + + List exceptions = new ArrayList(); + StringBuilder unknowns = new StringBuilder(); + + for (Object key : properties.keySet()) { + if (!(key instanceof String)) { + continue; // ignore keys not of type string + } + String s = ((String) key).toLowerCase(); // compare case-insensitively + if (!s.startsWith(JAVAX_JDO_PREFIX_LOWER_CASED)) { + continue; // ignore vendor-specific keys + } + if (s.startsWith(PROPERTY_PREFIX_INSTANCE_LIFECYCLE_LISTENER_LOWER_CASED)) { + continue; // ignore listener class names + } + if (!USER_CONFIGURABLE_STANDARD_PROPERTIES_LOWER_CASED.contains(s)) { + exceptions.add(new JDOUserException(msg.msg( + "EXC_UnknownStandardProperty", s))); + + if (exceptions.size() > 1) { + unknowns.append(","); + } + unknowns.append(s); + } + } + + if (exceptions.size() == 1) { + throw exceptions.get(0); + } else if (exceptions.size() > 1) { + throw new JDOUserException(msg.msg("EXC_UnknownStandardProperties", + unknowns.toString()), + exceptions.toArray(new JDOUserException[] {})); + } + } } Index: src/java/javax/jdo/Constants.java =================================================================== --- src/java/javax/jdo/Constants.java (revision 1201431) +++ src/java/javax/jdo/Constants.java (working copy) @@ -26,6 +26,17 @@ public interface Constants { /** + * The JDO standard package name. + * @since 3.1 + */ + static String JAVAX_JDO = "javax.jdo"; + + /** + * The JDO standard property string and option string prefix. + */ + static String JAVAX_JDO_PREFIX = JAVAX_JDO + "."; + + /** * The name of the standard service configuration resource text file containing * the name of an implementation of {@link PersistenceManagerFactory}. * Constant value is META-INF/services/javax.jdo.PersistenceManagerFactory. @@ -1068,5 +1079,4 @@ * @since 2.2 */ public static final String TX_SERIALIZABLE = "serializable"; - } Index: src/java/javax/jdo/JDOHelper.java =================================================================== --- src/java/javax/jdo/JDOHelper.java (revision 1201431) +++ src/java/javax/jdo/JDOHelper.java (working copy) @@ -22,14 +22,29 @@ package javax.jdo; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; -import org.xml.sax.ErrorHandler; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.w3c.dom.Node; -import org.w3c.dom.NamedNodeMap; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; import javax.jdo.spi.I18NHelper; import javax.jdo.spi.JDOImplHelper; @@ -45,31 +60,17 @@ import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.Map; -import java.util.HashMap; -import java.util.Collections; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ArrayList; -import java.util.Properties; -import java.util.Enumeration; -import java.io.IOException; -import java.io.InputStream; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + /** * This class can be used by a JDO-aware application to call the JDO behavior * of PersistenceCapable instances without declaring them to be @@ -794,11 +795,15 @@ */ protected static PersistenceManagerFactory getPersistenceManagerFactory (Map overrides, Map props, ClassLoader pmfClassLoader) { + List exceptions = new ArrayList(); if (pmfClassLoader == null) throw new JDOFatalUserException (msg.msg ( "EXC_GetPMFNullLoader")); //NOI18N + JDOImplHelper.assertOnlyKnownStandardProperties(overrides); + JDOImplHelper.assertOnlyKnownStandardProperties(props); + // first try to get the class name from the properties object. String pmfClassName = (String) props.get ( PROPERTY_PERSISTENCE_MANAGER_FACTORY_CLASS); Index: src/java/javax/jdo/Bundle.properties =================================================================== --- src/java/javax/jdo/Bundle.properties (revision 1201431) +++ src/java/javax/jdo/Bundle.properties (working copy) @@ -154,3 +154,5 @@ MSG_EnhancerOutputDirectory=Enhancer processing output directory {0}. ERR_EnhancerBadClassPath=Enhancer cannot construct URL from path {0}. MSG_EnhancerProperty:Enhancer property key:{0} value:{1}. +EXC_UnknownStandardProperty=The property {0} begins with javax.jdo but is not a recognized standard JDO property. +EXC_UnknownStandardProperties=Multiple properties begin with javax.jdo but are not recognized standard JDO properties: {0}