/*****************************************************************
* 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.cayenne.access;
import org.apache.cayenne.Cayenne;
import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.DataObject;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.dba.JdbcAdapter;
import org.apache.cayenne.dba.JdbcPkGenerator;
import org.apache.cayenne.dba.PkGenerator;
import org.apache.cayenne.di.AdhocObjectFactory;
import org.apache.cayenne.di.Inject;
import org.apache.cayenne.log.JdbcEventLogger;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.query.SQLTemplate;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.test.jdbc.DBHelper;
import org.apache.cayenne.test.jdbc.TableHelper;
import org.apache.cayenne.testdo.testmap.Artist;
import org.apache.cayenne.testdo.testmap.Painting;
import org.apache.cayenne.unit.di.server.CayenneProjects;
import org.apache.cayenne.unit.di.server.ServerCase;
import org.apache.cayenne.unit.di.server.UseServerRuntime;
import org.junit.Before;
import org.junit.Test;
import java.sql.Types;
import java.util.Date;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
public class DataContextExtrasIT extends ServerCase {
@Inject
protected DataContext context;
@Inject
protected DBHelper dbHelper;
@Inject
protected JdbcEventLogger logger;
@Inject
protected AdhocObjectFactory objectFactory;
protected TableHelper tArtist;
protected TableHelper tPainting;
@Before
public void setUp() throws Exception {
tArtist = new TableHelper(dbHelper, "ARTIST");
tArtist.setColumns("ARTIST_ID", "ARTIST_NAME");
tPainting = new TableHelper(dbHelper, "PAINTING");
tPainting.setColumns(
"PAINTING_ID",
"ARTIST_ID",
"PAINTING_TITLE",
"ESTIMATED_PRICE").setColumnTypes(
Types.INTEGER,
Types.BIGINT,
Types.VARCHAR,
Types.DECIMAL);
}
protected void createPhantomModificationDataSet() throws Exception {
tArtist.insert(33001, "artist1");
tArtist.insert(33002, "artist2");
}
protected void createPhantomModificationsValidateToOneDataSet() throws Exception {
tArtist.insert(33001, "artist1");
tPainting.insert(33001, 33001, "P1", 3000);
}
protected void createValidateOnToManyChangeDataSet() throws Exception {
tArtist.insert(33001, "artist1");
}
protected void createPhantomRelationshipModificationCommitDataSet() throws Exception {
tArtist.insert(33001, "artist1");
tArtist.insert(33002, "artist2");
tPainting.insert(33001, 33001, "P1", 3000);
}
@Test
public void testManualIdProcessingOnCommit() throws Exception {
Artist object = context.newObject(Artist.class);
object.setArtistName("ABC");
assertEquals(PersistenceState.NEW, object.getPersistenceState());
// do a manual ID substitution
ObjectId manualId = new ObjectId("Artist", Artist.ARTIST_ID_PK_COLUMN, 77777);
object.setObjectId(manualId);
context.commitChanges();
assertEquals(PersistenceState.COMMITTED, object.getPersistenceState());
assertSame(object, context.getGraphManager().getNode(manualId));
assertEquals(manualId, object.getObjectId());
}
@Test
public void testResolveFault() {
Artist o1 = context.newObject(Artist.class);
o1.setArtistName("a");
context.commitChanges();
context.invalidateObjects(o1);
assertEquals(PersistenceState.HOLLOW, o1.getPersistenceState());
assertNull(o1.readPropertyDirectly("artistName"));
context.prepareForAccess(o1, null, false);
assertEquals(PersistenceState.COMMITTED, o1.getPersistenceState());
assertEquals("a", o1.readPropertyDirectly("artistName"));
}
@Test
public void testResolveFaultFailure() {
Persistent o1 = context.findOrCreateObject(new ObjectId(
"Artist",
Artist.ARTIST_ID_PK_COLUMN,
234));
try {
context.prepareForAccess(o1, null, false);
fail("Must blow on non-existing fault.");
}
catch (CayenneRuntimeException ex) {
}
}
@Test
public void testUserProperties() {
assertNull(context.getUserProperty("ABC"));
Object object = new Object();
context.setUserProperty("ABC", object);
assertSame(object, context.getUserProperty("ABC"));
}
@Test
public void testHasChangesNew() {
assertTrue("No changes expected in context", !context.hasChanges());
context.newObject("Artist");
assertTrue(
"Object added to context, expected to report changes",
context.hasChanges());
}
@Test
public void testNewObject() {
Artist a1 = (Artist) context.newObject("Artist");
assertTrue(context.getGraphManager().registeredNodes().contains(a1));
assertTrue(context.newObjects().contains(a1));
}
@Test
public void testNewObjectWithClass() {
Artist a1 = context.newObject(Artist.class);
assertTrue(context.getGraphManager().registeredNodes().contains(a1));
assertTrue(context.newObjects().contains(a1));
}
@Test
public void testIdObjectFromDataRow() {
DataRow row = new DataRow(10);
row.put("ARTIST_ID", new Integer(100000));
DataObject obj = context.objectFromDataRow(Artist.class, row);
assertNotNull(obj);
assertTrue(context.getGraphManager().registeredNodes().contains(obj));
assertEquals(PersistenceState.HOLLOW, obj.getPersistenceState());
assertNull(context.getObjectStore().getCachedSnapshot(obj.getObjectId()));
}
@Test
public void testPartialObjectFromDataRow() {
DataRow row = new DataRow(10);
row.put("ARTIST_ID", new Integer(100001));
row.put("ARTIST_NAME", "ArtistXYZ");
DataObject obj = context.objectFromDataRow(Artist.class, row);
assertNotNull(obj);
assertTrue(context.getGraphManager().registeredNodes().contains(obj));
assertEquals(PersistenceState.HOLLOW, obj.getPersistenceState());
assertNull(context.getObjectStore().getCachedSnapshot(obj.getObjectId()));
}
@Test
public void testFullObjectFromDataRow() {
DataRow row = new DataRow(10);
row.put("ARTIST_ID", new Integer(123456));
row.put("ARTIST_NAME", "ArtistXYZ");
row.put("DATE_OF_BIRTH", new Date());
Artist obj = context.objectFromDataRow(Artist.class, row);
assertTrue(context.getGraphManager().registeredNodes().contains(obj));
assertEquals(PersistenceState.COMMITTED, obj.getPersistenceState());
assertNotNull(context.getObjectStore().getCachedSnapshot(obj.getObjectId()));
assertEquals("ArtistXYZ", obj.getArtistName());
}
@Test
public void testCommitChangesError() {
DataDomain domain = context.getParentDataDomain();
// setup mockup PK generator that will blow on PK request
// to emulate an exception
JdbcAdapter jdbcAdapter = objectFactory.newInstance(
JdbcAdapter.class,
JdbcAdapter.class.getName());
PkGenerator newGenerator = new JdbcPkGenerator(jdbcAdapter) {
@Override
public Object generatePk(DataNode node, DbAttribute pk) throws Exception {
throw new CayenneRuntimeException("Intentional");
}
};
PkGenerator oldGenerator = domain
.getDataNodes()
.iterator()
.next()
.getAdapter()
.getPkGenerator();
JdbcAdapter adapter = (JdbcAdapter) domain
.getDataNodes()
.iterator()
.next()
.getAdapter();
adapter.setPkGenerator(newGenerator);
try {
Artist newArtist = context.newObject(Artist.class);
newArtist.setArtistName("aaa");
context.commitChanges();
fail("Exception expected but not thrown due to missing PK generation routine.");
}
catch (CayenneRuntimeException ex) {
// exception expected
}
finally {
adapter.setPkGenerator(oldGenerator);
}
}
/**
* Testing behavior of Cayenne when a database exception is thrown in SELECT query.
*/
@Test
public void testSelectException() {
SQLTemplate q = new SQLTemplate(Artist.class, "SELECT * FROM NON_EXISTENT_TABLE");
try {
context.performGenericQuery(q);
fail("Query was invalid and was supposed to fail.");
}
catch (RuntimeException ex) {
// exception expected
}
}
@Test
public void testEntityResolver() {
assertNotNull(context.getEntityResolver());
}
@Test
public void testPhantomModificationsValidate() throws Exception {
createPhantomModificationDataSet();
List<?> objects = context.performQuery(new SelectQuery(Artist.class));
Artist a1 = (Artist) objects.get(0);
Artist a2 = (Artist) objects.get(1);
a1.setArtistName(a1.getArtistName());
a1.resetValidationFlags();
a2.resetValidationFlags();
context.commitChanges();
assertFalse(a1.isValidateForSaveCalled());
assertFalse(a2.isValidateForSaveCalled());
// "phantom" modification - the property is really unchanged
a1.setArtistName(a1.getArtistName());
// some other unrelated object modification caused phantom modification to be
// committed as well...
// (see CAY-355)
a2.setArtistName(a2.getArtistName() + "_x");
a1.resetValidationFlags();
a2.resetValidationFlags();
context.commitChanges();
assertTrue(a2.isValidateForSaveCalled());
assertFalse(a1.isValidateForSaveCalled());
}
@Test
public void testPhantomModificationsValidateToOne() throws Exception {
createPhantomModificationsValidateToOneDataSet();
List<?> objects = context.performQuery(new SelectQuery(Painting.class));
Painting p1 = (Painting) objects.get(0);
p1.setPaintingTitle(p1.getPaintingTitle());
p1.resetValidationFlags();
context.commitChanges();
assertFalse(
"To-one relationship presence caused incorrect validation call.",
p1.isValidateForSaveCalled());
}
@Test
public void testValidateOnToManyChange() throws Exception {
createValidateOnToManyChangeDataSet();
List<?> objects = context.performQuery(new SelectQuery(Artist.class));
Artist a1 = (Artist) objects.get(0);
Painting p1 = context.newObject(Painting.class);
p1.setPaintingTitle("XXX");
a1.addToPaintingArray(p1);
a1.resetValidationFlags();
context.commitChanges();
assertFalse(a1.isValidateForSaveCalled());
}
@Test
public void testPhantomAttributeModificationCommit() throws Exception {
createPhantomModificationDataSet();
List<?> objects = context.performQuery(new SelectQuery(Artist.class));
Artist a1 = (Artist) objects.get(0);
String oldName = a1.getArtistName();
a1.setArtistName(oldName + ".mod");
a1.setArtistName(oldName);
context.commitChanges();
assertEquals(PersistenceState.COMMITTED, a1.getPersistenceState());
}
@Test
public void testPhantomRelationshipModificationCommit() throws Exception {
createPhantomRelationshipModificationCommitDataSet();
SelectQuery query = new SelectQuery(Painting.class);
List<?> objects = context.performQuery(query);
assertEquals(1, objects.size());
Painting p1 = (Painting) objects.get(0);
Artist oldArtist = p1.getToArtist();
Artist newArtist = Cayenne.objectForPK(context, Artist.class, 33002);
assertNotSame(oldArtist, newArtist);
p1.setToArtist(newArtist);
p1.setToArtist(oldArtist);
context.commitChanges();
assertEquals(PersistenceState.COMMITTED, p1.getPersistenceState());
assertEquals(PersistenceState.COMMITTED, oldArtist.getPersistenceState());
assertEquals(PersistenceState.COMMITTED, newArtist.getPersistenceState());
}
@Test
public void testPhantomRelationshipModificationValidate() throws Exception {
createPhantomRelationshipModificationCommitDataSet();
SelectQuery query = new SelectQuery(Painting.class);
List<?> objects = context.performQuery(query);
assertEquals(1, objects.size());
Painting p1 = (Painting) objects.get(0);
Artist oldArtist = p1.getToArtist();
Artist newArtist = Cayenne.objectForPK(context, Artist.class, 33002);
assertNotSame(oldArtist, newArtist);
p1.setToArtist(newArtist);
p1.setToArtist(oldArtist);
p1.resetValidationFlags();
context.commitChanges();
assertFalse(p1.isValidateForSaveCalled());
}
}