// BlogBoard.java // ------------------------------------- // (C) by Michael Peter Christen; mc@yacy.net // first published on http://www.anomic.de // Frankfurt, Germany, 2004 // last major change: 20.07.2004 // // $LastChangedDate$ // $LastChangedRevision$ // $LastChangedBy$ // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // This file is contributed by Jan Sandbrink // based on the Code of wikiBoard.java package net.yacy.data; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import net.yacy.cora.date.GenericFormatter; import net.yacy.cora.document.encoding.UTF8; import net.yacy.cora.order.Base64Order; import net.yacy.cora.order.NaturalOrder; import net.yacy.cora.protocol.Domains; import net.yacy.cora.util.ConcurrentLog; import net.yacy.cora.util.SpaceExceededException; import net.yacy.data.wiki.WikiBoard; import net.yacy.kelondro.blob.MapHeap; import net.yacy.kelondro.util.kelondroException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class BlogBoard { private static final int KEY_LENGTH = 64; private MapHeap database = null; public BlogBoard(final File actpath) throws IOException { new File(actpath.getParent()).mkdir(); //database = new MapView(BLOBTree.toHeap(actpath, true, true, keyLength, recordSize, '_', NaturalOrder.naturalOrder, newFile), 500, '_'); this.database = new MapHeap(actpath, KEY_LENGTH, NaturalOrder.naturalOrder, 1024 * 64, 500, '_'); } public int size() { return this.database.size(); } /** * Tells if the database contains an element. * @param key the ID of the element * @return true if the database contains the element, else false */ public boolean contains(final String key) { return this.database.containsKey(UTF8.getBytes(key)); } public synchronized void close() { this.database.close(); } private static String normalize(final String key) { return (key == null) ? "null" : key.trim().toLowerCase(); } public static String webalize(final String key) { return (key == null) ? "null": key.trim().toLowerCase().replaceAll(" ", "%20"); } public String guessAuthor(final String ip) { return WikiBoard.guessAuthor(ip); } /** * Create a new BlogEntry and return it * @param key * @param subject * @param author * @param ip * @param date * @param page the content of the Blogentry * @param comments * @param commentMode possible params are: 0 - no comments allowed, 1 - comments allowed, 2 - comments moderated * @return BlogEntry */ public BlogEntry newEntry(final String key, final byte[] subject, final byte[] author, final String ip, final Date date, final byte[] page, final List<String> comments, final String commentMode) { return new BlogEntry(normalize(key), subject, author, ip, date, page, comments, commentMode); } /* * writes a new page and return the key */ public String writeBlogEntry(final BlogEntry page) { String ret = null; try { this.database.insert(UTF8.getBytes(page.key), page.record); ret = page.key; } catch (final IOException ex) { ConcurrentLog.logException(ex); } catch (final SpaceExceededException ex) { ConcurrentLog.logException(ex); } return ret; } public BlogEntry readBlogEntry(final String key) { return readBlogEntry(key, this.database); } private BlogEntry readBlogEntry(final String key, final MapHeap base) { final String normalized = normalize(key); Map<String, String> record; try { record = base.get(UTF8.getBytes(normalized.substring(0, Math.min(normalized.length(), KEY_LENGTH)))); } catch (final IOException e) { ConcurrentLog.logException(e); record = null; } catch (final SpaceExceededException e) { ConcurrentLog.logException(e); record = null; } return (record == null) ? newEntry(key, new byte[0], UTF8.getBytes("anonymous"), Domains.LOCALHOST, new Date(), new byte[0], null, null) : new BlogEntry(key, record); } public boolean importXML(final String input) { if (input != null && !input.isEmpty()) { final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { final DocumentBuilder builder = factory.newDocumentBuilder(); return parseXMLimport(builder.parse(new ByteArrayInputStream(UTF8.getBytes(input)))); } catch (final ParserConfigurationException ex) { ConcurrentLog.logException(ex); } catch (final SAXException ex) { ConcurrentLog.logException(ex); } catch (final IOException ex) { ConcurrentLog.logException(ex); } } return false; } private boolean parseXMLimport(final Document doc) { if(!"blog".equals(doc.getDocumentElement().getTagName())) { return false; } final NodeList items = doc.getDocumentElement().getElementsByTagName("item"); if(items.getLength() == 0) { return false; } for (int i = 0, n = items.getLength(); i < n; ++i) { String key = null, ip = null, StrSubject = null, StrAuthor = null, StrPage = null, StrDate = null; Date date = null; if(!"item".equals(items.item(i).getNodeName())) continue; final NodeList currentNodeChildren = items.item(i).getChildNodes(); for (int j = 0, m = currentNodeChildren.getLength(); j < m; ++j) { final Node currentNode = currentNodeChildren.item(j); if ("id".equals(currentNode.getNodeName())) { key = currentNode.getFirstChild().getNodeValue(); } else if ("ip".equals(currentNode.getNodeName())) { ip = currentNode.getFirstChild().getNodeValue(); } else if ("timestamp".equals(currentNode.getNodeName())) { StrDate = currentNode.getFirstChild().getNodeValue(); } else if ("subject".equals(currentNode.getNodeName())) { StrSubject = currentNode.getFirstChild().getNodeValue(); } else if ("author".equals(currentNode.getNodeName())) { StrAuthor = currentNode.getFirstChild().getNodeValue(); } else if ("content".equals(currentNode.getNodeName())) { StrPage = currentNode.getFirstChild().getNodeValue(); } } try { date = GenericFormatter.SHORT_SECOND_FORMATTER.parse(StrDate, 0).getTime(); } catch (final ParseException e1) { date = new Date(); } if(key == null || ip == null || StrSubject == null || StrAuthor == null || StrPage == null || date == null) { return false; } byte[] subject,author,page; subject = UTF8.getBytes(StrSubject); author = UTF8.getBytes(StrAuthor); page = UTF8.getBytes(StrPage); writeBlogEntry (newEntry(key, subject, author, ip, date, page, null, null)); } return true; } public void deleteBlogEntry(final String key) { try { this.database.delete(UTF8.getBytes(normalize(key))); } catch (final IOException e) { } } public Iterator<byte[]> keys(final boolean up) throws IOException { return this.database.keys(up, false); } /** * Comparator to sort objects of type Blog according to their timestamps */ public class BlogComparator implements Comparator<String> { private final boolean newestFirst; /** * @param newestFirst newest first, or oldest first? */ public BlogComparator(final boolean newestFirst){ this.newestFirst = newestFirst; } @Override public int compare(final String obj1, final String obj2) { final BlogEntry blogEntry1 = readBlogEntry(obj1); final BlogEntry blogEntry2 = readBlogEntry(obj2); if (blogEntry1 == null || blogEntry2 == null) return 0; if (this.newestFirst) { if (Long.parseLong(blogEntry2.getTimestamp()) - Long.parseLong(blogEntry1.getTimestamp()) > 0) { return 1; } return -1; } if (Long.parseLong(blogEntry1.getTimestamp()) - Long.parseLong(blogEntry2.getTimestamp()) > 0) { return 1; } return -1; } } public Iterator<String> getBlogIterator(final boolean priv){ final Set<String> set = new TreeSet<String>(new BlogComparator(true)); final Iterator<BlogEntry> iterator = blogIterator(true); BlogEntry blogEntry; while (iterator.hasNext()) { blogEntry = iterator.next(); if (priv || blogEntry.isPublic()) { set.add(blogEntry.getKey()); } } return set.iterator(); } public Iterator<BlogEntry> blogIterator(final boolean up){ try { return new BlogIterator(up); } catch (final IOException e) { return new HashSet<BlogEntry>().iterator(); } } /** * Subclass of blogBoard, which provides the blogIterator object-type */ public class BlogIterator implements Iterator<BlogEntry> { Iterator<byte[]> blogIter; //blogBoard.BlogEntry nextEntry; public BlogIterator(final boolean up) throws IOException { this.blogIter = BlogBoard.this.database.keys(up, false); //this.nextEntry = null; } @Override public boolean hasNext() { try { return this.blogIter.hasNext(); } catch (final kelondroException e) { //resetDatabase(); return false; } } @Override public BlogEntry next() { try { return readBlogEntry(UTF8.String(this.blogIter.next())); } catch (final kelondroException e) { //resetDatabase(); return null; } } @Override public void remove() { // if (this.nextEntry != null) { // try { // final Object blogKey = this.nextEntry.getKey(); // if (blogKey != null) deleteBlogEntry((String) blogKey); // } catch (final kelondroException e) { // //resetDatabase(); // } // } throw new UnsupportedOperationException("Method not implemented yet."); } } public class BlogEntry { String key; Map<String, String> record; public BlogEntry(final String nkey, final byte[] subject, final byte[] author, final String ip, final Date date, final byte[] page, final List<String> comments, final String commentMode) { this.record = new HashMap<String, String>(); setKey(nkey); setDate(date); setSubject(subject); setAuthor(author); setIp(ip); setPage(page); setComments(comments); setCommentMode(commentMode); // TODO: implement this function this.record.put("privacy", "public"); WikiBoard.setAuthor(ip, UTF8.String(author)); } BlogEntry(final String key, final Map<String, String> record) { this.key = key; this.record = record; if (this.record.get("comments") == null) { this.record.put("comments", ListManager.collection2string(new ArrayList<String>())); } if (this.record.get("commentMode") == null || this.record.get("commentMode").length() < 1) { this.record.put("commentMode", "2"); } } private void setKey(final String key) { this.key = key.substring(0, Math.min(key.length(), KEY_LENGTH)); } public String getKey() { return this.key; } public byte[] getSubject() { final String m = this.record.get("subject"); if (m == null) { return new byte[0]; } final byte[] b = Base64Order.enhancedCoder.decode(m); return (b == null) ? new byte[0] : b; } private void setSubject(final byte[] subject) { if (subject == null) { this.record.put("subject",""); } else { this.record.put("subject", Base64Order.enhancedCoder.encode(subject)); } } public Date getDate() { try { final String date = this.record.get("date"); if (date == null) { if (ConcurrentLog.isFinest("Blog")) { ConcurrentLog.finest("Blog", "ERROR: date field missing in blogBoard"); } return new Date(); } return GenericFormatter.SHORT_SECOND_FORMATTER.parse(date, 0).getTime(); } catch (final ParseException ex) { return new Date(); } } private void setDate(final Date date) { Date ret = date; if (ret == null) { ret = new Date(); } this.record.put("date", GenericFormatter.SHORT_SECOND_FORMATTER.format(ret)); } public String getTimestamp() { final String timestamp = this.record.get("date"); if (timestamp == null) { if (ConcurrentLog.isFinest("Blog")) { ConcurrentLog.finest("Blog", "ERROR: date field missing in blogBoard"); } return GenericFormatter.SHORT_SECOND_FORMATTER.format(); } return timestamp; } public byte[] getAuthor() { final String author = this.record.get("author"); if (author == null) { return new byte[0]; } final byte[] b = Base64Order.enhancedCoder.decode(author); return (b == null) ? new byte[0] : b; } private void setAuthor(final byte[] author) { if (author == null) this.record.put("author",""); else this.record.put("author", Base64Order.enhancedCoder.encode(author)); } public int getCommentsSize() { // This ist a Bugfix for Version older than 4443. if (this.record.get("comments").startsWith(",")) { this.record.put("comments", this.record.get("comments").substring(1)); writeBlogEntry(this); } final List<String> commentsize = ListManager.string2arraylist(this.record.get("comments")); return commentsize.size(); } public List<String> getComments() { return ListManager.string2arraylist(this.record.get("comments")); } private void setComments(final List<String> comments) { if (comments == null) { this.record.put("comments", ListManager.collection2string(new ArrayList<String>())); } else { this.record.put("comments", ListManager.collection2string(comments)); } } public String getIp() { final String ip = this.record.get("ip"); return (ip == null) ? Domains.LOCALHOST : ip; } private void setIp(final String ip) { String ret = ip; if ((ret == null) || (ret.isEmpty())) ret = ""; this.record.put("ip", ret); } public byte[] getPage() { final String page = this.record.get("page"); if (page == null) { return new byte[0]; } final byte[] page_as_byte = Base64Order.enhancedCoder.decode(page); return (page_as_byte == null) ? new byte[0] : page_as_byte; } private void setPage(final byte[] page) { if (page == null) { this.record.put("page", ""); } else { this.record.put("page", Base64Order.enhancedCoder.encode(page)); } } public void addComment(final String commentID) { final List<String> comments = ListManager.string2arraylist(this.record.get("comments")); comments.add(commentID); this.record.put("comments", ListManager.collection2string(comments)); } public boolean removeComment(final String commentID) { final List<String> comments = ListManager.string2arraylist(this.record.get("comments")); final boolean success = comments.remove(commentID); this.record.put("comments", ListManager.collection2string(comments)); return success; } /** * returns the comment mode * 0 - no comments allowed * 1 - comments allowed * 2 - comments moderated * @return comment mode */ public int getCommentMode(){ return Integer.parseInt(this.record.get("commentMode")); } private void setCommentMode(final String mode) { if (mode == null) { this.record.put("commentMode", "2"); } else { this.record.put("commentMode", mode); } } public boolean isPublic() { final String privacy = this.record.get("privacy"); return (privacy == null || privacy.equalsIgnoreCase("public")) ? true : false; } } }