openjpa-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ppod...@apache.org
Subject svn commit: r832874 - in /openjpa/trunk: openjpa-kernel/src/main/java/org/apache/openjpa/conf/ openjpa-kernel/src/main/java/org/apache/openjpa/datacache/ openjpa-kernel/src/main/java/org/apache/openjpa/enhance/ openjpa-kernel/src/main/resources/org/apa...
Date Wed, 04 Nov 2009 21:18:39 GMT
Author: ppoddar
Date: Wed Nov  4 21:18:38 2009
New Revision: 832874

URL: http://svn.apache.org/viewvc?rev=832874&view=rev
Log:
OPENJPA-1334: Refresh should ignore cache.retrieve.mode settings. Assorted changes in property
processing for usability 

Modified:
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/datacache/DataCacheStoreManager.java
    openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/Reflection.java
    openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/enhance/localizer.properties
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java
    openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestRefresh.java
    openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
    openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java
    openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceProductDerivation.java
    openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceUnitInfoImpl.java
    openjpa/trunk/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java
(original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/conf/OpenJPAConfiguration.java
Wed Nov  4 21:18:38 2009
@@ -374,6 +374,8 @@
      * The entities are never refreshed from DataCache if lock is being applied 
      * (e.g. in a pessimistic transaction) and hence this setting only refers 
      * to behavior when not locking.
+     * This flag can be used to overwrite RetrieveMode.BYPASS.
+     * By default, however, this falg is false. 
      * 
      * @since 1.2.0
      */

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/datacache/DataCacheStoreManager.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/datacache/DataCacheStoreManager.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/datacache/DataCacheStoreManager.java
(original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/datacache/DataCacheStoreManager.java
Wed Nov  4 21:18:38 2009
@@ -387,8 +387,7 @@
     public boolean load(OpenJPAStateManager sm, BitSet fields,
         FetchConfiguration fetch, int lockLevel, Object edata) {
         DataCache cache = _mgr.selectCache(sm);
-        if (cache == null || sm.isEmbedded() 
-            || _ctx.getFetchConfiguration().getCacheRetrieveMode() == DataCacheRetrieveMode.BYPASS)
+        if (cache == null || sm.isEmbedded() || bypass(_ctx.getFetchConfiguration(), StoreManager.FORCE_LOAD_NONE))
             return super.load(sm, fields, fetch, lockLevel, edata);
 
         DataCachePCData data = cache.get(sm.getObjectId());
@@ -410,11 +409,8 @@
 
     public Collection<Object> loadAll(Collection<OpenJPAStateManager> sms, PCState
state, int load,
     		FetchConfiguration fetch, Object edata) {
-    	if (isLocking(fetch) || 
-    	   (!isLocking(fetch) &&
-    		(load == StoreManager.FORCE_LOAD_REFRESH)
-    		&& !_ctx.getConfiguration().getRefreshFromDataCache())) {
-    	       return super.loadAll(sms, state, load, fetch, edata);
+    	if (bypass(fetch, load)) {
+    	    return super.loadAll(sms, state, load, fetch, edata);
     	}
 
         Map<OpenJPAStateManager, BitSet> unloaded = null;
@@ -528,7 +524,7 @@
         }
         return failed;
     }
-
+    
     /**
      * Helper method to add an unloaded instance to the given map.
      */
@@ -691,6 +687,24 @@
     }
 
     /**
+     * Affirms if a load operation must bypass the L2 cache.
+     * If lock is active, always bypass.
+     * 
+     */
+    boolean bypass(FetchConfiguration fetch, int load) {
+        // Order of checks are important
+        if (isLocking(fetch))
+            return true;
+        if (_ctx.getConfiguration().getRefreshFromDataCache()) 
+            return false;
+        if (fetch.getCacheRetrieveMode() == DataCacheRetrieveMode.BYPASS)
+            return true;
+        if (load == StoreManager.FORCE_LOAD_REFRESH)
+            return true;
+        return false;
+    }
+
+    /**
      * Return whether the context is locking loaded data.
      */
     private boolean isLocking(FetchConfiguration fetch) {

Modified: openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/Reflection.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/Reflection.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/Reflection.java
(original)
+++ openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/enhance/Reflection.java
Wed Nov  4 21:18:38 2009
@@ -34,6 +34,7 @@
 import org.apache.openjpa.lib.util.Localizer;
 import org.apache.openjpa.lib.util.ReferenceMap;
 import org.apache.openjpa.lib.util.Reflectable;
+import org.apache.openjpa.lib.util.Localizer.Message;
 import org.apache.openjpa.lib.util.concurrent.ConcurrentReferenceHashMap;
 import org.apache.openjpa.util.GeneralException;
 import org.apache.openjpa.util.UserException;
@@ -282,7 +283,7 @@
         try {
             return field.get(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
     
@@ -327,9 +328,10 @@
     /**
      * Wrap the given reflection exception as a runtime exception.
      */
-    private static RuntimeException wrapReflectionException(Throwable t) {
+    private static RuntimeException wrapReflectionException(Throwable t, Message message)
{
         if (t instanceof InvocationTargetException)
-            t = ((InvocationTargetException) t).getTargetException();    
+            t = ((InvocationTargetException) t).getTargetException();  
+        t.initCause(new IllegalArgumentException(message.getMessage()));
         if (t instanceof RuntimeException)
             return (RuntimeException) t;
         return new GeneralException(t);
@@ -345,7 +347,7 @@
         try {
             return field.getBoolean(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
 
@@ -359,7 +361,7 @@
         try {
             return field.getByte(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
 
@@ -373,7 +375,7 @@
         try {
             return field.getChar(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
 
@@ -387,7 +389,7 @@
         try {
             return field.getDouble(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
 
@@ -401,7 +403,7 @@
         try {
             return field.getFloat(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
 
@@ -415,7 +417,7 @@
         try {
             return field.getInt(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
 
@@ -429,7 +431,7 @@
         try {
             return field.getLong(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
 
@@ -443,7 +445,7 @@
         try {
             return field.getShort(target);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-field", target, field));
         }
     }
 
@@ -457,7 +459,7 @@
         try {
             return getter.invoke(target, (Object[]) null);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("get-method", target, getter));
         }
     }
 
@@ -535,7 +537,8 @@
         try {
             field.set(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, 
+                    value == null ? "" : value.getClass()}));
         }
     }
 
@@ -549,7 +552,7 @@
         try {
             field.setBoolean(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, "boolean"}));
         }
     }
 
@@ -563,7 +566,7 @@
         try {
             field.setByte(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, "byte"}));
         }
     }
 
@@ -577,7 +580,7 @@
         try {
             field.setChar(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, "char"}));
         }
     }
 
@@ -591,7 +594,7 @@
         try {
             field.setDouble(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, "double"}));
         }
     }
 
@@ -605,7 +608,7 @@
         try {
             field.setFloat(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, "float"}));
         }
     }
 
