You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by da...@apache.org on 2008/03/09 02:30:21 UTC

svn commit: r635133 - in /tapestry/tapestry5/trunk/tapestry-hibernate/src: main/java/org/apache/tapestry/hibernate/ main/java/org/apache/tapestry/internal/hibernate/ main/resources/org/apache/tapestry/internal/hibernate/ site/apt/ test/java/org/apache/...

Author: dadams
Date: Sat Mar  8 17:30:20 2008
New Revision: 635133

URL: http://svn.apache.org/viewvc?rev=635133&view=rev
Log:
TAPESTRY-2246: Add @Persist strategy for Hibernate entities

Added:
    tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategy.java
    tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategyTest.java
    tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/example/app0/pages/PersistEntity.java
    tapestry/tapestry5/trunk/tapestry-hibernate/src/test/webapp/PersistEntity.tml
Modified:
    tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateModule.java
    tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateSessionManager.java
    tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/HibernateMessages.java
    tapestry/tapestry5/trunk/tapestry-hibernate/src/main/resources/org/apache/tapestry/internal/hibernate/HibernateStrings.properties
    tapestry/tapestry5/trunk/tapestry-hibernate/src/site/apt/userguide.apt
    tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/hibernate/integration/TapestryHibernateIntegrationTests.java

Modified: tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateModule.java?rev=635133&r1=635132&r2=635133&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateModule.java Sat Mar  8 17:30:20 2008
@@ -17,6 +17,7 @@
 import org.apache.tapestry.ValueEncoder;
 import org.apache.tapestry.internal.InternalConstants;
 import org.apache.tapestry.internal.hibernate.DefaultHibernateConfigurer;
+import org.apache.tapestry.internal.hibernate.EntityPersistentFieldStrategy;
 import org.apache.tapestry.internal.hibernate.HibernateEntityValueEncoder;
 import org.apache.tapestry.internal.hibernate.HibernateSessionManagerImpl;
 import org.apache.tapestry.internal.hibernate.HibernateSessionSourceImpl;
@@ -37,6 +38,7 @@
 import org.apache.tapestry.ioc.services.RegistryShutdownHub;
 import org.apache.tapestry.ioc.services.TypeCoercer;
 import org.apache.tapestry.services.AliasContribution;
+import org.apache.tapestry.services.PersistentFieldStrategy;
 import org.apache.tapestry.services.ValueEncoderFactory;
 import org.hibernate.Session;
 import org.hibernate.mapping.PersistentClass;
@@ -164,4 +166,13 @@
 			configuration.add(entityClass, factory);
     	}
     }
+    
+    /**
+     * Contributes the following: <dl> <dt>entity</dt> <dd>Stores the id of the entity and reloads from the {@link Session}</dd> </dl>
+     */
+    public void contributePersistentFieldManager(MappedConfiguration<String, PersistentFieldStrategy> configuration, ObjectLocator locator)
+    {
+    	configuration.add("entity", locator.autobuild(EntityPersistentFieldStrategy.class));
+    }
+    
 }

Modified: tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateSessionManager.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateSessionManager.java?rev=635133&r1=635132&r2=635133&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateSessionManager.java (original)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/hibernate/HibernateSessionManager.java Sat Mar  8 17:30:20 2008
@@ -21,10 +21,10 @@
  * needed, allowing the session to checkpoint (commit the current transaction and continue) and
  * commit the transaction automatically at the end of the request.
  * <p>
- * Remember that in Tapestry, action requests and render requests are entirely seperate, and you
- * will see a seperate request and a seperate transaction for each. Care should be taken to ensure
+ * Remember that in Tapestry, action requests and render requests are entirely separate, and you
+ * will see a separate request and a separate transaction for each. Care should be taken to ensure
  * that entity objects that are retained (in the session, as persistent field values) between
- * requests are handled correct (they tend to become detached instances).
+ * requests are handled correctly (they tend to become detached instances).
  * <p>
  * This implementation of this service is per-thread.
  */
@@ -46,7 +46,7 @@
   void commit();
 
   /**
-   * Aborts the current transaction, and starts a new tranasction to replace it.
+   * Aborts the current transaction, and starts a new transaction to replace it.
    */
   void abort();
 }

