# HG changeset patch # User František Kučera # Date 1319097544 -7200 # Node ID d843b4fee5dc222eafb777655d122adcb45278bf # Parent d54786065fa34753f3d919935b95e30a8855753e Drupal: posílání zpráv do skupiny. diff -r d54786065fa3 -r d843b4fee5dc src/org/sonews/storage/DrupalMessage.java --- a/src/org/sonews/storage/DrupalMessage.java Wed Oct 19 21:40:51 2011 +0200 +++ b/src/org/sonews/storage/DrupalMessage.java Thu Oct 20 09:59:04 2011 +0200 @@ -18,6 +18,7 @@ package org.sonews.storage; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -45,6 +46,7 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; +import org.sonews.daemon.NNTPConnection; import org.sonews.util.io.Resource; /** @@ -66,6 +68,8 @@ private static final String XHTML_CONTENT_TYPE = "text/html; charset=" + CHARSET; private static final String ZNAKČKA_KONCE_ŘÁDKU = "◆"; private String messageID; + private Long parentID; + private Long groupID; /** * Constructs MIME message from SQL result. @@ -75,13 +79,14 @@ public DrupalMessage(ResultSet rs, String myDomain, boolean constructBody) throws SQLException, UnsupportedEncodingException, MessagingException { super(Session.getDefaultInstance(System.getProperties())); - addHeader("Message-id", constructMessageId(rs.getInt("id"), rs.getInt("group_id"), rs.getString("group_name"), myDomain)); + groupID = rs.getLong("group_id"); + addHeader("Message-id", constructMessageId(rs.getInt("id"), groupID, rs.getString("group_name"), myDomain)); addHeader("Newsgroups", rs.getString("group_name")); setFrom(new InternetAddress(rs.getString("sender_email"), rs.getString("sender_name"))); setSubject(rs.getString("subject")); setSentDate(new Date(rs.getLong("created"))); - int parentID = rs.getInt("parent_id"); + parentID = rs.getLong("parent_id"); if (parentID > 0) { String parentMessageID = constructMessageId(parentID, rs.getInt("group_id"), rs.getString("group_name"), myDomain); addHeader("In-Reply-To", parentMessageID); @@ -116,6 +121,38 @@ } } + /** + * Constructs MIME message from article posted by user. + * @param article article that came through NNTP. + * @throws MessagingException + */ + public DrupalMessage(Article article) throws MessagingException { + super(Session.getDefaultInstance(System.getProperties()), serializeArticle(article)); + + String[] parentHeaders = getHeader("In-Reply-To"); + if (parentHeaders.length == 1) { + String parentMessageID = parentHeaders[0]; + parentID = parseArticleID(parentMessageID); + groupID = parseGroupID(parentMessageID); + } else { + throw new MessagingException("Message posted by user must have exactly one In-Reply-To header."); + } + } + + private static InputStream serializeArticle(Article a) { + byte articleHeaders[] = a.getHeaderSource().getBytes(); + byte delimiter[] = (NNTPConnection.NEWLINE + NNTPConnection.NEWLINE).getBytes(); + byte body[] = a.getBody(); + + byte message[] = new byte[articleHeaders.length + delimiter.length + body.length]; + + System.arraycopy(articleHeaders, 0, message, 0, articleHeaders.length); + System.arraycopy(delimiter, 0, message, articleHeaders.length, delimiter.length); + System.arraycopy(body, 0, message, articleHeaders.length + delimiter.length, body.length); + + return new ByteArrayInputStream(message); + } + private String readPlainText(ResultSet rs, String xhtmlText) { try { TransformerFactory tf = TransformerFactory.newInstance(); @@ -255,7 +292,7 @@ return výsledek.toString(); } - private static String constructMessageId(int articleID, int groupID, String groupName, String domainName) { + public static String constructMessageId(long articleID, long groupID, String groupName, String domainName) { StringBuilder sb = new StringBuilder(); sb.append("<"); sb.append(articleID); @@ -269,6 +306,54 @@ return sb.toString(); } + /** + * @return article ID of parent of this message | or null, if this is root article and not reply to another one + */ + public Long getParentID() { + return parentID; + } + + /** + * @return group ID of this message | or null, if this message is not reply to any other one – which is wrong because we have to know the group + */ + public Long getGroupID() { + return groupID; + } + + /** + * + * @param messageID <{0}-{1}-{2}@domain.tld> where {0} is nntp_id and {1} is group_id and {2} is group_name + * @return array where [0] = nntp_id and [1] = group_id and [2] = group_name or returns null if messageID is invalid + */ + private static String[] parseMessageID(String messageID) { + if (messageID.matches("<[0-9]+\\-[0-9]+\\-[a-z0-9\\.]+@.+>")) { + return messageID.substring(1).split("@")[0].split("\\-"); + } else { + return null; + } + } + + public static Long parseArticleID(String messageID) { + String[] localPart = parseMessageID(messageID); + if (localPart == null) { + return null; + } else { + return Long.parseLong(localPart[0]); + } + } + + public static Long parseGroupID(String messageID) { + String[] localPart = parseMessageID(messageID); + if (localPart == null) { + return null; + } else { + return Long.parseLong(localPart[1]); + // If needed: + // parseGroupName() will be same as this method, just with: + // return localPart[2]; + } + } + @Override public void setHeader(String name, String value) throws MessagingException { super.setHeader(name, value); diff -r d54786065fa3 -r d843b4fee5dc src/org/sonews/storage/impl/DrupalDatabase.java --- a/src/org/sonews/storage/impl/DrupalDatabase.java Wed Oct 19 21:40:51 2011 +0200 +++ b/src/org/sonews/storage/impl/DrupalDatabase.java Thu Oct 20 09:59:04 2011 +0200 @@ -28,12 +28,13 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.sonews.config.Config; -import org.sonews.daemon.Connections; import org.sonews.feed.Subscription; import org.sonews.storage.Article; import org.sonews.storage.ArticleHead; import org.sonews.storage.DrupalArticle; import org.sonews.storage.DrupalMessage; +import static org.sonews.storage.DrupalMessage.parseArticleID; +import static org.sonews.storage.DrupalMessage.parseGroupID; import org.sonews.storage.Group; import org.sonews.storage.Storage; import org.sonews.storage.StorageBackendException; @@ -68,6 +69,18 @@ String password = Config.inst().get(Config.LEVEL_FILE, Config.STORAGE_PASSWORD, ""); conn = DriverManager.getConnection(url, username, password); + /** + * Kódování češtiny: + * SET NAMES utf8 → dobrá čeština + * Client characterset: utf8 + * Conn. characterset: utf8 + * SET CHARACTER SET utf8; → dobrá čeština jen pro SLECT, ale při volání funkce se zmrší + * Client characterset: utf8 + * Conn. characterset: latin1 + * + * Správné řešení: + * V JDBC URL musí být: ?useUnicode=true&characterEncoding=UTF-8 + */ conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); if (conn.getTransactionIsolation() != Connection.TRANSACTION_SERIALIZABLE) { log.warning("Database is NOT fully serializable!"); @@ -98,40 +111,6 @@ } } - /** - * - * @param messageID <{0}-{1}-{2}@domain.tld> where {0} is nntp_id and {1} is group_id and {2} is group_name - * @return array where [0] = nntp_id and [1] = group_id and [2] = group_name or returns null if messageID is invalid - */ - private static String[] parseMessageID(String messageID) { - if (messageID.matches("<[0-9]+\\-[0-9]+\\-[a-z0-9\\.]+@.+>")) { - return messageID.substring(1).split("@")[0].split("\\-"); - } else { - return null; - } - } - - private static Long parseArticleID(String messageID) { - String[] localPart = parseMessageID(messageID); - if (localPart == null) { - return null; - } else { - return Long.parseLong(localPart[0]); - } - } - - private static Long parseGroupID(String messageID) { - String[] localPart = parseMessageID(messageID); - if (localPart == null) { - return null; - } else { - return Long.parseLong(localPart[1]); - // If needed: - // parseGroupName() will be same as this method, just with: - // return localPart[2]; - } - } - @Override public List getGroups() throws StorageBackendException { PreparedStatement ps = null; @@ -397,7 +376,8 @@ * (but should not be thrown if only bad thing is wrong username or password) */ @Override - public boolean authenticateUser(String username, char[] password) throws StorageBackendException { + public boolean authenticateUser(String username, + char[] password) throws StorageBackendException { PreparedStatement ps = null; ResultSet rs = null; try { @@ -414,14 +394,81 @@ } } + /** + * Validates article and if OK, calls {@link #insertArticle(java.lang.String, java.lang.String, java.lang.String, java.lang.Long, java.lang.Long) } + * @param article + * @throws StorageBackendException + */ @Override - public void addArticle(Article art) throws StorageBackendException { - if (art.getAuthenticatedUser() == null) { + public void addArticle(Article article) throws StorageBackendException { + if (article.getAuthenticatedUser() == null) { log.log(Level.SEVERE, "User was not authenticated, so his article was rejected."); throw new StorageBackendException("User must be authenticated to post articles"); } else { + try { + DrupalMessage m = new DrupalMessage(article); - log.log(Level.INFO, "User ''{0}'' has posted an article", art.getAuthenticatedUser()); + Long parentID = m.getParentID(); + Long groupID = m.getGroupID(); + + if (parentID == null || groupID == null) { + throw new StorageBackendException("No valid In-Reply-To header was found → rejecting posted message."); + } else { + if (m.isMimeType("text/plain")) { + Object content = m.getContent(); + if (content instanceof String) { + String subject = m.getSubject(); + String text = (String) content; + + /** + * TODO: validovat a transformovat text + * (v současné době se o to stará až Drupal při výstupu) + */ + if (subject == null || subject.length() < 1) { + subject = text.substring(0, Math.min(10, text.length())); + } + + insertArticle(article.getAuthenticatedUser(), subject, text, parentID, groupID); + log.log(Level.INFO, "User ''{0}'' has posted an article", article.getAuthenticatedUser()); + } + } else { + throw new StorageBackendException("Only text/plain messages are supported for now – post it as plain text please."); + } + } + } catch (Exception e) { + throw new StorageBackendException(e); + } + } + } + + /** + * Physically stores article in database. + * @param subject + * @param text + * @param parentID + * @param groupID + */ + private void insertArticle(String sender, String subject, String text, Long parentID, Long groupID) throws StorageBackendException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + ps = conn.prepareStatement("SELECT nntp_post_article(?, ?, ?, ?, ?)"); + + ps.setString(1, sender); + ps.setString(2, subject); + ps.setString(3, text); + ps.setLong(4, parentID); + ps.setLong(5, groupID); + + rs = ps.executeQuery(); + rs.next(); + + Long articleID = rs.getLong(1); + log.log(Level.INFO, "Article was succesfully stored as {0}", articleID); + } catch (Exception e) { + throw new StorageBackendException(e); + } finally { + close(null, ps, rs); } }