@@ -619,7 +622,7 @@
         try {
             field.setInt(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, "int"}));
         }
     }
 
@@ -633,7 +636,7 @@
         try {
             field.setLong(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, "long"}));
         }
     }
 
@@ -647,7 +650,7 @@
         try {
             field.setShort(target, value);
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-field", new Object[]{target, field,
value, "short"}));
         }
     }
 
@@ -760,7 +763,8 @@
         try {
             setter.invoke(target, new Object[] { value });
         } catch (Throwable t) {
-            throw wrapReflectionException(t);
+            throw wrapReflectionException(t, _loc.get("set-method", new Object[]{target,
setter, value, 
+                    value == null ? "" : value.getClass()}));
         }
     }
 
@@ -831,16 +835,16 @@
      * it is ignored.
      *   
      */
-    public static Set<String> getBeanStylePropertyNames(Class c) {
+    public static Set<String> getBeanStylePropertyNames(Class<?> c) {
         if (c == null)
-            return Collections.EMPTY_SET;
+            return Collections.emptySet();
         Set<String> result = beanPropertiesNameCache.get(c);
         if (result != null) {
             return result;
         }
         Method[] methods = c.getMethods();
         if (methods == null || methods.length < 2)
-            return Collections.EMPTY_SET;
+            return Collections.emptySet();
         result = new TreeSet<String>();
         for (Method m : methods) {
             if (m.getName().startsWith("get")) {
@@ -848,9 +852,9 @@
                     continue;
                 String prop = StringUtils.capitalize(m.getName()
                     .substring("get".length()));
-                Class rtype = m.getReturnType();
+                Class<?> rtype = m.getReturnType();
                 try {
-                  Method setter = c.getMethod("set"+prop, new Class[]{rtype});
+                  Method setter = c.getMethod("set"+prop, new Class<?>[]{rtype});
                   if (setter.getReturnType() == void.class || 
                       setter.getReturnType().isAssignableFrom(c))
                   result.add(prop);

Modified: openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/enhance/localizer.properties
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/enhance/localizer.properties?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/enhance/localizer.properties
(original)
+++ openjpa/trunk/openjpa-kernel/src/main/resources/org/apache/openjpa/enhance/localizer.properties
Wed Nov  4 21:18:38 2009
@@ -211,4 +211,7 @@
     to creating any Entities.    
 temp-file-creation: The temporary file "{0}" was created and it may not get \
     cleaned up properly.
-    
+get-field: Error while getting value of field {1} from instance {0} by reflection.    
+get-method: Error while getting value by getter method {1} on instance {0} by reflection.
   
+set-field: Error while setting value {2} of {3} on field {1} of instance {0} by reflection.
   
+set-method: Error while setting value {2} of {3} by setter method {1} of instance {0} by
reflection.    

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java
(original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/datacache/TestDataCacheBehavesIdentical.java
Wed Nov  4 21:18:38 2009
@@ -56,9 +56,8 @@
     private static final boolean DIRTY = true;
     private static final boolean REFRESH_FROM_DATACACHE = true;
     private static final LockModeType NOLOCK = null;
-    private static final Class ENTITY_NOT_FOUND_ERROR =
-        EntityNotFoundException.class;
-    private static final Class NO_ERROR = null;
+    private static final Class<?> ENTITY_NOT_FOUND_ERROR = EntityNotFoundException.class;
+    private static final Class<?> NO_ERROR = null;
 
     private static final String MARKER_DATACACHE = "in DataCache";
     private static final String MARKER_DATABASE  = "in Database";
@@ -376,7 +375,7 @@
     
     public void testDirtyRefreshWithNoLockHitsDatabase() {
         verifyRefresh(WITH_DATACACHE, NOLOCK, DIRTY, !REFRESH_FROM_DATACACHE,
-                MARKER_DATACACHE);
+                MARKER_DATABASE);
     }
     
     public void testDirtyRefreshWithNoLockHitsDataCache() {
@@ -464,7 +463,7 @@
      * @param lock
      */
     public void verifyDeleteDetectionOnRefresh(boolean useDataCache, 
-            boolean dirty, LockModeType lock, Class expectedExceptionType) {
+            boolean dirty, LockModeType lock, Class<?> expectedExceptionType) {
         OpenJPAEntityManagerFactorySPI emf = (useDataCache)
             ? emfWithDataCache : emfWithoutDataCache;
             
@@ -522,49 +521,36 @@
         }
     }
 
-    public void testDeleteIsNotDetectedOnCleanRefreshWithoutLockWithDataCache()
-    {
-        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, !DIRTY, NOLOCK,
-                NO_ERROR);
+    public void testDeleteIsNotDetectedOnCleanRefreshWithoutLockWithDataCache() {
+        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, !DIRTY, NOLOCK, NO_ERROR);
     }
     
     public void testDeleteIsDetectedOnCleanRefreshWithLockWithDataCache() {
-        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, !DIRTY,
-                LockModeType.READ, ENTITY_NOT_FOUND_ERROR);
-        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, !DIRTY,
-                LockModeType.WRITE, ENTITY_NOT_FOUND_ERROR);
+        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, !DIRTY, LockModeType.READ,  ENTITY_NOT_FOUND_ERROR);
+        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, !DIRTY, LockModeType.WRITE, ENTITY_NOT_FOUND_ERROR);
     }
 
