ATG Ordered One-to-Many Relationships

Last week I blogged about ATG unordered one-to-many relationships. Ordered one-to-many is quite similar but a little more complex.

Here again is the same example of players belonging to a team except now the players are a list property of the team.

[code language=”xml”]<item -descriptor name="team">
<table name="team" type="primary" id-column-names="id">
<property name="name" required="true"></property>
</table>
<table name="player" type="multi" multi-column-name=”idx” id-column-names="team_id">
<property name="players" column-name="id" data-type="list" component-item-type="player"></property>
</table>
</item>

<item -descriptor name="player">
<table name="player" type="primary" id-column-names="id">
<property name="name" required="true"></property>
<property name="team" column-name="team_id" item-type="team"></property>
</table>
</item>[/code]

And here is the SQL.

[code language=”sql”]CREATE TABLE team (
id VARCHAR2(40) NOT NULL,
name VARCHAR2(3) NOT NULL,
CONSTRAINT team_p PRIMARY KEY (id)
);

CREATE TABLE player (
id VARCHAR2(40) NOT NULL,
idx NUMBER(2,0),<p> name VARCHAR2(40),
team_id VARCHAR2(40),
CONSTRAINT player_p PRIMARY KEY (id)
);[/code]

Now comes the tricky part. You can never just set the team in the player repository item.

[code language=”java”]player.setPropertyValue("team", team);[/code]

This is because when you do this the idx column in the player table is not set. We could create a repository property and try to set it separately but that is bad practice. You would have to know how many players are already on the team and then set the idx to that number.

What you should do is not allow the team property to be written.

[code language=”xml”]<property name="team" column-name="team_id" item-type="team" writable="false" />;[/code]

And instead add the player to the team repository item.

[code language=”java”]Set<RepositoryItem> players = (Set<RepositoryItem>) team.getPropertyValue("players");
players.add(player);[/code]

Because of this requirement you cannot make the team property required for the player as discussed before.

Again if you have any questions please leave them in the comments.

ATG Unordered One-to-Many Relationships

Bautista, Lawrie, Rasmus

Photo by james_in_to

Previously I blogged about specifying one-to-many relationships in ATG. Turns out I could have done it much more simply.

Using the same example of players belonging to a team here are the simplified SQL repository definitions.
[code language=”xml”]<item-descriptor name="team">
<table name="team" type="primary" id-column-names="id">
<property name="name" required="true" />
</table>
<table name="player" type="multi" id-column-names="team_id">
<property name="players" column-name="id" data-type="set" component-item-type="player" />
</table>
</item-descriptor>

<item-descriptor name="player">
<table name="player" type="primary" id-column-names="id">
<property name="name" required="true" />
<property name="team" column-name="team_id" item-type="team" />
</table>
</item-descriptor>[/code]
And here is the SQL.
[code language=”sql”]CREATE TABLE team (
id VARCHAR2(40) NOT NULL,
name VARCHAR2(3) NOT NULL,
CONSTRAINT team_p PRIMARY KEY (id)
);

CREATE TABLE player (
id VARCHAR2(40) NOT NULL,
name VARCHAR2(40) NOT NULL,
team_id VARCHAR2(40),
CONSTRAINT player_p PRIMARY KEY (id)
);[/code]
You can set the team in the player repository item.
[code language=”java”]player.setPropertyValue("team", team);[/code]
Or add the player to the team repository item.
[code language=”java”]Set<RepositoryItem> players = (Set<RepositoryItem>) team.getPropertyValue("players");
players.add(player);[/code]
What gets tricky is if you require a player to be on a team.
[code language=”xml”]<property name="team" column-name="team_id" item-type="team" required="true" />[/code]
[code language=”sql”]team_id VARCHAR2(40) NOT NULL,[/code]
In this case you cannot remove a player from a team by doing this:
[code language=”java”]Set<RepositoryItem> players = (Set<RepositoryItem>) team.getPropertyValue("players");
players.remove(player);[/code]
This is because when you remove the player from the team the player will no longer have a team. This violates both the repository definition and will result in a SQL error because team_id cannot be null.

