So tonight I geeked out and created a boilerplate Java project with debugging properly enabled for Tridion Storage Extensions when publishing Pages to the filesystem.  There are a couple tutorials out there explaining how to do this for ComponentPresentation and for a custom type, but not how to do this Pages.  There are a couple of interesting points with this exercise that aren’t really explained elsewhere, so I thought I’d highlight them with this post.  Also, some copy-paste boilerplate code should be handy.
There is code below for Tridion 2011 SP1.  If you’re versed with Eclipse and Java, simply copy paste the boilerplate code into your project and off you go.
For those anxious, here are the main points you need know:
- If you’re on 2011 SP1 or SP2, then make sure your class is inside a package that starts with “com.tridion.storageâ€. If it is not, then your extension won’t run. This is very subtly mentioned on SDLLiveContent.  Note, this is not needed for 2011 GA not 2013.
- You need to provide two constructors, not one!
- If you simply want to extend existing Page or ComponentPresentation publishing, you do not need to implement any interface. You can get away by extending FSPageDAO (for file system publishing of Pages) or JPAComponentPresentationDAO (for database publishing of CPs). The reason: FSPageDAO already implements the PageDAO interface.
- The example on SDLLiveContent has a typo in one of the imports:Â
import org.springframework.context.stereotype.Component;
is incorrect. It should be:
import org.springframework.stereotype.Component;
Ok, so now the long story:
To start off, we should set up a proper test bed. This means setting up Http Upload as a Java project in Eclipse and getting it working so that your Tridion CM can publish to it. However, before we jump into setting up the deployer, let’s first set up the most basic “Hello World†Java app and get it working with the debugger.
This is actually pretty easy.  Just google “setting up Java project in Eclipseâ€.  I actually followed this dude’s tutorial: http://www.avajava.com/tutorials/lessons/how-do-i-debug-my-web-project-in-tomcat-from-eclipse.html?page=1.  Despite it being for an older version of Eclipse and Tomcat, it still worked for me with pretty much the same steps.
Now that we got our basic servlet app running with the ability to set and hit breakpoints, let us proceed to configure Http Upload.
The procedure is pretty standard. Â You can follow Nuno’s guide on importing the pre-packaged WAR file (http://www.sdltridionworld.com/articles/sdltridion2011/tutorials/deployer-and-odata-3.aspx), create the project manually following the steps below:
- Create a new Java project
- Set up the standard folder structure:
- /WEB-INF/
- /WEB-INF/lib
- Put your cd_storage_conf.xml, cd_deployer_conf.xml and logback.xml into /src.
- Copy all the jars from the Tridion installation media lib and third-party lib folders into your project’s lib.
- Configure the BuildPath of your project by adding all the above jars to it.
- Add a standard web.xml file into your WEB-INF.
Here is what my project looks like:
Before you start adding any extension code or configs, make sure that you can start the Deployer via Eclipse. Â It will significantly boost your productivity.
Once you can hit the default /httpupload URL, set up a Publishing Target in your dev/test Tridion instance that points to your Eclipse Deployer:
Make sure you get stuff publishing successfully before you go forward with configuring/building Storage Extensions.  Another approach is to follow Jaime’s suggestion, which is to place a test package into the Deployer’s incoming directory and work independently from the CM.  Read more about that here: http://sdltridionworld.com/articles/sdltridion2011/tutorials/Deployer_Extensions_With_Eclipse_2.aspx.
Just to show what is possible, here is the publisher configured to point to my Eclipse Http Upload with a breakpoint hit in my custom Storage Extension when publishing a test.html page:
Before I jump into the code, I have to give Mihai some credit for posting his tutorial. Respek yo!  I conveniently printed all 3 chapters into PDF and was able to get most of this stuff figured out on the plane.  Here is a link to the tutorial: http://sdltridionworld.com/articles/sdltridion2011/tutorials/extending-content-delivery-storage-sdltridion-2011-1.aspx.  Also, please visit his blog after you read this (http://yatb.mitza.net/).  He’s addicted to staring at his Google Analytics console monitoring page hits, so please give him the satisfaction of seeing +1 from your visit.
Another useful article demonstrating a very cool use case is from Will regarding CDN integration. Â So have a read:Â http://sdltridionworld.com/community/2011_extensions/cdn_integration.aspx. Â Note, this is written for 2011 GA and the “com.tridion.storage” package name didn’t seem like a pre-requisite requirement in that release.
Alright, enough yammering. Â Here is the boilerplate:
package com.tridion.storage.extension; import java.io.File; import java.util.Collection; import com.tridion.broker.StorageException; import com.tridion.data.CharacterData; import com.tridion.storage.filesystem.FSEntityManager; import com.tridion.storage.filesystem.FSPageDAO; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component("BoilerplateFSPageDAO") @Scope("prototype") public class BoilerplateFSPageDAO extends FSPageDAO { public BoilerplateFSPageDAO(String storageId, String storageName, File storageLocation) { super(storageId, storageName, storageLocation); // TODO Auto-generated constructor stub } public BoilerplateFSPageDAO(String storageId, String storageName, File storageLocation, FSEntityManager fsEntityManager) { super(storageId, storageName, storageLocation); // TODO Auto-generated constructor stub } @Override public void create(CharacterData page, String relativePath) throws StorageException { // TODO Auto-generated method stub super.create(page, relativePath); } @Override public Collection findAll(int publicationId) throws StorageException { // TODO Auto-generated method stub return super.findAll(publicationId); } @Override public CharacterData findByPrimaryKey(int publicationId, int pageId) throws StorageException { // TODO Auto-generated method stub return super.findByPrimaryKey(publicationId, pageId); } @Override public void remove(int publicationId, int pageId, String relativePath) throws StorageException { // TODO Auto-generated method stub super.remove(publicationId, pageId, relativePath); } @Override public void update(CharacterData page, String originalRelativePath, String newRelativePath) throws StorageException { // TODO Auto-generated method stub super.update(page, originalRelativePath, newRelativePath); } }
Also here is a boilerplate for Component Presentations published to the Broker DB:
package com.tridion.storage.extension; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import com.tridion.broker.StorageException; import com.tridion.storage.ComponentPresentation; import com.tridion.storage.dao.ComponentPresentationDAO; import com.tridion.storage.persistence.JPAComponentPresentationDAO; import com.tridion.storage.util.ComponentPresentationTypeEnum; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @Component("BoilerplateJPAComponentPresentationDAO") @Scope("prototype") public class BoilerplateJPAComponentPresentationDAO extends JPAComponentPresentationDAO implements ComponentPresentationDAO { public BoilerplateJPAComponentPresentationDAO(String storageId, EntityManagerFactory entityManagerFactory, String storageType) { super(storageId, entityManagerFactory, storageType); // TODO Auto-generated constructor stub } public BoilerplateJPAComponentPresentationDAO(String storageId, EntityManagerFactory entityManagerFactory, EntityManager entityManager, String storageType) { super(storageId, entityManagerFactory, entityManager, storageType); // TODO Auto-generated constructor stub } @Override public void create(ComponentPresentation itemToCreate, ComponentPresentationTypeEnum componentPresentationType) throws StorageException { // TODO Auto-generated method stub super.create(itemToCreate, componentPresentationType); } @Override public void remove(ComponentPresentation itemToRemove, ComponentPresentationTypeEnum componentPresentationType) throws StorageException { // TODO Auto-generated method stub super.remove(itemToRemove, componentPresentationType); } @Override public void remove(int publicationId, int componentId, int componentTemplateId, ComponentPresentationTypeEnum componentPresentationType) throws StorageException { // TODO Auto-generated method stub super.remove(publicationId, componentId, componentTemplateId, componentPresentationType); } @Override public void update(ComponentPresentation itemToUpdate, ComponentPresentationTypeEnum componentPresentationType) throws StorageException { // TODO Auto-generated method stub super.update(itemToUpdate, componentPresentationType); } }
Finally here is my bundle XML file. Don’t forget to reference it in cd_storage_conf.xml:
<?xml version="1.0" encoding="UTF-8"?> <StorageDAOBundles> <StorageDAOBundle type="filesystem"> <StorageDAO typeMapping="Page" class="com.tridion.storage.extension.BoilerplateFSPageDAO" /> </StorageDAOBundle> <StorageDAOBundle type="persistence"> <StorageDAO typeMapping="ComponentPresentation" class="com.tridion.storage.extension.BoilerplateJPAComponentPresentationDAO" /> </StorageDAOBundle> </StorageDAOBundles>