Added: tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategy.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategy.java?rev=635133&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategy.java (added)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategy.java Sat Mar  8 17:30:20 2008
@@ -0,0 +1,111 @@
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry.internal.hibernate;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tapestry.internal.services.AbstractSessionPersistentFieldStrategy;
+import org.apache.tapestry.internal.services.PersistentFieldChangeImpl;
+import org.apache.tapestry.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry.ioc.internal.util.Defense;
+import org.apache.tapestry.ioc.services.TypeCoercer;
+import org.apache.tapestry.services.PersistentFieldChange;
+import org.apache.tapestry.services.PersistentFieldStrategy;
+import org.apache.tapestry.services.Request;
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+import org.hibernate.metadata.ClassMetadata;
+
+/** Persists Hibernate entities by storing their id in the session. */
+public class EntityPersistentFieldStrategy implements PersistentFieldStrategy {
+    private static final Pattern KEY_PATTERN = Pattern.compile("^([^:]+):([^:]+):(.+)$");
+    
+	private final PersistentFieldStrategy _strategy;
+    private final Session _session;
+    private final TypeCoercer _typeCoercer;
+    
+	public EntityPersistentFieldStrategy(Session session, TypeCoercer typeCoercer, Request request) {
+		super();
+		_strategy = new EntityStrategy(request);
+		_session = session;
+		_typeCoercer = typeCoercer;
+	}
+
+	public void discardChanges(String pageName) {
+		_strategy.discardChanges(pageName);
+	}
+
+	public Collection<PersistentFieldChange> gatherFieldChanges(String pageName) {
+		Collection<PersistentFieldChange> changes = CollectionFactory.newList();
+		for(PersistentFieldChange change : _strategy.gatherFieldChanges(pageName)) {
+			if (change.getValue() == null) {
+				changes.add(change);
+				continue;
+			}
+			
+			String key = change.getValue().toString();
+			Matcher matcher = KEY_PATTERN.matcher(key);
+			matcher.matches();
+			
+			String entityName = matcher.group(1);
+			String idClassName = matcher.group(2);
+			String stringId = matcher.group(3);
+				
+			try {
+				Class<?> idClass = Class.forName(idClassName);
+				Object idObj = _typeCoercer.coerce(stringId, idClass);
+				
+				Serializable id = Defense.cast(idObj, Serializable.class, "id");
+				Object entity = _session.get(entityName, id);
+				changes.add(new PersistentFieldChangeImpl(change.getComponentId(), change.getFieldName(), entity));
+			} catch (ClassNotFoundException e) {
+				throw new RuntimeException(HibernateMessages.badEntityIdType(entityName, idClassName, stringId), e);
+			}
+		}
+		
+		return changes;
+	}
+
+	/** Stores the entity id's as values in the form: entityName:idClass:id */
+	public void postChange(String pageName, String componentId, String fieldName, Object newValue) {
+		if (newValue != null) {
+			try {
+				String entityName = _session.getEntityName(newValue);
+				ClassMetadata metadata = _session.getSessionFactory().getClassMetadata(newValue.getClass());
+				Serializable id = metadata.getIdentifier(newValue, _session.getEntityMode());
+				newValue = entityName + ":" + id.getClass().getCanonicalName() + ":" + _typeCoercer.coerce(id, String.class);
+				
+			} catch (HibernateException e) {
+				throw new IllegalArgumentException(HibernateMessages.entityNotAttached(newValue), e);
+			}
+		}
+		
+		_strategy.postChange(pageName, componentId, fieldName, newValue);
+	}
+
+	/** We want to store the data in the session normally, we just need to control the values.
+	 * We also need a separate instance so that we know it's using the right prefix for the values.
+	*/
+	private static final class EntityStrategy extends AbstractSessionPersistentFieldStrategy {
+
+		public EntityStrategy(Request request) {
+			super("entity:", request);
+		}
+		
+	}
+}

Modified: tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/HibernateMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/HibernateMessages.java?rev=635133&r1=635132&r2=635133&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/HibernateMessages.java (original)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/main/java/org/apache/tapestry/internal/hibernate/HibernateMessages.java Sat Mar  8 17:30:20 2008
@@ -37,4 +37,12 @@
     static String configurationImmutable() {
     	return MESSAGES.get("configuration-immutable");
     }
