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.

<item-descriptor name="team">
  <table name="team" type="primary" id-column-names="id">
    <property name="name" required="true" />
  </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" />
  </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>

And here is the 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),

name VARCHAR2(40), team_id VARCHAR2(40), CONSTRAINT player_p PRIMARY KEY (id) );

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

player.setPropertyValue("team", team);

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.

<property name="team" column-name="team_id" item-type="team" writable="false" />

And instead add the player to the team repository item.

Set<RepositoryItem> players = (Set<RepositoryItem>) team.getPropertyValue("players");
players.add(player);

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.

Share

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.

<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>

And here is the 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)
);

You can set the team in the player repository item.

player.setPropertyValue("team", team);

Or add the player to the team repository item.

Set<RepositoryItem> players = (Set<RepositoryItem>) team.getPropertyValue("players");
players.add(player);

What gets tricky is if you require a player to be on a team.

<property name="team" column-name="team_id" item-type="team" required="true" />
team_id VARCHAR2(40) NOT NULL,

In this case you cannot remove a player from a team by doing this:

Set<RepositoryItem> players = (Set<RepositoryItem>) team.getPropertyValue("players");
players.remove(player);

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.

player.setPropertyValue("team", newTeam);

To help enforce this you can make the team’s players property unwritable.

<property name="players" column-name="id" data-type="set" component-item-type ="player" writable="false" />

I hope this makes sense. If you have any questions please leave them in the comments.

Share

Ignore Unavoidable Generic Type Problems in Eclipse

When you use Eclipse by default most generic type problems show up as warnings. Unfortunately this results in lots of warnings when you use ATG code.

You can turn off the warnings that result from ATG code by checking the option “Ignore unavoidable generic type problems”.

Ignore unavoidable generic type problems

Ignore unavoidable generic type problems

For example you might have code like this where getValues() comes from the ATG API:

getValues().put(name, value);

By default Eclipse reports warnings for the above line. But this is unavoidable and with this Ignore option turned on no warning is reported for this line.

Kind of useful and you don’t have to add @SuppressWarnings(“unchecked”) liberally through your ATG code.

Share

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

Share

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

<export-items />

Or if you want to export only certain item-descriptors:

<export-items item-descriptors="authors, books" />

And if you want to ensure you only get a certain item-descriptor without any referenced item-descriptors:

<export-items item-descriptors="books" skip-references="true" />

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.

Share

Why is there a _requestid in my URL?

Sometimes when you are using an ATG powered website, e.g. Keds, you will see a _requestid parameter in your URL, e.g. http://www.keds.com/?_requestid=138580.

This occurs when the ATG code, typically a form handler, does a local redirect instead of a redirect. This is ATG’s own flavor of redirect which does a few extra things as described in the JavaDoc:

Sends a redirect response to the client using the specified redirect location URL. This function is similar to sendRedirect(), with the following two differences:

  • The session ID is added to the URL if a valid cookie with the session ID is not found.
  • If the URL is a relative URL (i.e., “login/error.html”), then the URL will be converted to a full absolute URL, as required by the HTTP specification. A relative URL is one that does not specify a protocol (e.g., “http:”).

In general, this function should be used when redirecting back to a page on the same site. If you are redirecting to a page on some other site, use the full absolute URL and call sendRedirect().

However this does not explain the mysterious _requestid parameter. Fortunately ATG’s Programming Guide does:

Preserving Request Scoped Objects on Redirects

If a request results in a redirect to a local page through the method HttpServletResponse.sendLocalRedirect(), the ATG platform treats the redirect request as part of the original request, and maintains any request-scoped objects associated with that request. To implement this, the ATG platform adds an additional query parameter named _requestid to the redirected URL.

Now you know what the _requestid parameter is and why it exists. And if you don’t want it then you the programmer should use a redirect instead of a local redirect.

Share

Fixing Resource Already Exists on Disk Errors in Eclipse

When I imported a new project I could not build it because of “Resource already exists on disk” errors like this.

Eclipse: The project was not built due to "Resource already exists on disk"

The simple fix was to remove the directory, typically classes, which contained the file it was complaining about and then refreshing. Removing just the file and then refreshing did not work.

However when I would do an ant build later and then refresh my project I would get the same error.

The better solution is to tell Eclipse not to copy the files it is complaining about to the output folder.

  1. Go to Project Properties.
  2. Select Java Compiler -> Building.
  3. In the Filtered resources box add the files you don’t want copied.
  4. Rebuild project.

Here is a screenshot showing how I added “*.properties, *.xslt” to the Filtered resources to fix this problem.

Eclipse: Filtered resources in Project Properties, Java Compiler, Building

Eclipse: Filtered resources in Project Properties, Java Compiler, Building

Share

Cannot Login To BCC

Business Control Center Error

Sometimes I can’t login to the BCC. Usually when that happens I see errors like this in the logs.

page seeing error: /atg/templates/page/html/BizUIPageTemplate.jsp
error message: null

java.lang.NullPointerException
   at org.apache.pluto.factory.PortletObjectAccess.getRenderRequest(PortletObjectAccess.java:36)
   at org.apache.pluto.PortletContainerImpl.portletLoad(PortletContainerImpl.java:197)
   at atg.portal.portletstandard.services.ATGContainerService.portletLoad(ATGContainerService.java:202)
   at atg.portal.servlet.GearRequestDispatcherImpl.dispatch(GearRequestDispatcherImpl.java:397)
   at atg.portal.servlet.GearRequestDispatcherImpl.include(GearRequestDispatcherImpl.java:438)
   at atg.portal.servlet.GearRequestDispatcherImpl.include(GearRequestDispatcherImpl.java:453)
   at atg.portal.framework.taglib.IncludeTag.doEndTag(IncludeTag.java:162)
   at atg.portal.framework.taglib.elwrap.IncludeTagWrapper.doEndTag(IncludeTagWrapper.java:36)
   at org.apache.jsp.templates.layout.html.BizUILayout_jsp._jspx_meth_paf_005finclude_005f0(BizUILayout_jsp.java:708)
   at org.apache.jsp.templates.layout.html.BizUILayout_jsp._jspService(BizUILayout_jsp.java:263)
....stack trace CROPPED after 10 lines.
ATG Business Control Center 
© ATG 2005-2011

When this happens I first try clearing my cookies and then reloading the BCC login page.

When that doesn’t work I then remove the tmp and work directories.

$ cd /opt/jboss/home/server/merch
$ rm -rf tmp work

Then I restart the BCC and things seem happy again.

Share

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’;
Share