The only way you can change a player’s team is to do this.
[code language=”java”]player.setPropertyValue("team", newTeam);[/code]
To help enforce this you can make the team’s players property unwritable.
[code language=”xml”]<property name="players" column-name="id" data-type="set" component-item-type ="player" writable="false" />[/code]
I hope this makes sense. If you have any questions please leave them in the comments.

Debugging Transaction is not Active

Problems like this happen occasionally and are quite bedeviling.

Caused by: java.lang.RuntimeException: CONTAINER:atg.repository.RepositoryException; SOURCE:org.jboss.util.NestedSQLException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000001:c525:503e32fb:2f2c status: ActionStatus.ABORT_ONLY >; - nested throwable: (javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000001:c525:503e32fb:2f2c status: ActionStatus.ABORT_ONLY >)
    at atg.adapter.gsa.GSAItemDescriptor.loadProperty(GSAItemDescriptor.java:5479)
    at atg.adapter.gsa.GSAItem.getPersistentPropertyValue(GSAItem.java:1101)
    at atg.adapter.gsa.GSAItem.getPropertyValue(GSAItem.java:994)
    at atg.adapter.gsa.GSAItem.getPropertyValue(GSAItem.java:1272)
    at atg.repository.RepositoryItemImpl.getPropertyValue(RepositoryItemImpl.java:128)
    at atg.commerce.order.processor.ProcLoadHandlingInstructionObjects.runProcess(ProcLoadHandlingInstructionObjects.java:183)
    at atg.service.pipeline.PipelineLink.runProcess(PipelineLink.java:233)
    at atg.service.pipeline.PipelineChain.runProcess(PipelineChain.java:343)
    ... 17 more
Caused by: CONTAINER:atg.repository.RepositoryException; SOURCE:org.jboss.util.NestedSQLException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000001:c525:503e32fb:2f2c status: ActionStatus.ABORT_ONLY >; - nested throwable: (javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000001:c525:503e32fb:2f2c status: ActionStatus.ABORT_ONLY >)
    at atg.adapter.gsa.GSAItemDescriptor.loadProperties(GSAItemDescriptor.java:5431)
    at atg.adapter.gsa.GSAItemDescriptor.loadProperty(GSAItemDescriptor.java:5471)
    ... 24 more
Caused by: org.jboss.util.NestedSQLException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000001:c525:503e32fb:2f2c status: ActionStatus.ABORT_ONLY >; - nested throwable: (javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000001:c525:503e32fb:2f2c status: ActionStatus.ABORT_ONLY >)
    at org.jboss.resource.adapter.jdbc.WrapperDataSource.getConnection(WrapperDataSource.java:96)
    at atg.service.jdbc.WatcherDataSource.getConnection(WatcherDataSource.java:801)
    at atg.service.jdbc.WatcherDataSource.getConnection(WatcherDataSource.java:782)
    at atg.adapter.gsa.GSATransaction.getConnection(GSATransaction.java:744)
    at atg.adapter.gsa.GSAItemDescriptor.getConnection(GSAItemDescriptor.java:2365)
    at atg.adapter.gsa.GSAItemDescriptor.loadProperties(GSAItemDescriptor.java:5345)
    ... 25 more
Caused by: javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000001:c525:503e32fb:2f2c status: ActionStatus.ABORT_ONLY >
    at org.jboss.resource.connectionmanager.TxConnectionManager.getManagedConnection(TxConnectionManager.java:319)
    at org.jboss.resource.connectionmanager.BaseConnectionManager2.allocateConnection(BaseConnectionManager2.java:403)
    at org.jboss.resource.connectionmanager.BaseConnectionManager2$ConnectionManagerProxy.allocateConnection(BaseConnectionManager2.java:850)
    at org.jboss.resource.adapter.jdbc.WrapperDataSource.getConnection(WrapperDataSource.java:90)
    ... 30 more

I tweeted about issues like this over two years ago.

As I said in the tweet this almost always is not a transaction issue but actually an application issue. The tweet references an excellent article Transaction is not active: tx=TransactionImple < ac, BasicAction in which the author found in his case that the problem was a NullPointerException which caused the transaction to end.

Today I had to debug this issue again. First I checked the transaction in ProcLoadHandlingInstructionObjects before it called getPropertyValue on the shipping group repository item.

