/**
*
*/
package org.concord.otrunk;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.concord.framework.otrunk.OTChangeEvent;
import org.concord.framework.otrunk.OTChangeListener;
import org.concord.framework.otrunk.OTChangeLogger;
import org.concord.framework.otrunk.OTChangeNotifying;
import org.concord.framework.otrunk.OTObject;
import org.concord.framework.otrunk.OTObjectService;
import org.concord.framework.otrunk.otcore.OTClass;
import org.concord.framework.otrunk.otcore.OTClassProperty;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jdom.input.SAXHandler;
import org.jdom.xpath.XPath;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
/**
* This extends TraceListener so it will be ignored in all the same places that
* tracelistener is ignored.
*
* To reduce duplicate information this could just store the old value
* not the new one. The newest value will be in the learner data.
* However this will require storing the lastModified information in the learner data.
* Except for the fact that we won't know which property changed in that last modification
* so then the log won't reflect that. So in conclusion it is better to log everything.
*
* Another issue with this log approach is trying to find an old version of an object. If you
* want to look at an old version of a graph you need to access the whole collection of graph
* objects. With the log this could be done by replaying the log up to the point that is
* necessary. But this would be slow if done a lot. And there are issues of having 2 versions
* of the same object available at the same time. I guess it could be done as an overlay.
*
* @author scott
*
*/
public class XMLChangeLogger
implements OTObjectServiceListener, OTChangeListener, InternalListener
{
private ContentHandler saxContentHandler;
private PrintWriter writer;
private SAXHandler saxHandler;
/**
* @param label
* @throws TransformerConfigurationException
* @throws SAXException
*/
public XMLChangeLogger(OutputStream out) throws TransformerConfigurationException, SAXException
{
writer = new PrintWriter(out);
StreamResult streamResult = new StreamResult(writer);
SAXTransformerFactory tf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
TransformerHandler transformerHandler2 = tf.newTransformerHandler();
Transformer serializer = transformerHandler2.getTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
serializer.setOutputProperty(OutputKeys.INDENT,"yes");
transformerHandler2.setResult(streamResult);
saxContentHandler = transformerHandler2;
start();
}
public XMLChangeLogger(ContentHandler contentHandler) throws SAXException
{
this.saxContentHandler = contentHandler;
start();
}
protected void start() throws SAXException
{
saxContentHandler.startDocument();
AttributesImpl atts = new AttributesImpl();
saxContentHandler.startElement("", "log", "log", atts);
}
/* (non-Javadoc)
* @see org.concord.otrunk.OTObjectServiceListener#objectLoaded(org.concord.framework.otrunk.OTObject)
*/
public void objectLoaded(OTObject object)
{
if(object instanceof OTChangeNotifying) {
((OTChangeNotifying)object).addOTChangeListener(this);
}
}
/* (non-Javadoc)
* @see org.concord.framework.otrunk.OTChangeListener#stateChanged(org.concord.framework.otrunk.OTChangeEvent)
*/
public void stateChanged(OTChangeEvent e)
{
OTObject source = (OTObject) e.getSource();
OTClass otClass = source.otClass();
String className = otClass.getName();
int lastDot = className.lastIndexOf(".");
String localClassName = className.substring(lastDot+1,className.length());
String propertyStr = e.getProperty();
OTClassProperty otClassProperty = otClass.getProperty(propertyStr);
AttributesImpl atts = new AttributesImpl();
try {
atts.addAttribute("", "id", "id", "CDATA", source.otExternalId());
saxContentHandler.startElement("", localClassName, localClassName, atts);
atts.clear();
atts.addAttribute("", "op", "op", "CDATA", e.getOperation());
atts.addAttribute("", "time", "time", "CDATA", "" + System.currentTimeMillis());
saxContentHandler.startElement("",propertyStr,propertyStr, atts);
String value = null;
if(otClassProperty.isPrimitive()){
value = e.getValue().toString();
} else {
Object eventValue = e.getValue();
if(eventValue instanceof OTObject){
atts.clear();
atts.addAttribute("", "refid", "refid", "CDATA", ((OTObject)eventValue).otExternalId());
saxContentHandler.startElement("","object","object", atts);
saxContentHandler.endElement("", "object", "object");
} else if(eventValue != null){
value = "unknown_value: " + eventValue.toString();
}
}
if(value != null){
saxContentHandler.characters(value.toCharArray(), 0, value.length());
}
atts.addAttribute("", propertyStr, propertyStr, "CDATA", value);
saxContentHandler.endElement("",propertyStr,propertyStr);
saxContentHandler.endElement("", localClassName, localClassName);
} catch (SAXException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(writer != null){
writer.flush();
}
}
/**
* This is a temporary test method. Using its implementation of OTChangeLogger requires Jaxen on
* the classpath.
*/
public OTChangeLogger tryQueriableLog(OTObjectService objectService)
{
saxHandler = new SAXHandler();
try {
XMLChangeLogger changeLogger = new XMLChangeLogger(saxHandler);
((OTObjectServiceImpl)objectService).addObjectServiceListener(changeLogger);
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
OTChangeLogger changeLoggerService = new OTChangeLogger()
{
/**
* FIXME This method is not complete it always returns null
*
* @see org.concord.framework.otrunk.OTChangeLogger#getPreviousValues(org.concord.framework.otrunk.OTObject, org.concord.framework.otrunk.otcore.OTClassProperty)
*/
@SuppressWarnings("unchecked")
public Iterator getPreviousValues(OTObject otObject, OTClassProperty property)
{
Document document = saxHandler.getDocument();
XPath xpath;
try {
// /log/*[@id='33754150-b594-11d9-9669-0800200c9a66!/normal_choice']/currentChoice[@op='set']/node()
// this code is not complete has not been tested,
xpath = XPath.newInstance("/log/*[@id='"+ otObject.otExternalId() + "']/" +
property.getName() + "[@op='set']/node()");
List selectNodes = xpath.selectNodes(document);
for(Iterator it = selectNodes.iterator();it.hasNext();){
@SuppressWarnings("unused")
Object node = it.next();
}
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// TODO Auto-generated method stub
return null;
}
};
return changeLoggerService;
}
/**
* This is a temporary test method. It is specific to a particular otml file. Using
* it requires jaxen on the classapth.
*/
@SuppressWarnings("unchecked")
public void testXPathQuery()
{
Document document = saxHandler.getDocument();
try {
XPath xpath = XPath.newInstance("/log/*[@id='33754150-b594-11d9-9669-0800200c9a66!/normal_choice']/" +
"currentChoice[@op='set']/node()");
List selectNodes = xpath.selectNodes(document);
System.out.println("found:" + selectNodes);
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}