-    public void testDeleteIsNotDetectedOnDirtyRefreshWithoutLockWithDataCache()
-    {
-        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY, NOLOCK, NO_ERROR);
+    public void testDeleteIsDetectedOnDirtyRefreshWithoutLockWithDataCache() {
+        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY, NOLOCK, ENTITY_NOT_FOUND_ERROR);
     }
     
     public void testDeleteIsDetectedOnDirtyRefreshWithLockWithDataCache() {
-        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY, LockModeType.READ,
-                ENTITY_NOT_FOUND_ERROR);
-        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY,
-                LockModeType.WRITE, ENTITY_NOT_FOUND_ERROR);
+        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY, LockModeType.READ,  ENTITY_NOT_FOUND_ERROR);
+        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY, LockModeType.WRITE, ENTITY_NOT_FOUND_ERROR);
     }
     
     public void testDeleteIsDetectedOnDirtyRefreshWitDataCache() {
-        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY, LockModeType.READ,
-                ENTITY_NOT_FOUND_ERROR);
-        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY,
-                LockModeType.WRITE, ENTITY_NOT_FOUND_ERROR);
+        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY, LockModeType.READ,  ENTITY_NOT_FOUND_ERROR);
+        verifyDeleteDetectionOnRefresh(WITH_DATACACHE, DIRTY, LockModeType.WRITE, ENTITY_NOT_FOUND_ERROR);
     }
     
-    public void testDeleteIsDetectedOnCleanRefreshWithoutLockWithoutDataCache()
-    {
-        verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, !DIRTY, NOLOCK,
-                ENTITY_NOT_FOUND_ERROR);
+    public void testDeleteIsDetectedOnCleanRefreshWithoutLockWithoutDataCache() {
+        verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, !DIRTY, NOLOCK, ENTITY_NOT_FOUND_ERROR);
     }
     
     public void testDeleteIsDetectedOnCleanRefreshWithLockWithoutDataCache() {
-        verifyDeleteDetectionOnRefresh(!WITH_DATACACHE,
-                !DIRTY, LockModeType.READ, ENTITY_NOT_FOUND_ERROR);
-        verifyDeleteDetectionOnRefresh(!WITH_DATACACHE,
-                !DIRTY, LockModeType.WRITE, ENTITY_NOT_FOUND_ERROR);
+        verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, !DIRTY, LockModeType.READ,  ENTITY_NOT_FOUND_ERROR);
+        verifyDeleteDetectionOnRefresh(!WITH_DATACACHE, !DIRTY, LockModeType.WRITE, ENTITY_NOT_FOUND_ERROR);
     }
 
 }

