6

When DCP is published, the DCP is stored to Broker DB and also to custom DB by Storage Extension. To incarnate the above, I implement the program as below.

Furthermore, To confirm whether executing storing to Broker DB and custom DB in same transaction is possible or not I have followed below steps -

First, I have confirmed whether storing DCP to Broker DB is rollbacked or not if some exception occurs in Custom DB. As a result, I know it is possible.

Second, I have confirmed that storing DCP to Custom DB is rollbacked or not if some exception occurs in Broker DB.

When SQLGrammarException occurs in Broker DB(As a result Committing Deployment phase is failed), I have confirmed that storing DCP to Custom DB and Broker DB is not occured. (JPAComponentPresentationDAOImpl class even isn't called because No log is output.)

But I want to confirm whether custom DB is rollbacked or not when super.create(or super.remove) method on JPAComponentPresentationDAOImpl class throws StorageException.

How to cause exception on super.create method ?

@Component("JPAComponentPresentationDAOImpl")
@Scope("prototype")
public class JPAComponentPresentationDAOImpl extends
        JPAComponentPresentationDAO implements ComponentPresentationDAO {
    public JPAComponentPresentationDAOImpl(String storageId,
            EntityManagerFactory entityManagerFactory,
            EntityManager entityManager, String storageName) {
        super(storageId, entityManagerFactory, entityManager, storageName);
        outputLog("JPAComponentPresentationDAOImpl コンストラクタ1");
    }

public JPAComponentPresentationDAOImpl(String storageId,
        EntityManagerFactory entityManagerFactory, String storageName) {
    super(storageId, entityManagerFactory, storageName);
    outputLog("JPAComponentPresentationDAOImpl コンストラクタ2");
}

/**
 * @param args
 */
public static void main(String[] args) {
    // TODO 自動生成されたメソッド・スタブ
}

// this method is called when a DCP is Published or Re-Published
public void create(ComponentPresentation componentPresentation,
        ComponentPresentationTypeEnum type) throws StorageException {
    super.create(componentPresentation, type);
    if (componentPresentation.getTemplateId() == 176260) {
        connectDB(componentPresentation, "create",
                componentPresentation.getComponentId());
    }
}

public Collection<ComponentPresentation> findAll(int publicationId,
        int componentId,
        ComponentPresentationTypeEnum componentPresentationType)
        throws StorageException {
    outputLog("findAll");

    return super.findAll(publicationId, componentId,
            componentPresentationType);
}

public ComponentPresentation getComponentPresentation(int publicationId,
        int componentId, int templateId,
        ComponentPresentationTypeEnum componentPresentationType)
        throws StorageException {
    if (templateId == 176260) {
        outputLog("getComponentPresentation");
    }
    return super.getComponentPresentation(publicationId, componentId,
            templateId, componentPresentationType);
    // return null;
}

public void remove(ComponentPresentation componentPresentation,
        ComponentPresentationTypeEnum type) throws StorageException {
    outputLog("Remove1");
    super.remove(componentPresentation, type);
}

// this method is called when a DCP is UnPublished
public void remove(int publicationId, int componentId, int templateId,
        ComponentPresentationTypeEnum componentPresentationType)
        throws StorageException {
    super.remove(publicationId, componentId, templateId,
            componentPresentationType);
    if (templateId == 176260) {
        outputLog("Remove2");
        connectDB(null, "remove", componentId);
    }
}

public void update(ComponentPresentation componentPresentation,
        ComponentPresentationTypeEnum componentPresentationType)
        throws StorageException {
    outputLog("update");
    super.update(componentPresentation, componentPresentationType);
}

// start custom code
// output logfile
private static void outputLog(String msg) {
    try {
        File f = new File("/tmp/Results.log");
        BufferedWriter bw = new BufferedWriter(new FileWriter(f, true));
        bw.write(msg);
        bw.newLine();
        bw.close();
    } catch (IOException e) {
    }
}

// get XML and get NodeValues
private static ArrayList<String> getXml(ComponentPresentation componentPresentation){
    // my custom code (to get XML and XMLNodeValues)    
}

// connect to DB and executeUpdate
private static void connectDB(ComponentPresentation componentPresentation, String query, int componentId) {

    Connection conn = null;
    PreparedStatement ps = null;
    ResultSet rset = null;
    ArrayList<String> param = getXml(componentPresentation);

    try {
        // get driver
        Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");

        // connect to sqlserver
        String connUrl = getCnnectString();
        conn = DriverManager.getConnection(connUrl);

        conn.setAutoCommit(false);

        // SELECT
        if ("create".equals(query)) {
            String sql = "SELECT * FROM a WHERE id = ? ;";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, componentId);
            rset = ps.executeQuery();
        }

        // INSERT or UPDATE
        if ("create".equals(query)) {
            // INSERT
            if (!rset.next()) {
                String sql = "INSERT INTO a VALUES (?,?,?,?,?,?,?);";
                ps = conn.prepareStatement(sql);
                ps.setInt(1, componentId);
                for (int i = 0; i < param.size(); i++) {
                    ps.setNString(i + 2, param.get(i));
                }
                outputLog("insert");
            }
            // UPDATE
            else {
                String sql = "UPDATE a SET name = ?, desc = ?, c_at = ?, c_by = ?, u_at = ?,u_by = ? WHERE id = ? ;";
                ps = conn.prepareStatement(sql);
                for (int i = 0; i < param.size(); i++) {
                    ps.setNString(i + 1, param.get(i));
                }
                ps.setInt(7, componentId);
                outputLog("update");
            }
        }

        // DELETE
        if ("remove".equals(query)) {
            String sql = "DELETE FROM a WHERE id =? ;";
            ps = conn.prepareStatement(sql);
            ps.setInt(1, componentId);
            outputLog("delete");
        }

        // executeUpdate
        ps.executeUpdate();// INSERT, UPDATE, DELETE statment execute
        outputLog("Custom DB commit");
        conn.commit();

    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
        try {
            conn.rollback();
            outputLog("throw SQL Exceptions");
            MyJPADAOFactory.setIsFailed(true);
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        outputLog(e.getMessage());
    } finally {
        if (rset != null) {
            try {
                rset.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

// set DB info to connect DB
private static String getCnnectString() {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    String ConnectString = "";
    try {
        DocumentBuilder builder = factory.newDocumentBuilder();
        File file = new File("/tmp/ConnectDBInfo.xml");
        Document doc = (Document) builder.parse(file);
        Element root = doc.getDocumentElement();
        ConnectString = root.getAttribute("connection");
    } catch (ParserConfigurationException e) {
        e.printStackTrace();
    } catch (SAXException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return ConnectString;
}
}










@Component("MyJPADAOFactory")
@Scope("singleton")
public class MyJPADAOFactory extends JPADAOFactory implements
        ApplicationContextAware {
    private static ApplicationContext APPLICATION_CONTEXT;
    private static boolean isFailed = false;

public MyJPADAOFactory(String storageId, String dialect) {
    super(storageId, dialect);
    outputLog("MyJPADAOFactoryコンストラクタ");
}

public MyJPADAOFactory() {
    super("", "");
}

public static void setIsFailed(boolean failed) {
    isFailed = failed;
}

public void commitTransaction(String transactionId) throws StorageException {
    outputLog("commitTransaction開始");
    if (isFailed) {
        outputLog("ロールバック");
        super.rollbackTransaction(transactionId);
        throw new StorageException("MyJPADAOFactory : transaction is rollback");
    } else {
        outputLog("コミット");
        super.commitTransaction(transactionId);
    }
}

@Override
public void setApplicationContext(ApplicationContext applicationContext)
        throws BeansException {
    APPLICATION_CONTEXT = applicationContext;
}

public void configureBundle(Configuration storageDAOBundleConfiguration) {
    // first set the right value for the private field
    // called 'applicationContext'
    try {
        setPrivateField(this, "applicationContext", APPLICATION_CONTEXT);
    } catch (NoSuchFieldException e) {
        // TODO Auto-generated catch block
    } catch (IllegalAccessException e) {
        // TODO Auto-generated catch block
    }
    // configure the bundle like we normally do

    try {
        super.configureBundle(storageDAOBundleConfiguration);
    } catch (ConfigurationException e) {
        e.printStackTrace();
    }

}

private static void setPrivateField(final Object fieldOwner,
        final String fieldName, final Object value)
        throws NoSuchFieldException, IllegalAccessException {
    final Field privateField = getPrivateFieldRec(fieldOwner.getClass(),
            fieldName);

    if (privateField != null) {
        final boolean accesible = privateField.isAccessible();
        privateField.setAccessible(true);

        privateField.set(fieldOwner, value);

        privateField.setAccessible(accesible);
    }
}

private static Field getPrivateFieldRec(final Class<?> clazz,
        final String fieldName) {
    for (Field field : clazz.getDeclaredFields()) {
        if (fieldName.equals(field.getName())) {
            return field;
        }
    }
    final Class<?> superClazz = clazz.getSuperclass();

    if (superClazz != null) {
        return getPrivateFieldRec(superClazz, fieldName);
    }

    return null;
}

/**
 * @param args
 */
public static void main(String[] args) {
    // TODO 自動生成されたメソッド・スタブ
}

private static void outputLog(String msg) {
    try {
        File f = new File("/tmp/TransactionResult.log");
        BufferedWriter bw = new BufferedWriter(new FileWriter(f, true));
        bw.write(msg);
        bw.newLine();
        bw.close();
    } catch (IOException e) {
    }
}
}

Update

thank you for answering my question. your understanding is not different, but right. I don't well understand when JPAComponentPresentationDAOImpl class is called during comitting deployment phase .

You should have written your create method (in the storage extension) with in the try-catch block so if the super.create generate any exception, the transaction will automatically got rolled back.

is it mean...

Before

public void create(ComponentPresentation componentPresentation,
        ComponentPresentationTypeEnum type) throws StorageException {
    super.create(componentPresentation, type);
    if (componentPresentation.getTemplateId() == 176260) {
        connectDB(componentPresentation, "create",
                componentPresentation.getComponentId());
    }
}

After

public void create(ComponentPresentation componentPresentation,
        ComponentPresentationTypeEnum type) throws StorageException {

    try{
    super.create(componentPresentation, type);
    if (componentPresentation.getTemplateId() == 176260) {
        connectDB(componentPresentation, "create",
                componentPresentation.getComponentId());
    }
    }
    catch(StorageException e)
    {
    }
}

and I have another problem.

if super.commitTransaction(transactionId) on commitTransaction method (MyJPADAOFactory class) throws exception, I think it's impossible to rollback custom DB, because custom DB is committed before call super.commitTransaction(transactionId).

Can I solve this problem ?

2 Answers 2

6

Your code looks really familiar ;) . If super.create() fails, then the super class throws an exception and the entire transaction is rolled back. If you throw an exception in your connectDB method, the entire transaction be rolled back as well. So, instead of doing an e.printStackTrace everywhere, you should throw the exception.

If that doesn't work, have a look at this Storage Extension code, which essentially does the same as you are trying to do, but it instead calls the 'extra' action inside the commitTransaction() method after the super method has committed.

Another, and maybe simpler, alternative is to first call your updateDB method and then the super.create() method. In this case you will also need to throw Exceptions rather than eating them up.

4
  • thank you for answering my question. when first call updateDB method and then super.create(), is it possible to rollback custom DB!? when my custom method(connect DB method) is called, custom DB is committed(if not any exception), so if super.create() method throws some exception after custom DB is committed, is it impossible to rollback custom DB !?
    – Arai Yuhei
    Commented Oct 17, 2013 at 9:57
  • Yes and no. If you first call super.create() and there is an error -> throw exception, then updateDB is not called. if updateDB is called first and there is an error -> throw an exception and super.create is not called. If super.create is called first and everything is ok and then updateDB is called and there is an error -> throw exception -> super.create is rolled back.
    – Raimond
    Commented Oct 17, 2013 at 10:15
  • thank you to answer my question. Ah, I don't know well difference between (first super.create, second connectDB method) and (first connectDB, second super.create). I understand that finally custom commitTransaction method (on MyJPADAOFactory class) is called and execute super.commitTransaction or super.rollbackTransaction judging isFailed (variable) is true or false. is this my understanding is wrong!? Maybe I'm misunderstood.
    – Arai Yuhei
    Commented Oct 17, 2013 at 11:23
  • Your understanding is sort of correct. But, the DAO classes are initialized, loaded and executed (!) before the Factory commits the transaction. A commit just means that the whole deployment is ready to be finalized and stored. So anything in a normal Tridion deployment that is set up before commit is already called. So, if you want to update your custom database after the normal super.commitTransaction is executed, check out the mechanism I wrote to be able to do this. I would never do a super.rollback myself in any case, as that is all handled for you in the commitTransaction's super method.
    – Raimond
    Commented Oct 17, 2013 at 12:04
3

My question: Why do you want to rollback transaction of custom DB if super.create has failed? As you have called the super.create as the first call, and if there is any exception, your code for custom DB and so the related transaction would have not get executed. Isn't it?

You should have written your create method (in the storage extension) with in the try-catch block so if the super.create generate any exception, the transaction will automatically got rolled back and the the code for your custom DB would have not even been executed and so there should be no need of Rollback of your custom DB. Let me know if my understanding of your problem is different

UPDATE:

Post update I got your concern. I think you may proceed with the suggestion made by Raimond of calling your method for updating custom DB first and then calling the super.create.

Alternatively(depending on what actually you visioned to do), you may write your Create method in Storage Extension in try-catch block as suggested earlier and in the catch block, you need to write logic to check whether a transaction has been done in the Custom DB, if it is you may need to call another stored procedure in Custom DB which undo the previous action

1
  • thank you for answering my question. I update my question. May i read my question again. i'm sorry to cause you trouble. (your understanding is not different.)
    – Arai Yuhei
    Commented Oct 17, 2013 at 9:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.