package thaw.plugins.index;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Iterator;
/* DOM */
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
/* SAX */
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import thaw.core.Logger;
import thaw.fcp.FreenetURIHelper;
import thaw.plugins.insertPlugin.DefaultMIMETypes;
public class IndexParser {
public final static String DATE_FORMAT = "yyyyMMdd";
private IndexContainer index;
public IndexParser(IndexContainer index) {
this.index = index;
}
public boolean generateXML(String path) {
try {
FileOutputStream stream = new FileOutputStream(new File(path));
generateXML(stream);
stream.close();
return true;
} catch(java.io.FileNotFoundException e) {
Logger.error(this, "File not found exception ?!");
} catch(java.io.IOException e) {
Logger.error(this, "IOException while generating the index: "+e.toString());
}
return false;
}
public void generateXML(final OutputStream out) {
StreamResult streamResult;
streamResult = new StreamResult(out);
Document xmlDoc;
final DocumentBuilderFactory xmlFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder xmlBuilder;
try {
xmlBuilder = xmlFactory.newDocumentBuilder();
} catch(final javax.xml.parsers.ParserConfigurationException e) {
Logger.error(this, "Unable to generate the index because : "+e.toString());
return;
}
final DOMImplementation impl = xmlBuilder.getDOMImplementation();
xmlDoc = impl.createDocument(null, "index", null);
final Element rootEl = xmlDoc.getDocumentElement();
/**** DOM Tree generation ****/
fillInRootElement(rootEl, xmlDoc);
/* Serialization */
final DOMSource domSource = new DOMSource(xmlDoc);
final TransformerFactory transformFactory = TransformerFactory.newInstance();
Transformer serializer;
try {
serializer = transformFactory.newTransformer();
} catch(final javax.xml.transform.TransformerConfigurationException e) {
Logger.error(this, "Unable to save index because: "+e.toString());
return;
}
serializer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
serializer.setOutputProperty(OutputKeys.INDENT,"yes");
/* final step */
try {
serializer.transform(domSource, streamResult);
} catch(final javax.xml.transform.TransformerException e) {
Logger.error(this, "Unable to save index because: "+e.toString());
return;
}
}
public boolean fillInRootElement(Element rootEl, Document xmlDoc) {
rootEl.appendChild(getXMLHeader(xmlDoc));
rootEl.appendChild(getXMLLinks(xmlDoc));
rootEl.appendChild(getXMLFileList(xmlDoc));
if (index.canHaveComments())
rootEl.appendChild(getXMLCommentInfos(xmlDoc));
return true;
}
private Element getXMLHeader(final Document xmlDoc) {
final Element header = xmlDoc.createElement("header");
final Element title = xmlDoc.createElement("title");
final Text titleText = xmlDoc.createTextNode(index.toString(false));
title.appendChild(titleText);
header.appendChild(title);
final Element clientVersion = xmlDoc.createElement("client");
final Text versionText = xmlDoc.createTextNode(index.getClientVersion());
clientVersion.appendChild(versionText);
header.appendChild(clientVersion);
if (index.publishPrivateKey() && index.getPrivateKey() != null) {
final Element privateKeyEl = xmlDoc.createElement("privateKey");
final Text privateKeyText = xmlDoc.createTextNode(index.getPrivateKey());
privateKeyEl.appendChild(privateKeyText);
header.appendChild(privateKeyEl);
}
/* insertion date */
String dateStr;
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat(DATE_FORMAT);
sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
dateStr = sdf.format(new java.util.Date());
final Element date = xmlDoc.createElement("date");
final Text dateText = xmlDoc.createTextNode(dateStr);
date.appendChild(dateText);
header.appendChild(date);
/* category */
String cat = index.getCategory();
if (cat != null) {
Element category = xmlDoc.createElement("category");
Text categoryText = xmlDoc.createTextNode(cat);
category.appendChild(categoryText);
header.appendChild(category);
}
/* TODO : Author */
return header;
}
private Element getXMLLinks(final Document xmlDoc) {
final Element linksEl = xmlDoc.createElement("indexes");
LinkContainer[] links = index.getLinkList();
for(int i = 0 ; i < links.length ; i++) {
LinkContainer link = links[i];
String key = index.findTheLatestKey(link.getPublicKey());
String cat = link.getCategory();
final Element xmlLink = xmlDoc.createElement("link");
xmlLink.setAttribute("key", key);
if (cat != null)
xmlLink.setAttribute("category", cat);
linksEl.appendChild(xmlLink);
}
return linksEl;
}
private Element getXMLFileList(final Document xmlDoc) {
final Element filesEl = xmlDoc.createElement("files");
FileContainer[] files = index.getFileList();
for(int i = 0 ; i < files.length ; i++) {
FileContainer file = (FileContainer)files[i];
String pubKey = file.getPublicKey();
if (pubKey == null)
continue;
pubKey = pubKey.trim();
if (!FreenetURIHelper.isAKey(pubKey)) {
Logger.notice(this, "One of the file key wasn't generated => not added");
continue;
}
final Element xmlFile = xmlDoc.createElement("file");
//xmlFile.setAttribute("id", set.getString("id"));
xmlFile.setAttribute("key", pubKey);
xmlFile.setAttribute("size", Long.toString(file.getSize()));
if (file.getMime() == null)
xmlFile.setAttribute("mime", DefaultMIMETypes.guessMIMEType(file.getFilename()));
else
xmlFile.setAttribute("mime", file.getMime());
filesEl.appendChild(xmlFile);
}
return filesEl;
}
private Element getXMLCommentInfos(final Document xmlDoc) {
final Element infos = xmlDoc.createElement("comments");
infos.setAttribute("publicKey", index.getCommentPublicKey());
if (index.getCommentPrivateKey() != null)
infos.setAttribute("privateKey", index.getCommentPrivateKey());
for (Iterator it = index.getCommentBlacklistedRev().iterator();
it.hasNext() ;) {
Integer rev = (Integer)it.next();
Element bl = xmlDoc.createElement("blackListed");
bl.setAttribute("rev", rev.toString());
infos.appendChild(bl);
}
return infos;
}
/**************************** INDEX LOADING *******************************/
public void loadXML(final String filePath) {
loadXML(filePath, true);
}
/**
* @param clean if set to false, will do a merge (won't call purgeIndex())
*/
public void loadXML(final String filePath, boolean clean) {
try {
FileInputStream stream = new FileInputStream(filePath);
loadXML(stream, clean);
stream.close();
stream = null;
} catch(final java.io.FileNotFoundException e) {
Logger.error(this, "Unable to load XML: FileNotFoundException ('"+filePath+"') ! : "+e.toString());
} catch(java.io.IOException e) {
Logger.error(this, "IOException while parsing the index: "+e.toString());
}
}
protected class IndexHandler extends DefaultHandler {
private boolean clean = true;
public IndexHandler() {
this(true);
}
public IndexHandler(boolean clean) {
this.clean = clean;
}
/**
* Called when parsing is started
* @see org.xml.sax.ContentHandler#startDocument()
*/
public void startDocument() throws SAXException {
if (clean)
index.purgeIndex();
}
private boolean ownerTag = false;
private boolean privateKeyTag = false;
private boolean dateTag = false;
private boolean categoryTag = false;
private boolean hasCommentTag = false;
private boolean clientTag = false;
private String dateStr = null;
private String categoryStr = null;
/**
* Called when the parsed find an opening tag
* @param localName local tag name
* @param rawName rawName (the one used here)
* @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
public void startElement(String nameSpaceURI, String localName,
String rawName, Attributes attrs) throws SAXException {
if (rawName == null) {
rawName = localName;
}
if (rawName == null)
return;
ownerTag = false;
privateKeyTag = false;
/* TODO : <title></title> */
if ("owner".equals(rawName)) {
ownerTag = true;
return;
} else if ("privateKey".equals(rawName)) {
privateKeyTag = true;
return;
} else if ("date".equals(rawName)) {
dateTag = true;
return;
} else if ("category".equals(rawName)) {
categoryTag = true;
return;
} else if ("link".equals(rawName)
|| "index".equals(rawName)) { /* links */
if (!index.addLink(attrs.getValue("key"), attrs.getValue("category"))) {
throw new SAXException("Index parsing interrupted because of a backend error");
}
return;
} else if ("file".equals(rawName)) {
if (!index.addFile(attrs.getValue("key"),
Long.parseLong(attrs.getValue("size")),
attrs.getValue("mime"))) {
throw new SAXException("Index parsing interrupted because of a backend error");
}
} else if ("comments".equals(rawName)) {
String pub = attrs.getValue("publicKey");
String priv = attrs.getValue("privateKey");
if (pub != null) {
hasCommentTag = true;
Logger.debug(this, "Comment allowed in this index");
index.setCommentKeys(pub, priv);
}
} else if ("client".equals(rawName)) {
clientTag = true;
return;
} else if ("blackListed".equals(rawName)) {
int blRev;
blRev = Integer.parseInt(attrs.getValue("rev"));
Logger.notice(this, "BlackListing rev '"+Integer.toString(blRev)+"'");
index.addBlackListedRev(blRev);
}
/* ignore unknown tags */
/* et paf ! Ca fait des Chocapics(tm)(r)(c)(m)(dtc) ! */
}
/**
* Called when a closing tag is met
* @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
*/
public void endElement(String nameSpaceURI, String localName,
String rawName) throws SAXException {
if (rawName == null) {
rawName = localName;
}
if (rawName == null)
return;
if ("owner".equals(rawName)) {
ownerTag = false;
return;
} else if ("privateKey".equals(rawName)) {
privateKeyTag = false;
return;
} else if ("date".equals(rawName)) {
dateTag = false;
return;
} else if ("category".equals(rawName)) {
categoryTag = false;
return;
} else if ("header".equals(rawName)) {
if (dateStr != null) {
java.text.SimpleDateFormat sdf =
new java.text.SimpleDateFormat(DATE_FORMAT);
java.util.Date dateUtil = sdf.parse(dateStr, new java.text.ParsePosition(0));
index.setInsertionDate(dateUtil);
}
if (categoryStr != null)
index.setCategory(categoryStr);
} else if ("comments".equals(rawName)) {
return;
} else if ("client".equals(rawName)) {
clientTag = false;
return;
}
}
/**
* Called when a text between two tag is met
* @param ch text
* @param start position
* @param end position
* @see org.xml.sax.ContentHandler#characters(char[], int, int)
*/
public void characters(char[] ch, int start, int end) throws SAXException {
String txt = new String(ch, start, end);
if (ownerTag) {
/* \_o< ==> TODO */
return;
}
if (dateTag) {
dateStr = txt;
}
if (categoryTag) {
categoryStr = txt;
}
if (clientTag) {
index.setClientVersion(txt);
}
if (privateKeyTag) {
if (index.getPrivateKey() == null
|| index.getPrivateKey().trim().equals(txt.trim())) {
/**
* the private key was published, we will have to do the same later
*/
index.setPublishPrivateKey(true);
} else {
/**
* the provided key doesn't match with the one we have,
* we won't publish it anymore
*/
Logger.warning(this, "A private key was provided, but didn't match with the one we have ; ignored.");
}
if (index.getPrivateKey() == null) {
String newPrivate = txt.trim();
/* check that nobody is trying to inject some FCP commands
* through the private key
*/
if (!FreenetURIHelper.isAKey(newPrivate)
|| newPrivate.indexOf('\n') >= 0) {
Logger.warning(this, "Invalid private key");
return;
}
index.setPrivateKey(newPrivate);
}
return;
}
/* ignore unkwown stuffs */
}
/**
* Called when parsing is finished
* @see org.xml.sax.ContentHandler#endDocument()
*/
public void endDocument() throws SAXException {
if (!hasCommentTag) {
Logger.debug(this, "No comment allowed in this index");
}
}
}
/**
* see import functionnality
*/
public IndexHandler getIndexHandler() {
return getIndexHandler(true);
}
public IndexHandler getIndexHandler(boolean clean) {
return new IndexHandler(clean);
}
public synchronized void loadXML(final java.io.InputStream input, boolean clean) {
IndexHandler handler = new IndexHandler(clean);
try {
// Use the default (non-validating) parser
SAXParserFactory factory = SAXParserFactory.newInstance();
// Parse the input
SAXParser saxParser = factory.newSAXParser();
Logger.info(this, "Parsing index ...");
saxParser.parse(input, handler );
Logger.info(this, "Parsing done");
} catch(javax.xml.parsers.ParserConfigurationException e) {
Logger.notice(this, "Error (1) while parsing index: "+e.toString());
} catch(org.xml.sax.SAXException e) {
Logger.notice(this, "Error (2) while parsing index: "+e.toString());
} catch(java.io.IOException e) {
Logger.notice(this, "Error (3) while parsing index: "+e.toString());
} catch(Exception e) {
Logger.notice(this, "Error (4) while parsing index: "+e.toString());
}
}
}