Modified: openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestRefresh.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestRefresh.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestRefresh.java
(original)
+++ openjpa/trunk/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/simple/TestRefresh.java
Wed Nov  4 21:18:38 2009
@@ -18,12 +18,26 @@
  */
 package org.apache.openjpa.persistence.simple;
 
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
+
+import javax.persistence.CacheRetrieveMode;
+import javax.persistence.CacheStoreMode;
+import javax.persistence.EntityManager;
+
+import org.apache.openjpa.kernel.Broker;
+import org.apache.openjpa.persistence.JPAProperties;
+import org.apache.openjpa.persistence.OpenJPAEntityManager;
 import org.apache.openjpa.persistence.test.SingleEMTestCase;
 
 public class TestRefresh extends SingleEMTestCase {
 
     public void setUp() {
-        super.setUp(CLEAR_TABLES, Item.class, "openjpa.AutoDetach", "commit");
+        super.setUp(CLEAR_TABLES, Item.class, 
+             "openjpa.AutoDetach", "commit",
+             "openjpa.DataCache", "true",
+             "openjpa.RemoteCommitProvider", "sjvm");
     }
 
     public void testFlushRefreshNewInstance() {
@@ -36,4 +50,79 @@
         em.getTransaction().commit();
         assertEquals("Test Data", item.getItemData());
     }
+    
+    /**
+     * Refresh always bypass L2 cache.
+     * According to JPA 2.0 Spec Section 3.7.2:
+     * "The retrieveMode property is ignored for the refresh method,
+     *  which always causes data to be retrieved from the database, not the cache."
+     */
+    public void testRefreshBypassL2Cache() {
+        String original = "Original L2 Cached Data";
+        String sneakUpdate = "Sneak Update";
+        em.getTransaction().begin();
+        Item item = new Item();
+        item.setItemData(original);
+        em.persist(item);
+        em.getTransaction().commit();
+        assertCached(Item.class, item.getItemId());
+        
+        // Sneakily update with SQL
+        String sql = "UPDATE I_ITEM SET I_DATA=?1 WHERE I_ID=?2";
+        em.getTransaction().begin();
+        int updateCount = em.createNativeQuery(sql)
+            .setParameter(1, sneakUpdate)
+            .setParameter(2, item.getItemId())
+            .executeUpdate();
+        assertEquals(1, updateCount);
+        em.getTransaction().commit();
+        
+        em.getTransaction().begin();
+        // Find will find the L2 cached data
+        item = em.find(Item.class, item.getItemId());
+        assertEquals(original, item.getItemData());
+        // But refresh will get the actual database record
+        em.refresh(item);
+        assertEquals(sneakUpdate, item.getItemData());
+
+        // Even if cache retrieve mode is set to USE
+        em.setProperty(JPAProperties.CACHE_RETRIEVE_MODE, CacheRetrieveMode.USE);
+        em.refresh(item);
+        assertEquals(sneakUpdate, item.getItemData());
+        em.getTransaction().rollback();
+    }
+    
+    public void testCacheRetrieveModeSetting() {
+        OpenJPAEntityManager em = emf.createEntityManager();
+        em.setProperty(JPAProperties.CACHE_RETRIEVE_MODE, CacheRetrieveMode.USE);
+        Map<String, Object> properties = em.getProperties();
+        if (!properties.containsKey(JPAProperties.CACHE_RETRIEVE_MODE)) {
+            System.err.println(properties);
+            fail("Expected " + JPAProperties.CACHE_RETRIEVE_MODE + " properties be returned");
+        }
+        Object mode = properties.get(JPAProperties.CACHE_RETRIEVE_MODE);
+        assertEquals(mode, CacheRetrieveMode.USE);
+    }
+    
+    public void testCacheStoreModeSetting() {
+        OpenJPAEntityManager em = emf.createEntityManager();
+        em.setProperty(JPAProperties.CACHE_STORE_MODE, CacheStoreMode.USE);
+        Map<String, Object> properties = em.getProperties();
+        if (!properties.containsKey(JPAProperties.CACHE_STORE_MODE)) {
+            System.err.println(properties);
+            fail("Expected " + JPAProperties.CACHE_STORE_MODE + " properties be returned");
+        }
+        Object mode = properties.get(JPAProperties.CACHE_STORE_MODE);
+        assertEquals(mode, CacheStoreMode.USE);
+    }
+    
+    
+    void assertCached(Class<?> cls, Object oid) {
+        assertTrue(cls + ":" + oid + " should be in L2 cache, but not", emf.getCache().contains(cls,
oid));
+    }
+    
+    void assertNotCached(Class<?> cls, Object oid) {
+        assertTrue(cls + ":" + oid + " should not be in L2 cache, but is", !emf.getCache().contains(cls,
oid));
+    }
+    
 }

Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
(original)
+++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java
Wed Nov  4 21:18:38 2009
@@ -33,6 +33,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -49,6 +50,7 @@
 import javax.persistence.metamodel.Metamodel;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.LogFactory;
 import org.apache.openjpa.conf.Compatibility;
 import org.apache.openjpa.conf.OpenJPAConfiguration;
 import org.apache.openjpa.ee.ManagedRuntime;