+    
+    static String badEntityIdType(String entityName, String idClassName, String id) {
+    	return MESSAGES.format("bad-entity-id-type", entityName, idClassName, id);
+    }
+    
+    static String entityNotAttached(Object entity) {
+    	return MESSAGES.format("entity-not-attached", entity);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-hibernate/src/main/resources/org/apache/tapestry/internal/hibernate/HibernateStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/main/resources/org/apache/tapestry/internal/hibernate/HibernateStrings.properties?rev=635133&r1=635132&r2=635133&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/main/resources/org/apache/tapestry/internal/hibernate/HibernateStrings.properties (original)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/main/resources/org/apache/tapestry/internal/hibernate/HibernateStrings.properties Sat Mar  8 17:30:20 2008
@@ -15,3 +15,5 @@
 startup-timing=Hibernate startup: %,d ms to configure, %,d ms overall.
 entity-catalog=Configured Hibernate entities: %s
 configuration-immutable=The Hibernate configuration is now immutable since the SessionFactory has already been created.
+bad-entity-id-type=Failed to load the entity id class while loading a persisted entity. entity: %s, id class: %s, id: %s
+entity-not-attached=Failed persisting an entity in the session. Only entities attached to a Hibernate Session can be persisted. entity: %s
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/tapestry-hibernate/src/site/apt/userguide.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/site/apt/userguide.apt?rev=635133&r1=635132&r2=635133&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/site/apt/userguide.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/site/apt/userguide.apt Sat Mar  8 17:30:20 2008
@@ -6,7 +6,7 @@
 
   Value encoders are automatically created for all mapped Hibernate entity types. This is done by encoding the entity as it's 
   id (coerced to a String) and decoding the entity by looking it up in the Hibernate Session using the encoded id. Consider
-  the following:
+  the following example:
   
 +----+
 public class ViewPerson {
@@ -17,6 +17,11 @@
   {
     _person = person;
   }
+  
+  Person onPassivate()
+  {
+    return _person;
+  }
 }
 +----+   
 
@@ -29,3 +34,24 @@
 +----+
 
   Accessing the page as <</viewperson/152>> would load the Person entity with id 152 and use that as the page context.
+
+Using @Persist with entities
+
+  If you wish to persist an entity in the session, you may use the "entity" persistence strategy:
+  
++----+
+public class ViewPerson {
+  @Persist("entity")
+  @Property
+  private Person _person;
+  
+  void onActivate(Person person)
+  {
+    _person = person;
+  }
+  
+}
++----+   
+  
+  This persistence strategy works with any Hibernate entity that is associated with a valid Hibernate Session by persisting only the id
+  of the entity. Notice that no onPassivate() method is needed; when the page renders the entity is loaded by the id stored in the session.

Modified: tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/hibernate/integration/TapestryHibernateIntegrationTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/hibernate/integration/TapestryHibernateIntegrationTests.java?rev=635133&r1=635132&r2=635133&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/hibernate/integration/TapestryHibernateIntegrationTests.java (original)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/hibernate/integration/TapestryHibernateIntegrationTests.java Sat Mar  8 17:30:20 2008
@@ -17,7 +17,7 @@
 import org.apache.tapestry.test.AbstractIntegrationTestSuite;
 import org.testng.annotations.Test;
 
-@Test
+@Test(sequential=true,groups="integration")
 public class TapestryHibernateIntegrationTests extends AbstractIntegrationTestSuite
 {
     public TapestryHibernateIntegrationTests()
@@ -25,7 +25,7 @@
         super("src/test/webapp");
     }
 
-	public void test_valueencode_all_entity_types() throws Exception {
+	public void valueencode_all_entity_types() throws Exception {
 		open("/encodeentities");
 		
 		assertEquals(0, getText("//span[@id='name']").length());
@@ -39,4 +39,29 @@
 		assertEquals(0, getText("//span[@id='name']").length());
 	}
 
+	public void persist_entities() {
+		open("/persistentity");
+		assertEquals(0, getText("//span[@id='name']").length());
+		
+		clickAndWait("link=create entity");
+		assertText("//span[@id='name']", "name");
+		
+		// shouldn't save the change to the name because it's reloaded every time
+		clickAndWait("link=change the name");
+		assertText("//span[@id='name']", "name");
+		
+		// can set back to null
+		clickAndWait("link=set to null");
+		assertEquals(getText("//span[@id='name']").length(), 0);
+		
+		// deleting an entity that is still persisted. just remove the entity from the session if it's not found.
+		clickAndWait("link=create entity");
+		assertText("//span[@id='name']", "name");
+		clickAndWait("link=delete");
+		assertEquals(getText("//span[@id='name']").length(), 0);
+				
+		// transient objects cannot be persisted
+		clickAndWait("link=set to transient");
+		assertTextPresent("Error persisting");
+	}
 }

Added: tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategyTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategyTest.java?rev=635133&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategyTest.java (added)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/apache/tapestry/internal/hibernate/EntityPersistentFieldStrategyTest.java Sat Mar  8 17:30:20 2008
@@ -0,0 +1,36 @@
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry.internal.hibernate;
+
+import org.apache.tapestry.test.TapestryTestCase;
+import org.hibernate.HibernateException;
+import org.hibernate.Session;
+import org.testng.annotations.Test;
+
+@Test
+public class EntityPersistentFieldStrategyTest extends TapestryTestCase {
+	public void not_an_entity() {
+		Session session = newMock(Session.class);
+		EntityPersistentFieldStrategy strategy = new EntityPersistentFieldStrategy(session, null, null);
+		
+		expect(session.getEntityName("foo")).andThrow(new HibernateException("error"));
+		replay();
+		try {
+			strategy.postChange(null, null, null, "foo");
+			fail("did not throw");
+		} catch (IllegalArgumentException e) { }
+		verify();		
+	}
+}

Added: tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/example/app0/pages/PersistEntity.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/example/app0/pages/PersistEntity.java?rev=635133&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/example/app0/pages/PersistEntity.java (added)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/test/java/org/example/app0/pages/PersistEntity.java Sat Mar  8 17:30:20 2008
@@ -0,0 +1,58 @@
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
+//
+// Licensed 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.example.app0.pages;
+
+import java.util.List;
+
+import org.apache.tapestry.annotations.Persist;
+import org.apache.tapestry.annotations.Property;
+import org.apache.tapestry.ioc.annotations.Inject;
+import org.example.app0.entities.User;
+import org.hibernate.Session;
+
+public class PersistEntity {
+	@Persist("entity")
+	@Property
+	private User _user;
+	
+	@Inject
+	private Session _session;
+		
+	void onCreateEntity() {
+		User user = new User();
+		user.setFirstName("name");
+		_session.save(user);
+		_user = user;
+	}
+	
+	void onChangeName() {
+		_user.setFirstName("name2");
+		// avoid having the changes saved if the request transaction is committed
+		_session.evict(_user);
+	}
+	
+	void onSetToTransient() {
+		_user = new User();
+	}
+	
+	void onSetToNull() {
+		_user = null;
+	}
+	
+	void onDelete() {
+		for(User user : (List<User>)_session.createQuery("from User").list())
+			_session.delete(user);
+	}
+}

Added: tapestry/tapestry5/trunk/tapestry-hibernate/src/test/webapp/PersistEntity.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-hibernate/src/test/webapp/PersistEntity.tml?rev=635133&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-hibernate/src/test/webapp/PersistEntity.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-hibernate/src/test/webapp/PersistEntity.tml Sat Mar  8 17:30:20 2008
@@ -0,0 +1,10 @@
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+<body>
+	<p>entity name: <span id="name"><t:if test="user">${user.firstName}</t:if></span></p>
+	<p><t:eventlink event="createEntity">create entity</t:eventlink></p>
+	<p><t:eventlink event="changeName">change the name</t:eventlink></p>
+	<p><t:eventlink event="setToNull">set to null</t:eventlink></p>
+	<p><t:eventlink event="delete">delete</t:eventlink></p>
+	<p><t:eventlink event="setToTransient">set to transient</t:eventlink></p>
+</body>
+</html>
\ No newline at end of file