GSAItem item = (GSAItem) sgMutItem;
ItemTransactionState state = item.getItemTransactionState(false);
logDebug("state=" + state.mGSATransaction);

I saw in the logs that the status of the transaction was ActionStatus.ABORT_ONLY which meant the transaction was already aborted before getting the property value from the repository which is why the Transaction is not active exception happened.

Next I checked the transaction at the beginning of ProcLoadHandlingInstructionObjects using the order repository item.

GSAItem item = (GSAItem) orderItem;
ItemTransactionState state = item.getItemTransactionState(false);
logDebug("state=" + state.mGSATransaction);

When I confirmed that the status of the transaction was ActionStatus.ABORT_ONLY at the beginning of the processor I went backwards through the pipeline checking all the processors in the same way.

After doing that and confirming the transaction was ActionStatus.ABORT_ONLY at the beginning of the pipeline process I began checking the code that called the pipeline using code like this.

GSAItem orderItem = (GSAItem) order.getRepositoryItem();
ItemTransactionState state = orderItem.getItemTransactionState(false);
try {
  int status = state.mGSATransaction.getTransaction().getStatus();
  if (status == Status.STATUS_MARKED_ROLLBACK) {
    logError("Oh-oh, marked for rollback.");
  }
} catch (SystemException exc) {}

I finally found the problem was that in a previous call to another pipeline an error had occurred and the transaction was marked for rollback. However the code did not check the result of the pipeline call and had continued.

The moral of the story is always check your return values and don’t go down the rat hole of believing it’s a transaction issue.

Alice in front of the rabbit hole

Exporting ATG Repositories

ATG’s documentation suggests running startSQLRepository to export repositories. However this is an older solution that works well with SOLID and DAS but not so easily with Oracle and JBoss. Oracle uses its own JDBC jar which you have to add to the CLASSPATH before running startSQLRepository. JBoss uses JNDI and real DataSource’s to connect to the database. But this doesn’t work with startSQLRepository so you have to set up a separate ATG server with fake DataSources to run with startSQLRepository.