@@ -105,11 +107,7 @@
     private DelegatingBroker _broker;
     private EntityManagerFactoryImpl _emf;
     private Map<FetchConfiguration,FetchPlan> _plans = new IdentityHashMap<FetchConfiguration,FetchPlan>(1);
-
     private RuntimeExceptionTranslator _ret = PersistenceExceptions.getRollbackTranslator(this);
-    
-    protected final String RETRIEVE_MODE_PROP = "javax.persistence.cache.retrieveMode";
-    protected final String STORE_MODE_PROP = "javax.persistence.cache.storeMode";
 
     public EntityManagerImpl() {
         // for Externalizable
@@ -732,10 +730,7 @@
     }
 
     public void refresh(Object entity) {
-        assertNotCloseInvoked();
-        assertValidAttchedEntity(entity);
-        _broker.assertWriteOperation();
-        _broker.refresh(entity, this);
+        refresh(entity, null, null);
     }
 
     public void refresh(Object entity, LockModeType mode) {
@@ -748,11 +743,19 @@
 
     public void refresh(Object entity, LockModeType mode, Map<String, Object> properties)
{
         assertNotCloseInvoked();
-        assertValidAttchedEntity(entity);
+        assertValidAttchedEntity("refresh", entity);
 
         _broker.assertWriteOperation();
-
         configureCurrentFetchPlan(pushFetchPlan(), properties, mode, true);
+        DataCacheRetrieveMode rmode = getFetchPlan().getCacheRetrieveMode();
+        if (DataCacheRetrieveMode.USE.equals(rmode) || rmode == null) {
+            getFetchPlan().setCacheRetrieveMode(DataCacheRetrieveMode.BYPASS);
+            if (rmode != null) {
+                Log log = _broker.getConfiguration().getConfigurationLog();
+                log.warn(_loc.get("cache-retrieve-override", Exceptions.toString(entity)));
+            }
+                
+        }
         try {
             _broker.refresh(entity, this);
         } finally {
@@ -1121,7 +1124,7 @@
     public LockModeType getLockMode(Object entity) {
         assertNotCloseInvoked();
         _broker.assertActiveTransaction();
-        assertValidAttchedEntity(entity);
+        assertValidAttchedEntity("getLockMode", entity);
         return MixedLockLevelsHelper.fromLockLevel(
             _broker.getLockLevel(entity));
     }
@@ -1132,13 +1135,13 @@
 
     public void lock(Object entity) {
         assertNotCloseInvoked();
-        assertValidAttchedEntity(entity);
+        assertValidAttchedEntity("lock", entity);
         _broker.lock(entity, this);
     }
 
     public void lock(Object entity, LockModeType mode, int timeout) {
         assertNotCloseInvoked();
-        assertValidAttchedEntity(entity);
+        assertValidAttchedEntity("lock", entity);
 
         configureCurrentFetchPlan(pushFetchPlan(), null, mode, false);
         try {
@@ -1150,7 +1153,7 @@
 
     public void lock(Object entity, LockModeType mode, Map<String, Object> properties)
{
         assertNotCloseInvoked();
-        assertValidAttchedEntity(entity);
+        assertValidAttchedEntity("lock", entity);
         _broker.assertActiveTransaction();
 
         configureCurrentFetchPlan(pushFetchPlan(), properties, mode, false);
@@ -1315,11 +1318,11 @@
      * Throw IllegalArgumentExceptionif if entity is not a valid entity or
      * if it is detached.
      */
-    void assertValidAttchedEntity(Object entity) {
+    void assertValidAttchedEntity(String call, Object entity) {
         OpenJPAStateManager sm = _broker.getStateManager(entity);
         if (sm == null || !sm.isPersistent() || sm.isDetached()) {
-            throw new IllegalArgumentException(_loc.get(
-                "invalid_entity_argument").getMessage());
+            throw new IllegalArgumentException(_loc.get("invalid_entity_argument", 
+                call, entity == null ? "null" : Exceptions.toString(entity)).getMessage());
         }
     }
 
@@ -1556,12 +1559,36 @@
         return createQuery(jpql);
     }
 
+    /**
+     * Get the properties used currently by this entity manager.
+     * The property keys and their values are harvested from kernel artifacts namely
+     * the Broker and FetchPlan by reflection.
+     * These property keys and values that denote the bean properties/values of the kernel
artifacts
+     * are converted to the original keys/values that user used to set the properties.
+     *    
+     */
     public Map<String, Object> getProperties() {
-        Map props = _broker.getProperties();
+        Map<String,Object> props = _broker.getProperties();
         for (String s : _broker.getSupportedProperties()) {
-            Method getter = Reflection.findGetter(this.getClass(), getBeanPropertyName(s),
false);
-            if (getter != null)
-                props.put(s, Reflection.get(this, getter));
+            String kernelKey = getBeanPropertyName(s);
+            Method getter = Reflection.findGetter(this.getClass(), kernelKey, false);
+            if (getter != null) {
+                String userKey = JPAProperties.getUserName(kernelKey);
+                Object kvalue  = Reflection.get(this, getter);
+                props.put(userKey.equals(kernelKey) ? s : userKey, JPAProperties.convertToUserValue(userKey,
kvalue));
+            }
+        }
+        FetchPlan fetch = getFetchPlan();
+        Class<?> fetchType = fetch.getClass();
+        Set<String> fProperties = Reflection.getBeanStylePropertyNames(fetchType);
+        for (String s : fProperties) {
+            String kernelKey = getBeanPropertyName(s);
+            Method getter = Reflection.findGetter(fetchType, kernelKey, false);
+            if (getter != null) {
+                String userKey = JPAProperties.getUserName(kernelKey);
+                Object kvalue  = Reflection.get(fetch, getter);
+                props.put(userKey.equals(kernelKey) ? s : userKey, JPAProperties.convertToUserValue(userKey,
kvalue));
+            }
         }
         return props;
     }
@@ -1621,17 +1648,17 @@
     private void configureCurrentCacheModes(FetchPlan fetch, Map<String, Object> properties)
{
         if (properties == null)
             return;
-        CacheRetrieveMode rMode = JPAProperties.get(CacheRetrieveMode.class, JPAProperties.CACHE_RETRIEVE_MODE,

-                properties);
+        CacheRetrieveMode rMode = JPAProperties.getEnumValue(CacheRetrieveMode.class, 
+                JPAProperties.CACHE_RETRIEVE_MODE, properties);
         if (rMode != null) {
-            fetch.setCacheRetrieveMode(JPAProperties.convertValue(DataCacheRetrieveMode.class,

+            fetch.setCacheRetrieveMode(JPAProperties.convertToKenelValue(DataCacheRetrieveMode.class,

                     JPAProperties.CACHE_RETRIEVE_MODE, rMode));
             properties.remove(JPAProperties.CACHE_RETRIEVE_MODE);
         }
-        CacheStoreMode sMode = JPAProperties.get(CacheStoreMode.class, JPAProperties.CACHE_STORE_MODE,

-                properties);
+        CacheStoreMode sMode = JPAProperties.getEnumValue(CacheStoreMode.class, 
+                JPAProperties.CACHE_STORE_MODE, properties);
         if (sMode != null) {
-            fetch.setCacheStoreMode(JPAProperties.convertValue(DataCacheStoreMode.class,

+            fetch.setCacheStoreMode(JPAProperties.convertToKenelValue(DataCacheStoreMode.class,

                     JPAProperties.CACHE_STORE_MODE, sMode));
             properties.remove(JPAProperties.CACHE_STORE_MODE);
         }
@@ -1670,6 +1697,7 @@
      */
     private boolean setKernelProperty(Object target, String original, Object value) {
         String beanProp = getBeanPropertyName(original);
+        JPAProperties.record(beanProp, original);
         Class<?> kType  = null;
         Object   kValue = null;
         Method setter = Reflection.findSetter(target.getClass(), beanProp, false);
@@ -1694,20 +1722,25 @@
      * Extract a bean-style property name from the given string.
      * If the given string is <code>"a.b.xyz"</code> then returns <code>"xyz"</code>

      */
-    String getBeanPropertyName(String s) {
-        if (JPAProperties.isValidKey(s)) {
-            return JPAProperties.getBeanProperty(s);
+    String getBeanPropertyName(String user) {
+        String result = user;
+        if (JPAProperties.isValidKey(user)) {
+            result = JPAProperties.getBeanProperty(user);
+        } else {
+            int dot = user.lastIndexOf('.');
+            if (dot != -1)
+                result = user.substring(dot+1);
         }
-        int dot = s.lastIndexOf('.');
-        return dot == -1 ? s : s.substring(dot+1);
+        return result; 
     }
     
+    
     /**
      * Convert the given value to a value consumable by OpenJPA kernel constructs.
      */
     Object convertUserValue(String key, Object value, Class<?> targetType) {
         if (JPAProperties.isValidKey(key)) 
-            return JPAProperties.convertValue(targetType, key, value);
+            return JPAProperties.convertToKenelValue(targetType, key, value);
         if (value instanceof String) {
             if ("null".equals(value)) {
                 return null;

Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java
(original)
+++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/JPAProperties.java
Wed Nov  4 21:18:38 2009
@@ -18,6 +18,7 @@
  */
 package org.apache.openjpa.persistence;
 
+import java.util.HashMap;
 import java.util.Map;
 
 import javax.persistence.CacheRetrieveMode;
@@ -68,13 +69,29 @@
     public static final String VALIDATE_PRE_PERSIST = PREFIX + "validation.group.pre-persist";
     public static final String VALIDATE_PRE_REMOVE  = PREFIX + "validation.group.pre-remove";
     public static final String VALIDATE_PRE_UPDATE  = PREFIX + "validation.group.pre-update";
-    
     public static final String VALIDATE_GROUP_DEFAULT = "javax.validation.groups.Default";
     
+    private static Map<String,String> _names = new HashMap<String, String>();
+    
+    /**
+     * Record the given kernel property key (which is a bean property name without any suffix)
+     * corresponding to the given original JPA/OpenJPA property used by the user to set the
values.
+     */
+    static void record(String kernel, String user) {
+        _names.put(kernel, user);
+    }
+    
+    /**
+     * Gets the original JPA Property name corresponding to the kernel property key 
+     * (which is a bean property name without any suffix).
+     */
+    static String getUserName(String beanProperty) {
+        return _names.containsKey(beanProperty) ? _names.get(beanProperty) : beanProperty;
+    }
+    
     /**
      * Is the given key appears to be a valid JPA specification defined key?
      * 
-     * @param key
      * @return true if the given string merely prefixed with <code>javax.persistence.</code>.
      * Does not really check all the keys defined in the specification.
      */
@@ -101,16 +118,12 @@
         return buf.toString();
     }
     
-    static <E extends Enum<E>> E get(Class<E> type, String key, Map<String,Object>
prop) {
-        return getEnumValue(type, null, key, prop);
-    }
-    
     /**
      * Convert the given user value to a value consumable by OpenJPA kernel constructs.
      * 
      * @return the same value if the given key is not a valid JPA property key or the value
is null.
      */
-    public static <T> T  convertValue(Class<T> resultType, String key, Object
value) {
+    public static <T> T  convertToKenelValue(Class<T> resultType, String key,
Object value) {
         if (value == null)
             return null;
         if (JPAProperties.isValidKey(key)) {
@@ -124,7 +137,29 @@
         return (T)value;
     }
     
+    /**
+     * Convert the given kernel value to a value visible to the user.
+     * 
+     * @return the same value if the given key is not a valid JPA property key or the value
is null.
+     */
+    public static Object convertToUserValue(String key, Object value) {
+        if (value == null)
+            return null;
+        if (JPAProperties.isValidKey(key)) {
+            // works because enum values are identical String
+            if (value instanceof DataCacheRetrieveMode) {
+                return CacheRetrieveMode.valueOf(value.toString());
+            } else if (value instanceof DataCacheStoreMode) {
+                return CacheStoreMode.valueOf(value.toString());
+            }
+        }
+        return value;
+    }
     
+    /**
+     * Get the value of the given key from the given properties after converting it to the
given
+     * enumerated value.
+     */
     public static <E extends Enum<E>> E getEnumValue(Class<E> type, String
key, Map<String,Object> prop) {
         return getEnumValue(type, null, key, prop);
     }
@@ -140,14 +175,28 @@
     public static <E extends Enum<E>> E getEnumValue(Class<E> type, E[]
values, String key, Map<String,Object> prop) {
         if (prop == null)
             return null;
-        return getEnumValue(type, values, key, prop.get(key));
+        return getEnumValue(type, values, prop.get(key));
     }
     
-    public static <E extends Enum<E>> E  getEnumValue(Class<E> type, String
key, Object val) {
-        return getEnumValue(type, null, key, val);
+    /**
+     * Gets a enum value of the given type from the given value.
+     * Converts the original value from a String, if necessary.
+     * 
+     * @return null if the key does not exist in the given properties.
+     */
+    public static <E extends Enum<E>> E  getEnumValue(Class<E> type, Object
val) {
+        return getEnumValue(type, null, val);
     }
     
-    public static <E extends Enum<E>> E  getEnumValue(Class<E> type, E[]
values, String key, Object val) {
+    /**
+     * Gets a enum value of the given type from the given value.
+     * Converts the original value from a String or ordinal number, if necessary.
+     * Conversion from an integral number to enum value is only attempted if the allowed
enum values
+     * are provided as non-null, non-empty array. 
+     * 
+     * @return null if the key does not exist in the given properties.
+     */
+    public static <E extends Enum<E>> E  getEnumValue(Class<E> type, E[]
values, Object val) {
         if (val == null)
             return null;
         if (type.isInstance(val))

Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceProductDerivation.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceProductDerivation.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceProductDerivation.java
(original)
+++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceProductDerivation.java
Wed Nov  4 21:18:38 2009
@@ -525,8 +525,7 @@
     /**
      * Return whether the given persistence unit uses an OpenJPA provider.
      */
-    private static boolean isOpenJPAPersistenceProvider
-        (PersistenceUnitInfo pinfo, ClassLoader loader) {
+    private static boolean isOpenJPAPersistenceProvider(PersistenceUnitInfo pinfo, ClassLoader
loader) {
         String provider = pinfo.getPersistenceProviderClassName();
         if (StringUtils.isEmpty(provider) || PersistenceProviderImpl.class.getName().equals(provider))
             return true;
@@ -742,11 +741,7 @@
                 //      startPersistenceUnit()
                 // case 'property' for 'properties' is handled in startElement()
                 case 'c': // class
-                    if ("class".equals(name))
-                        _info.addManagedClassName(currentText());
-                    else // FIXME - caching
-                        throw new javax.persistence.PersistenceException(
-                            "Not implemented yet");
+                    _info.addManagedClassName(currentText());
                     break;
                 case 'e': // exclude-unlisted-classes
                     setExcludeUnlistedClasses(currentText());
@@ -754,8 +749,7 @@
                 case 'j':
                     if ("jta-data-source".equals(name))
                         _info.setJtaDataSourceName(currentText());
-                    else // jar-file
-                    {
+                    else { // jar-file 
                         try {
                             _info.addJarFileName(currentText());
                         } catch (IllegalArgumentException iae) {
@@ -774,12 +768,10 @@
                         _info.setPersistenceProviderClassName(currentText());
                     break;
                 case 's' : // shared-cache-mode
-                    _info.setSharedCacheMode(JPAProperties.getEnumValue(SharedCacheMode.class,

-                            JPAProperties.CACHE_MODE, currentText()));
+                    _info.setSharedCacheMode(JPAProperties.getEnumValue(SharedCacheMode.class,
currentText()));
                     break;
                 case 'v': // validation-mode
-                    _info.setValidationMode(JPAProperties.getEnumValue(ValidationMode.class,
-                        JPAProperties.VALIDATE_MODE, currentText()));
+                    _info.setValidationMode(JPAProperties.getEnumValue(ValidationMode.class,
currentText()));
                     break;
             }
         }

Modified: openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceUnitInfoImpl.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceUnitInfoImpl.java?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceUnitInfoImpl.java
(original)
+++ openjpa/trunk/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/PersistenceUnitInfoImpl.java
Wed Nov  4 21:18:38 2009
@@ -307,8 +307,7 @@
             if (JPAProperties.PROVIDER.equals(key))
                 setPersistenceProviderClassName((String) val);
             else if (JPAProperties.TRANSACTION_TYPE.equals(key)) {
-                setTransactionType(JPAProperties.getEnumValue(PersistenceUnitTransactionType.class,

-                        JPAProperties.TRANSACTION_TYPE, key));
+                setTransactionType(JPAProperties.getEnumValue(PersistenceUnitTransactionType.class,
key));
             } else if (JPAProperties.DATASOURCE_JTA.equals(key)) {
                 if (val instanceof String) {
                     setJtaDataSourceName((String) val);
@@ -322,9 +321,9 @@
                     setNonJtaDataSource((DataSource) val);
                 }
             } else if (JPAProperties.VALIDATE_MODE.equals(key)) {
-                setValidationMode(JPAProperties.getEnumValue(ValidationMode.class, JPAProperties.VALIDATE_MODE,
val));
+                setValidationMode(JPAProperties.getEnumValue(ValidationMode.class, val));
             } else if (JPAProperties.CACHE_MODE.equals(key)) { 
-                setSharedCacheMode(JPAProperties.getEnumValue(SharedCacheMode.class, JPAProperties.CACHE_MODE,
val));
+                setSharedCacheMode(JPAProperties.getEnumValue(SharedCacheMode.class, val));
             } else {
                 _props.put(key, val);
             }

Modified: openjpa/trunk/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties?rev=832874&r1=832873&r2=832874&view=diff
==============================================================================
--- openjpa/trunk/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties
(original)
+++ openjpa/trunk/openjpa-persistence/src/main/resources/org/apache/openjpa/persistence/localizer.properties
Wed Nov  4 21:18:38 2009
@@ -173,8 +173,8 @@
 non-unique-result: Query "{0}" selected {1} results, but expected unique result.
 unwrap-em-invalid: EntityManager can not be unwrapped to an instance of "{0}".
 unwrap-query-invalid: Query can not be unwrapped to an instance of "{0}".
-invalid_entity_argument: Object being locked must be an valid and not detached \
-    entity.
+invalid_entity_argument: {0} can not be invoked on "{1}". This entity is either \
+	detached or not persistent or null.
 dup-pu: The persistence unit "{0}" was found multiple times in the following \
     resources "{1}", but persistence unit names should be unique. The first \
     persistence unit matching the provided name in "{2}" is being used.
@@ -223,3 +223,4 @@
 create-emf-error: Failed to create a provider for "{0}".
 invalid-version-attribute: Persistence version attribute value "{0}" is not valid.  Using
version "{1}" by default.
 not-jpql-or-criteria-query: Query is neither a JPQL SELECT nor a Criteria API query.
+cache-retrieve-override: The setting of CacheRetrieveMode.USE is ignored and set to BYPASS
for refresh operation.  
\ No newline at end of file



Mime
View raw message