All of this is to say it’s a pain to use startSQLRepository. The much simpler method is to navigate to the repository in your Dynamo admin (e.g. http://localhost:8080/dyn/admin). Then in the “Run XML Operation Tags on the Repository” textbox enter the command
[xml]<export-items />[/xml]
Or if you want to export only certain item-descriptors:
[xml]<export-items item-descriptors="authors, books" />[/xml]
And if you want to ensure you only get a certain item-descriptor without any referenced item-descriptors:
[xml]<export-items item-descriptors="books" skip-references="true" />[/xml]
That Was EasyYou can then save the outputted XML to an XML file and then later paste the contents of this XML file to the same textbox to import the repository items.

For more information please see the <export-items> section in the SQL Repository Reference chapter of the ATG Repository Guide.

Fixing Existing Repository ID Errors

I was feeling part of the scenery. I walked right out of the machinery. | Flickr

I was feeling part of the scenery. I walked right out of the machinery. by Neal.

The other day I was trying to create a page repository item using the BCC when I got an error saying it could not create the item. Looking in the logs I saw this exception.

16:13:59,909 ERROR [RepositoryAssetService]
atg.repository.RepositoryException: createItem() was called with an existing ID : '3100007' for type 'page'. Use getItemForUpdate() instead.
        at atg.adapter.version.VersionRepository.createItem(VersionRepository.java:1903)
        at atg.adapter.version.VersionRepository.createItem(VersionRepository.java:1125)
        at atg.remote.assetmanager.editor.service.RepositoryAssetServiceImpl.doCreateAsset(RepositoryAssetServiceImpl.java:476)
        at atg.remote.assetmanager.editor.service.AssetServiceImpl.createAsset(AssetServiceImpl.java:331)
        at atg.remote.assetmanager.editor.service.AssetEditorService.createAssetAndLink(AssetEditorService.java:555)
        at atg.remote.assetmanager.editor.service.AssetEditorService.createAsset(AssetEditorService.java:508)

The problem was the ID generator was producing ID’s that already existed in the database. This sometimes happens when things are imported into the BCC.

To fix this problem do the following.

  1. Look for the row in the das_id_generator table which has the item descriptor that is causing the error.
  2. Change the seed value (typically upwards) or put a prefix or suffix on it.
  3. Restart the server.

In this case I ended up running this SQL to fix the existing ID error:

UPDATE DAS_ID_GENERATOR SET PREFIX = 'pg_' WHERE id_space_name = ‘page’;

Set ATG Repository Item Date or Timestamp Properties to the Current Time

*Time* Ticking away... on Flickr *Time* Ticking away… by Michel Filion

This is a neat trick for automatically setting a date or timestamp property to the current time.  I learned it while perusing the ATG Repository Guide.

Date and Timestamp Properties

A repository item can use properties whose values are dates or timestamps, with the value set to the current date or time, using the java.util.Date, java.sql.Date, or java.sql.Timestamp classes. You can have a property whose value is set to the current time or date at the moment a repository item is created. You can do this by setting the feature descriptor attribute useNowForDefault. For example:
[xml]<property name="creationDate" data-type="timestamp">
<attribute name="useNowForDefault" value="true"/>
</property>[/xml]
For more information about this technique, see the Assigning FeatureDescriptorValues with the <attribute> Tag section in this chapter.

Specifying One-to-Many Relationship in ATG Repositories

Monta driving on Flickr
(Photo: Monta driving by Yogma)

Specifying one-to-many relationships is ridiculously easy in Ruby on Rails.  Unfortunately it’s not so straight-forward in ATG repositories.

First you specify the “belongs to” relationship.  In this example the player belongs to a team.

[xml]<item-descriptor name="player">
<table name="team_players" type="auxiliary" id-column-names="team_id" shared-table-sequence="1">
<property name="team" column-name="team_id" item-type="team" />
</table>
</item-descriptor>[/xml]

Then you specify the “has many” relationship.  In this example the team has many players.

[xml]<item-descriptor name="team">
<table name="team_players" type="multi" id-column-names="player_id" shared-table-sequence="2">
<property name="players" column-name="player_id" data-type="set" component-item-type="player" />
</table>
</item-descriptor>[/xml]

Note the trick is specifying the “shared-table-sequence.”

Here is the SQL for the table that specifies this relationship in our example.

[sql]CREATE TABLE team_players
(
team_id VARCHAR2(40) NOT NULL,
player_id VARCHAR2(40) NOT NULL,
CONSTRAINT team_players_pk PRIMARY KEY (team_id, player_id),
CONSTRAINT team_players_players_fk foreign key (player_id) references players (id),
CONSTRAINT team_players_team_fk foreign key (team_id) references teams (id)
);[/sql]

The Dangers of Custom ATG Repository ID’s.

El Alma del Ebro on Flickr

(Photo: El Alma del Ebro by Paulo Brandão)

Recently I was creating some SKU and product items using the ACC and I decided to create them with custom ID’s.  The items were created fine but then I discovered I couldn’t add these items to the cart, I couldn’t use them in promotions, I couldn’t set up inventory for them, etc.  I continually got exceptions like these.

/atg/commerce/order/OrderManager	---	CONTAINER:atg.service.pipeline.RunProcessException: An exception was thrown from the context of the link named [setCatalogRefs].; SOURCE:atg.commerce.CommerceException: Unable to retrieve catalog reference for item with id ci90000002 and catalogRefId sku_free_01.
/atg/commerce/order/OrderManager		at atg.service.pipeline.PipelineChain.runProcess(PipelineChain.java:371)
/atg/commerce/order/OrderManager		at atg.service.pipeline.PipelineChainContext.runProcess(PipelineChainContext.java:185)
/atg/commerce/order/OrderManager		at atg.service.pipeline.PipelineManager.runProcess(PipelineManager.java:453)
/atg/commerce/order/OrderManager		at atg.commerce.order.OrderImpl.ensureContainers(OrderImpl.java:1261)
/atg/commerce/order/OrderManager		at atg.commerce.order.OrderImpl.getCommerceItems(OrderImpl.java:691)
/atg/commerce/order/OrderManager	....stack trace CROPPED
/atg/commerce/order/OrderManager	Caused by :atg.commerce.CommerceException: Unable to retrieve catalog reference for item with id ci90000002 and catalogRefId sku_free_01.
/atg/commerce/order/OrderManager		at atg.commerce.order.processor.ProcSetCatalogRefs.loadCatalogRef(ProcSetCatalogRefs.java:267)
/atg/commerce/order/OrderManager		at atg.commerce.order.processor.ProcSetCatalogRefs.runProcess(ProcSetCatalogRefs.java:121)
/atg/commerce/order/OrderManager		at atg.service.pipeline.PipelineLink.runProcess(PipelineLink.java:233)
/atg/commerce/order/OrderManager		at atg.service.pipeline.PipelineChain.runProcess(PipelineChain.java:343)
/atg/commerce/order/OrderManager		at atg.service.pipeline.PipelineChainContext.runProcess(PipelineChainContext.java:185)
/atg/commerce/order/OrderManager		at atg.service.pipeline.PipelineManager.runProcess(PipelineManager.java:453)
/atg/commerce/order/OrderManager		at atg.commerce.order.OrderImpl.ensureContainers(OrderImpl.java:1261)
/atg/commerce/order/OrderManager		at atg.commerce.order.OrderImpl.getCommerceItems(OrderImpl.java:691)
/atg/commerce/order/OrderManager	....stack trace CROPPED after 10 lines.

After pulling out my hair out for a few hours I realized the problem was because I had accidentally put a space at the end of the custom ID I had created. Too bad you can’t determine that from the stack trace.

Lesson Learned #1: Don’t put spaces in your custom ID’s, especially at the end.

Lesson Learned #2: Custom ID’s are a luxury you might want to avoid. 🙂

DynamicBeanMap

Previously I posted about using the atg.beans.DynamicBeanMap class to wrap a RepositoryItem so that it is accessible via JSTL.  However it turns out this is not a great solution.

If we want to access a simple property in a RepositoryItem wrapped in a DynamicBeanMap it is relatively straightforward.  For example,

${user.name}

However if we want to access a RepositoryItem property like this,

${user.address}

then we end up getting a StackOverflowError as the DynamicBeanMap spins going back forth between the user RepositoryItem and the address RepositoryItem.

Interestingly enough accessing a simple property from the RepositoryItem property works fine.

${user.address.city}

One solution is to set the recursive argument to false when creating the DynamicBeanMap.

DynamicBeanMap itemBean = new DynamicBeanMap(pValue, false);

When you do this the following will work correctly.

${user.address}

However this no longer works.

${user.address.city}

We ended up abandoning the DynamicBeanMap and creating a library of strongly typed repository item wrapper proxy objects.

ATG Support helped me tremendously to figure out what was going on. They suggested an alternative which we never tried because of the large impact it would have on our JSP.

What I determined in looking at this further is that the DynamicBeanMap class is not really documented for customer use, but there is a DSP/DSPEL tag called “tomap” that uses this class that we do document. See the appendix in our 2006.3 Page Developer’s guide.

So, this tag would avoid this problem since it does have an undocumented “recursive” attribute that defaults to “false”, but I think it might be preferable to set it to “true” and use another undocumented option. After using the “tomap” tag with recursive=true, you can then use a _realObject property to unwrap your “final” object being accessed.

So if your tag is:

<dspel:getvalueof var="address" param="user.address" />

You can change it to use:

<dspel:getvalueof var="address" param="user.address._realObject" />

Or if you were doing:

<dspel:valueof param="user" />

you could use:

<dspel:valueof param="user._realObject" />

Basically you just unwrap whatever end/final object you’re trying to get to with _realObject.  Since we can’t see exactly what code called hashCode that caused the StackOverflowError, I can’t be certain this will avoid the StackOverflowError, but I suspect it will.

This solution will have the benefit of having minimal impact on your ability to access properties with JSTL.

I’ve entered a PR #155848 about some of these properties not being documented.

Also PR #81771 was submitted requesting the ability to recursively access repository items in JSTL.

Repository creating tables automatically

Recently we noticed while running some ATG unit tests that tables were being created by the ATG repository if they had not already been created by our SQL scripts.  This was a functionality that I was unaware of but apparently it is not unique, Hibernate does this too.  I could not find any documentation about this nor could I determine how to turn it off.

The ATG repository creates these tables using the repository definition and the defaults for column width and data type.  It does not seem to warn that it is creating these tables.