From e6e94f3427a07cfc14fde0f809648f6cb3cd5469 Mon Sep 17 00:00:00 2001 From: "Eric J. Bowersox" Date: Wed, 9 Jun 2004 04:10:52 +0000 Subject: [PATCH] first draft of message importing and exporting to/from conferences (new host tools) --- build.properties.sample | 5 + build.xml | 7 + etc/ui-config.xml | 6 + scripts/conf/export.js | 103 ++ scripts/conf/import.js | 97 ++ .../util/Base64EncodeInputStream.java | 194 +++ src/com/silverwrist/util/IOUtil.java | 28 +- .../venice/core/ConferenceContext.java | 15 +- .../venice/core/TopicMessageContext.java | 4 + .../core/impl/ConferenceUserContextImpl.java | 94 +- .../core/impl/ConferencingExporter.java | 220 +++ .../core/impl/ConferencingImporter.java | 1293 +++++++++++++++++ .../core/impl/PublishedMessageImpl.java | 11 +- .../impl/TopicMessageUserContextImpl.java | 147 +- .../core/impl/TopicUserContextImpl.java | 15 +- src/com/silverwrist/venice/db/SQLUtil.java | 4 +- .../venice/ui/conf/AttachmentContent.java | 28 +- .../venice/ui/servlet/BaseServlet.java | 19 +- web/format/conf/export.jsp | 60 + web/format/conf/import.jsp | 53 + web/format/conf/import_results.jsp | 48 + web/images/classic/export.jpg | Bin 0 -> 2616 bytes web/images/classic/import.jpg | Bin 0 -> 2579 bytes web/images/gelcap/export.jpg | Bin 0 -> 3039 bytes web/images/gelcap/import.jpg | Bin 0 -> 3204 bytes 25 files changed, 2422 insertions(+), 29 deletions(-) create mode 100644 scripts/conf/export.js create mode 100644 scripts/conf/import.js create mode 100644 src/com/silverwrist/util/Base64EncodeInputStream.java create mode 100644 src/com/silverwrist/venice/core/impl/ConferencingExporter.java create mode 100644 src/com/silverwrist/venice/core/impl/ConferencingImporter.java create mode 100644 web/format/conf/export.jsp create mode 100644 web/format/conf/import.jsp create mode 100644 web/format/conf/import_results.jsp create mode 100644 web/images/classic/export.jpg create mode 100644 web/images/classic/import.jpg create mode 100644 web/images/gelcap/export.jpg create mode 100644 web/images/gelcap/import.jpg diff --git a/build.properties.sample b/build.properties.sample index b7cf3c6..6fb7ae3 100644 --- a/build.properties.sample +++ b/build.properties.sample @@ -35,6 +35,11 @@ collections.base=/usr/local/java/commons-collections-2.1 # collections.lib=${collections.base} # collections.jarfile=commons-collections.jar +# [Location of Commons Codec Library 1.2] +codec.base=/usr/local/java/commons-codec-1.2 +# codec.lib=${codec.base} +# codec.jarfile=commons-codec-1.2.jar + # [Location of Jakarta Regexp Library 1.3] regexp.base=/usr/local/java/jakarta-regexp-1.3 # regexp.lib=${regexp.base} diff --git a/build.xml b/build.xml index e6165fd..83039de 100644 --- a/build.xml +++ b/build.xml @@ -48,6 +48,11 @@ + + + + + @@ -81,6 +86,7 @@ + @@ -165,6 +171,7 @@ + diff --git a/etc/ui-config.xml b/etc/ui-config.xml index caadee5..7647df5 100644 --- a/etc/ui-config.xml +++ b/etc/ui-config.xml @@ -175,12 +175,14 @@ + + @@ -233,12 +235,14 @@ + + @@ -437,6 +441,8 @@ Text of this agreement is TBD. Conference Activity Reports Conference E-Mail + Export Messages + Import Messages Delete Conference diff --git a/scripts/conf/export.js b/scripts/conf/export.js new file mode 100644 index 0000000..54ec8ef --- /dev/null +++ b/scripts/conf/export.js @@ -0,0 +1,103 @@ +// The contents of this file are subject to the Mozilla Public License Version 1.1 +// (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at . +// +// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT +// WARRANTY OF ANY KIND, either express or implied. See the License for the specific +// language governing rights and limitations under the License. +// +// The Original Code is the Venice Web Communities System. +// +// The Initial Developer of the Original Code is Eric J. Bowersox , +// for Silverwrist Design Studios. Portions created by Eric J. Bowersox are +// Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. +// +// Contributor(s): + +importPackage(java.lang); +importPackage(java.util); +importPackage(Packages.com.silverwrist.venice.core); +importPackage(Packages.com.silverwrist.venice.except); +importPackage(Packages.com.silverwrist.venice.ui); +importPackage(Packages.com.silverwrist.venice.ui.conf); +importPackage(Packages.com.silverwrist.venice.ui.helpers); +importPackage(Packages.com.silverwrist.venice.ui.view); + +// get the request object and the community +rinput = bsf.lookupBean("request"); +comm = rinput.getCommunity(true,"top.js.vs"); + +// get the current conference +currc = new CurrentConference(rinput); +conf = currc.getConference(true,"conf/conferences.js.vs?cc=" + comm.communityID); +on_error = "conf/manage_conf.js.vs?cc=" + comm.communityID + "&conf=" + conf.confID; + +if (!(conf.canChangeConference())) +{ // you can't fiddle with this conference + vlib.output(new ErrorBox("Access Error","You do not have permission to export posts from this conference.", + on_error)); + vlib.done(); + +} // end if + +rc = null; // return from this script +try +{ // figure out what to do here + if ("GET"==rinput.verb) + { // load and display the JSP page for the form + l = conf.getTopicList(ConferenceContext.GET_ALL,ConferenceContext.SORT_NUMBER); + rinput.setRequestAttribute("topic.list",l); + + rc = new JSPView("Export Messages: " + conf.name,"conf/export.jsp"); + rc.menuSelector = Content.MENU_SELECTOR_COMMUNITY; + + } // end if + else // POST + { // get the parameters and generate the export file + if (rinput.isImageButtonClicked("cancel")) + rc = new Redirect(on_error,LinkTypes.SERVLET); // cancel - go back to menu + else if (rinput.isImageButtonClicked("export")) + { // get the list of topics to be exported + tarray = rinput.getParameterValues("tselect"); + if (tarray==null) + rc = new ErrorBox("Input Error","No topics selected.",on_error); + else if (tarray.length==0) + rc = new ErrorBox("Input Error","No topics selected.",on_error); + else + { // convert the array of string equivalent topic numbers to a set of topic indexes + tset = new HashSet(); + for (i=0; i. +// +// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT +// WARRANTY OF ANY KIND, either express or implied. See the License for the specific +// language governing rights and limitations under the License. +// +// The Original Code is the Venice Web Communities System. +// +// The Initial Developer of the Original Code is Eric J. Bowersox , +// for Silverwrist Design Studios. Portions created by Eric J. Bowersox are +// Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. +// +// Contributor(s): + +importPackage(java.lang); +importPackage(java.util); +importPackage(Packages.com.silverwrist.venice.core); +importPackage(Packages.com.silverwrist.venice.except); +importPackage(Packages.com.silverwrist.venice.ui); +importPackage(Packages.com.silverwrist.venice.ui.conf); +importPackage(Packages.com.silverwrist.venice.ui.helpers); +importPackage(Packages.com.silverwrist.venice.ui.view); + +// get the request object and the community +rinput = bsf.lookupBean("request"); +comm = rinput.getCommunity(true,"top.js.vs"); + +// get the current conference +currc = new CurrentConference(rinput); +conf = currc.getConference(true,"conf/conferences.js.vs?cc=" + comm.communityID); +on_error = "conf/manage_conf.js.vs?cc=" + comm.communityID + "&conf=" + conf.confID; + +if (!(conf.canChangeConference())) +{ // you can't fiddle with this conference + vlib.output(new ErrorBox("Access Error","You do not have permission to import posts to this conference.", + on_error)); + vlib.done(); + +} // end if + +rc = null; // return from this script +try +{ // figure out what to do + if ("GET"==rinput.verb) + { // load and display the JSP page for the form + rc = new JSPView("Import Messages: " + conf.name,"conf/import.jsp"); + rc.menuSelector = Content.MENU_SELECTOR_COMMUNITY; + + } // end get + else // POST + { // which button was pressed to get the POST going? + if (rinput.isImageButtonClicked("cancel")) + rc = new Redirect(on_error,LinkTypes.SERVLET); // cancel - go back to menu + else if (rinput.isImageButtonClicked("import")) + { // begin the import! + match_meth = rinput.getParameterInt("match",0); + create_new = rinput.hasParameter("create"); + if (rinput.hasParameter("the_file")) + { // perform the import and set up the results + msgs = conf.importMessages(rinput.getParameterDataStream("the_file"),match_meth,create_new); + rinput.setRequestAttribute("output.list",msgs); + rc = new JSPView("Import Message Results: " + conf.name,"conf/import_results.jsp"); + rc.menuSelector = Content.MENU_SELECTOR_COMMUNITY; + + } // end if + else // no input file specified + rc = new ErrorBox("Input Error","No input file specified.", + "conf/import.js.vs?cc=" + comm.communityID + "&conf=" + conf.confID); + + } // end else if + else + { // invalid command button pressed + logger.error("no known button click on POST to conf/import.js"); + rc = new ErrorBox("Internal Error","Unknown command button pressed",on_error); + + } // end else + + } // end else + +} // end try +catch (e) +{ // figure out the exception type + etype = vlib.exceptionType(e) + ""; + if (etype.match("AccessError")) + rc = new ErrorBox("Access Error",e.message,on_error); + else if (etype.match("DataException")) + rc = new ErrorBox("Database Error",e.message,on_error); + else if (etype.match("IOException")) + rc = new ErrorBox("I/O Error",e.message,on_error); + else + rc = e; + +} // end catch + +vlib.output(rc); diff --git a/src/com/silverwrist/util/Base64EncodeInputStream.java b/src/com/silverwrist/util/Base64EncodeInputStream.java new file mode 100644 index 0000000..09823e6 --- /dev/null +++ b/src/com/silverwrist/util/Base64EncodeInputStream.java @@ -0,0 +1,194 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.util; + +import java.io.*; +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.*; + +public class Base64EncodeInputStream extends FilterInputStream +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + private static Logger logger = Logger.getLogger(Base64EncodeInputStream.class); + + private static final int NREAD = 768; // must be divisible by three + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private byte[] m_coded_data = null; + private int m_ptr = 0; + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + public Base64EncodeInputStream(InputStream inner) + { + super(inner); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Internal operations + *-------------------------------------------------------------------------------- + */ + + private boolean backfill() throws IOException + { + if ((m_coded_data!=null) && (m_coded_data.length<=m_ptr)) + { // the buffer is empty - clear it + logger.debug("buffer drained"); + m_coded_data = null; + + } // end if + + if (m_coded_data==null) + { // read in a chunk from the underlying stream + byte[] data = new byte[NREAD]; + int nr = in.read(data); + if (nr<=0) + { // done with underlying stream + logger.debug("B64EIS - underlying stream at EOF"); + return true; + + } // end if + + if (logger.isDebugEnabled()) + logger.debug("read " + nr + " bytes from underlying stream"); + + if (nrb.length) + throw new IndexOutOfBoundsException("off right end of array"); + if (len==0) + return 0; + int rc = 0; + while (len>0) + { // force a backfill before copying + if (backfill()) + break; // end of the underlying file + int ncpy = Math.min(len,m_coded_data.length - m_ptr); + System.arraycopy(m_coded_data,m_ptr,b,off,ncpy); + m_ptr += ncpy; + off += ncpy; + rc += ncpy; + len -= ncpy; + + } // end while + + if (rc==0) + rc = -1; // EOF + return rc; + + } // end read + + public synchronized long skip(long n) throws IOException + { + long rc = 0; + + while (n>0) + { // force a backfill before skipping + if (backfill()) + break; // end of the underlying file + long nskp = Math.min(n,m_coded_data.length - m_ptr); + m_ptr += (int)nskp; + rc += nskp; + n -= nskp; + + } // end while + + return rc; + + } // end skip + + public void close() throws IOException + { + in.close(); + m_coded_data = null; + + } // end close + + public synchronized int available() throws IOException + { + if (backfill()) + return 0; // at end of file + return (m_coded_data.length - m_ptr) + (in.available() * 4 / 3); + + } // end available + + public void mark(int readLimit) + { // do nothing + } // end mark + + public void reset() + { // do nothing + } // end reset + + public boolean markSupported() + { + return false; + + } // end markSupported + +} // end class Base64EncodeInputStream diff --git a/src/com/silverwrist/util/IOUtil.java b/src/com/silverwrist/util/IOUtil.java index d622e09..72126ea 100644 --- a/src/com/silverwrist/util/IOUtil.java +++ b/src/com/silverwrist/util/IOUtil.java @@ -9,9 +9,9 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ @@ -125,20 +125,25 @@ public class IOUtil * @param input The stream to copy binary data from. * @param output The stream to copy binary data to. * @param bufsize The size of the buffer to allocate for copying. + * @return The total number of bytes copied. * @exception java.io.IOException If an exception occurred while reading or writing data. */ - public static void copy(InputStream input, OutputStream output, int bufsize) throws IOException + public static int copy(InputStream input, OutputStream output, int bufsize) throws IOException { byte[] buffer = new byte[bufsize]; + int rc = 0; int rd = input.read(buffer); while (rd>=0) { // simple read-write loop to shove data out the door if (rd>0) output.write(buffer,0,rd); + rc += rd; rd = input.read(buffer); } // end while + return rc; + } // end copy /** @@ -147,13 +152,14 @@ public class IOUtil * * @param input The stream to copy binary data from. * @param output The stream to copy binary data to. + * @return The total number of bytes copied. * @exception java.io.IOException If an exception occurred while reading or writing data. * @see #DEFAULT_BUFSIZE * @see #copy(java.io.InputStream,java.io.OutputStream,int) */ - public static void copy(InputStream input, OutputStream output) throws IOException + public static int copy(InputStream input, OutputStream output) throws IOException { - copy(input,output,DEFAULT_BUFSIZE); + return copy(input,output,DEFAULT_BUFSIZE); } // end copy @@ -246,20 +252,25 @@ public class IOUtil * @param input The reader to copy character data from. * @param output The writer to copy character data to. * @param bufsize The size of the buffer to allocate for copying. + * @return The number of characters copied. * @exception java.io.IOException If an exception occurred while reading or writing data. */ - public static void copy(Reader input, Writer output, int bufsize) throws IOException + public static int copy(Reader input, Writer output, int bufsize) throws IOException { char[] buffer = new char[bufsize]; + int rc = 0; int rd = input.read(buffer); while (rd>=0) { // simple read-write loop to shove data out the door if (rd>0) output.write(buffer,0,rd); + rc += rd; rd = input.read(buffer); } // end while + return rc; + } // end copy /** @@ -268,13 +279,14 @@ public class IOUtil * * @param input The reader to copy character data from. * @param output The writer to copy character data to. + * @return The number of characters copied. * @exception java.io.IOException If an exception occurred while reading or writing data. * @see #DEFAULT_BUFSIZE * @see #copy(java.io.Reader,java.io.Writer,int) */ - public static void copy(Reader input, Writer output) throws IOException + public static int copy(Reader input, Writer output) throws IOException { - copy(input,output,DEFAULT_BUFSIZE); + return copy(input,output,DEFAULT_BUFSIZE); } // end copy diff --git a/src/com/silverwrist/venice/core/ConferenceContext.java b/src/com/silverwrist/venice/core/ConferenceContext.java index 4c826c6..5e912f5 100644 --- a/src/com/silverwrist/venice/core/ConferenceContext.java +++ b/src/com/silverwrist/venice/core/ConferenceContext.java @@ -9,16 +9,19 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ package com.silverwrist.venice.core; +import java.io.InputStream; +import java.io.IOException; import java.util.Date; import java.util.List; +import java.util.Set; import com.silverwrist.venice.except.AccessError; import com.silverwrist.venice.except.DataException; import com.silverwrist.venice.except.EmailException; @@ -43,6 +46,9 @@ public interface ConferenceContext public static final int CUST_BLOCK_TOP = 0; public static final int CUST_BLOCK_BOTTOM = 1; + public static final int IMPORT_MATCH_NUM = 0; + public static final int IMPORT_MATCH_NAME = 1; + public abstract int getConfID(); public abstract String getName(); @@ -192,4 +198,9 @@ public interface ConferenceContext public abstract String getPostLink() throws DataException; + public abstract String exportTopics(Set select_topics) throws AccessError, DataException, IOException; + + public abstract List importMessages(InputStream xmlstream, int match_method, boolean create_new) + throws AccessError, DataException; + } // end interface ConferenceContext diff --git a/src/com/silverwrist/venice/core/TopicMessageContext.java b/src/com/silverwrist/venice/core/TopicMessageContext.java index 3391d52..2b8e122 100644 --- a/src/com/silverwrist/venice/core/TopicMessageContext.java +++ b/src/com/silverwrist/venice/core/TopicMessageContext.java @@ -18,6 +18,8 @@ package com.silverwrist.venice.core; import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.Date; import com.silverwrist.venice.except.AccessError; import com.silverwrist.venice.except.DataException; @@ -60,6 +62,8 @@ public interface TopicMessageContext public abstract InputStream getAttachmentData() throws AccessError, DataException; + public abstract int getAttachmentData(OutputStream here) throws AccessError, DataException, IOException; + public abstract boolean canHide(); public abstract boolean canScribble(); diff --git a/src/com/silverwrist/venice/core/impl/ConferenceUserContextImpl.java b/src/com/silverwrist/venice/core/impl/ConferenceUserContextImpl.java index ed7f9d5..6a9395f 100644 --- a/src/com/silverwrist/venice/core/impl/ConferenceUserContextImpl.java +++ b/src/com/silverwrist/venice/core/impl/ConferenceUserContextImpl.java @@ -9,14 +9,15 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001-02 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ package com.silverwrist.venice.core.impl; +import java.io.*; import java.sql.*; import java.util.*; import org.apache.log4j.*; @@ -1778,6 +1779,95 @@ class ConferenceUserContextImpl implements ConferenceContext, ConferenceBackend } // end getPostLink + public String exportTopics(Set select_topics) throws AccessError, DataException, IOException + { + if (logger.isDebugEnabled()) + logger.debug("exportTopics(" + select_topics.toString() + ") entry"); + if (!(getConferenceData().canChangeConference(level))) + throw new AccessError("You are not permitted to export posts from this conference."); + if (deleted) + throw new DataException("Cannot export posts from a deleted conference."); + + // "Normalize" the topic selection set. + HashSet norm_select_topics = null; + if (select_topics!=null) + { // build the normalized set + norm_select_topics = new HashSet(); + for (Iterator it=select_topics.iterator(); it.hasNext(); ) + { // get each object out in turn + Object o = it.next(); + if (o==null) + continue; + if (o instanceof Integer) + { // it's already an integer + norm_select_topics.add(o); + continue; + + } // end if + + if (o instanceof Number) + { // convert to Integer and add it + norm_select_topics.add(new Integer(((Number)o).intValue())); + continue; + + } // end if + + try + { // append integer equivalent to set + norm_select_topics.add(new Integer(o.toString())); + + } // end try + catch (NumberFormatException e) + { // do nothing + } // end catch + + } // end for + + } // end if + + ConferencingExporter xprt = new ConferencingExporter(); + xprt.setTopics(norm_select_topics); + return xprt.exportConference(this); + + } // end exportTopics + + public List importMessages(InputStream xmlstream, int match_method, boolean create_new) + throws AccessError, DataException + { + if (xmlstream==null) + throw new NullPointerException("bad input stream"); + if ((match_method!=IMPORT_MATCH_NUM) && (match_method!=IMPORT_MATCH_NAME)) + throw new IndexOutOfBoundsException("invalid match method specified"); + + if (!(getConferenceData().canChangeConference(level))) + throw new AccessError("You are not permitted to import posts to this conference."); + if (deleted) + throw new DataException("Cannot import messages to a deleted conference."); + + Connection conn = null; + try + { // get a connection and create the worker + conn = env.getConnection(); + ConferencingImporter worker = new ConferencingImporter(env.getUserID(),confid,match_method,create_new,conn, + env.getEngine()); + + // run it! + return worker.importMessages(xmlstream); + + } // end try + catch (SQLException e) + { // translate SQLException to DataException + throw new DataException("Error getting database connection: " + e.getMessage(),e); + + } // end catch + finally + { // shut down the connection + SQLUtil.shutdown(conn); + + } // end finally + + } // end importMessages + /*-------------------------------------------------------------------------------- * Implementations from interface ConferenceBackend *-------------------------------------------------------------------------------- diff --git a/src/com/silverwrist/venice/core/impl/ConferencingExporter.java b/src/com/silverwrist/venice/core/impl/ConferencingExporter.java new file mode 100644 index 0000000..dfa7608 --- /dev/null +++ b/src/com/silverwrist/venice/core/impl/ConferencingExporter.java @@ -0,0 +1,220 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.core.impl; + +import java.io.*; +import java.text.*; +import java.util.*; +import org.apache.log4j.*; +import com.silverwrist.util.*; +import com.silverwrist.venice.core.*; +import com.silverwrist.venice.except.*; + +class ConferencingExporter +{ + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + private static Logger logger = Logger.getLogger(ConferencingExporter.class); + + private static final DateFormat s_datefmt; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private StringWriter m_buffer = null; + private PrintWriter m_wr = null; + private Set m_include = null; + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + ConferencingExporter() + { // do nothing + } // end constructor + + /*-------------------------------------------------------------------------------- + * Internal operations + *-------------------------------------------------------------------------------- + */ + + private void visit(TopicMessageUserContextImpl msg) throws DataException, AccessError, IOException + { + if (logger.isDebugEnabled()) + logger.debug("exporting post #" + msg.getPostNumber()); + + m_wr.write("\n"); + + } // end visit + + private void visit(TopicUserContextImpl topic) throws DataException, AccessError, IOException + { + if ((m_include!=null) && !(m_include.contains(new Integer(topic.getTopicNumber())))) + return; // skip this topic + + if (logger.isDebugEnabled()) + logger.debug("exporting topic \"" + topic.getName() + "\""); + + // write the main topic information + m_wr.write("\n\n"); + + // get the posts and write them out + List l = topic.getMessages(0,topic.getTopMessage()); + for (Iterator it=l.iterator(); it.hasNext(); ) + visit((TopicMessageUserContextImpl)(it.next())); + + // done with the topic + m_wr.write("\n"); + + } // end visit + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + void setTopics(Set s) + { + m_include = s; + + } // end setTopics + + String exportConference(ConferenceUserContextImpl conf) throws DataException, AccessError, IOException + { + if (logger.isDebugEnabled()) + logger.debug("exporting conference \"" + conf.getName() + "\""); + m_buffer = new StringWriter(); + m_wr = new PrintWriter(m_buffer); + m_wr.write("\n\n"); + + List l = conf.getTopicList(ConferenceContext.GET_ALL,ConferenceContext.SORT_NUMBER); + for (Iterator it=l.iterator(); it.hasNext(); ) + visit((TopicUserContextImpl)(it.next())); + + m_wr.write("\n"); + m_wr.flush(); + + // Return the finished buffer. + String rc = m_buffer.toString(); + m_wr.close(); + m_wr = null; + m_buffer = null; + return rc; + + } // end exportConference + + /*-------------------------------------------------------------------------------- + * Static initializer + *-------------------------------------------------------------------------------- + */ + + static + { // create an ISO 8601 date formatter + SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + df.setTimeZone(new SimpleTimeZone(0,"UTC")); + s_datefmt = df; + + } // end static initializer + +} // end class ConferencingExporter diff --git a/src/com/silverwrist/venice/core/impl/ConferencingImporter.java b/src/com/silverwrist/venice/core/impl/ConferencingImporter.java new file mode 100644 index 0000000..5277467 --- /dev/null +++ b/src/com/silverwrist/venice/core/impl/ConferencingImporter.java @@ -0,0 +1,1293 @@ +/* + * The contents of this file are subject to the Mozilla Public License Version 1.1 + * (the "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at . + * + * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + * WARRANTY OF ANY KIND, either express or implied. See the License for the specific + * language governing rights and limitations under the License. + * + * The Original Code is the Venice Web Communities System. + * + * The Initial Developer of the Original Code is Eric J. Bowersox , + * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + * Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * + * Contributor(s): + */ +package com.silverwrist.venice.core.impl; + +import java.io.*; +import java.sql.*; +import java.text.*; +import java.util.*; +import java.util.zip.*; +import javax.xml.parsers.*; +import org.apache.commons.codec.binary.Base64; +import org.apache.log4j.*; +import org.xml.sax.*; +import org.xml.sax.helpers.*; +import com.silverwrist.util.*; +import com.silverwrist.venice.core.ConferenceContext; +import com.silverwrist.venice.core.internals.EngineBackend; +import com.silverwrist.venice.db.*; +import com.silverwrist.venice.except.*; + +class ConferencingImporter +{ + /*-------------------------------------------------------------------------------- + * Inner class containing topic data + *-------------------------------------------------------------------------------- + */ + + private class TopicData + { + /*==================================================================== + * Attributes + *==================================================================== + */ + + private int m_index; // topic index + private boolean m_frozen = false; // are we frozen? + private boolean m_archived = false; // are we archived? + private StringBuffer m_name; // topic name buffer + + /*==================================================================== + * Constructor + *==================================================================== + */ + + TopicData(Attributes attrs) throws SAXException + { + boolean index_seen = false; + for (int i=0; i index specified twice"); + m_index = Integer.parseInt(attrs.getValue(i)); + index_seen = true; + + } // end if + catch (NumberFormatException e) + { // this sucks! + throw new SAXException("invalid numeric format for index"); + + } // end catch + + } // end if + else if (name.equals("frozen")) + { // parse the "frozen" attribute + if (StringUtil.isBooleanTrue(attrs.getValue(i))) + m_frozen = true; + else if (StringUtil.isBooleanFalse(attrs.getValue(i))) + m_frozen = false; + else + throw new SAXException("invalid boolean format for frozen"); + + } // end else if + else if (name.equals("archived")) + { // parse the "archived" attribute + if (StringUtil.isBooleanTrue(attrs.getValue(i))) + m_archived = true; + else if (StringUtil.isBooleanFalse(attrs.getValue(i))) + m_archived = false; + else + throw new SAXException("invalid boolean format for archived"); + + } // end else if + // else ignore any unknown attributes + + } // end for + + if (!index_seen) + throw new SAXException(" index not specified"); + + m_name = new StringBuffer(); + + } // end constructor + + /*==================================================================== + * External operations + *==================================================================== + */ + + final void appendNameData(char[] ch, int start, int length) + { + m_name.append(ch,start,length); + + } // end appendNameData + + final void resolveTopic() throws SAXException + { + Statement stmt_x = null; + PreparedStatement stmt = null; + boolean mid_create = false; + + try + { // lock the tables we need + stmt_x = m_conn.createStatement(); + stmt_x.executeUpdate("LOCK TABLES confs WRITE, topics WRITE;"); + + // determine how to try to match the topic + switch (m_match_method) + { + case ConferenceContext.IMPORT_MATCH_NUM: + stmt = m_conn.prepareStatement("SELECT topicid FROM topics WHERE confid = ? AND num = ?;"); + stmt.setInt(2,m_index); + break; + + case ConferenceContext.IMPORT_MATCH_NAME: + stmt = m_conn.prepareStatement("SELECT topicid FROM topics WHERE confid = ? AND name = ?;"); + stmt.setString(2,m_name.toString()); + break; + + default: + throw new SAXException("internal error: invalid match method"); + + } // end switch + + stmt.setInt(1,m_conf_id); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) + { // found a matching topic - save the ID and return + m_cur_topic = rs.getInt(1); + SQLUtil.shutdown(rs); + return; + + } // end if + + SQLUtil.shutdown(rs); + if (!m_create_new) + { // the topic was not matched and cannot be created - ignore it + recordEvent("Topic <" + m_index + ".> (" + m_name.toString() + ") not matched or created - skipped."); + m_cur_topic = -1; + return; + + } // end if + + mid_create = true; + + // get the proper topic index + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("SELECT top_topic + 1 FROM confs WHERE confid = ?;"); + stmt.setInt(1,m_conf_id); + rs = stmt.executeQuery(); + if (rs.next()) + m_index = rs.getInt(1); + else + m_index = 1; + SQLUtil.shutdown(rs); + + // prepare the new topic entry + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("INSERT INTO topics (confid, num, creator_uid, top_message, frozen, archived, " + + "createdate, lastupdate, name) VALUES (?, ?, ?, -1, ?, ?, ?, ?, ?);"); + stmt.setInt(1,m_conf_id); + stmt.setInt(2,m_index); + stmt.setInt(3,m_uid); + stmt.setInt(4,m_frozen ? 1 : 0); + stmt.setInt(5,m_archived ? 1 : 0); + java.util.Date now = new java.util.Date(); + SQLUtil.setFullDateTime(stmt,6,now); + SQLUtil.setFullDateTime(stmt,7,now); + stmt.setString(8,m_name.toString()); + stmt.executeUpdate(); + + // get the new topic ID + rs = stmt_x.executeQuery("SELECT LAST_INSERT_ID();"); + if (rs.next()) + m_cur_topic = rs.getInt(1); + else + throw new SAXException("internal error: can\'t get new topic ID"); + + // update the last update and top topic fields + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("UPDATE confs SET lastupdate = ?, top_topic = ? WHERE confid = ?;"); + SQLUtil.setFullDateTime(stmt,1,now); + stmt.setInt(2,m_index); + stmt.setInt(3,m_conf_id); + stmt.executeUpdate(); + + } // end try + catch (SQLException e) + { // record the event and continue + if (mid_create) + recordEvent("error creating topic \"" + m_name.toString() + "\"",e); + else + recordEvent("error resolving topic <" + m_index + ".> (" + m_name.toString() + ")",e); + m_cur_topic = -1; + + } // end catch + finally + { // shut down things + SQLUtil.unlockTables(m_conn); + SQLUtil.shutdown(stmt); + SQLUtil.shutdown(stmt_x); + + } // end finally + + } // end resolveTopic + + } // end class TopicData + + /*-------------------------------------------------------------------------------- + * Inner class containing post data + *-------------------------------------------------------------------------------- + */ + + private class PostData + { + /*==================================================================== + * Attributes + *==================================================================== + */ + + private long m_id_in; + private long m_parent_in; + private int m_index; + private int m_line_count; + private String m_author; + private java.util.Date m_date; + private boolean m_hidden = false; + private String m_scribble_by = null; + private java.util.Date m_scribble_date = null; + private StringBuffer m_pseud; + private StringBuffer m_text; + private int m_att_length = -1; + private String m_att_type = null; + private String m_att_filename = null; + private File m_att_data = null; + + /*==================================================================== + * Constructor + *==================================================================== + */ + + PostData(Attributes attrs) throws SAXException + { + boolean id_seen = false, parent_seen = false, index_seen = false, linecount_seen = false, author_seen = false; + boolean date_seen = false; + for (int i=0; i ID specified twice"); + m_id_in = Long.parseLong(attrs.getValue(i)); + id_seen = true; + + } // end if + catch (NumberFormatException e) + { // this sucks! + throw new SAXException("invalid numeric format for ID"); + + } // end catch + + } // end if + else if (name.equals("parent")) + { // get the parent ID + try + { // handle parsing the topic index + if (parent_seen) + throw new SAXException(" parent ID specified twice"); + m_parent_in = Long.parseLong(attrs.getValue(i)); + parent_seen = true; + + } // end if + catch (NumberFormatException e) + { // this sucks! + throw new SAXException("invalid numeric format for parent ID"); + + } // end catch + + } // end else if + else if (name.equals("index")) + { // get the index + try + { // handle parsing the topic index + if (index_seen) + throw new SAXException(" index specified twice"); + m_index = Integer.parseInt(attrs.getValue(i)); + index_seen = true; + + } // end if + catch (NumberFormatException e) + { // this sucks! + throw new SAXException("invalid numeric format for index"); + + } // end catch + + } // end else if + else if (name.equals("lines")) + { // get the number of lines + try + { // handle parsing the topic index + if (linecount_seen) + throw new SAXException(" line count specified twice"); + m_line_count = Integer.parseInt(attrs.getValue(i)); + linecount_seen = true; + + } // end if + catch (NumberFormatException e) + { // this sucks! + throw new SAXException("invalid numeric format for line count"); + + } // end catch + + } // end else if + else if (name.equals("author")) + { // get the post author as a string + if (author_seen) + throw new SAXException(" author name specified twice"); + m_author = attrs.getValue(i); + author_seen = true; + + } // end else if + else if (name.equals("date")) + { // get the post date + try + { // parse the ISO 8601 date + if (date_seen) + throw new SAXException(" date specified twice"); + m_date = s_datefmt.parse(attrs.getValue(i)); + date_seen = true; + + } // end try + catch (ParseException e) + { // date parsing failed + throw new SAXException("invalid date format for date",e); + + } // end catch + + } // end else if + else if (name.equals("hidden")) + { // parse the "hidden" attribute + if (StringUtil.isBooleanTrue(attrs.getValue(i))) + m_hidden = true; + else if (StringUtil.isBooleanFalse(attrs.getValue(i))) + m_hidden = false; + else + throw new SAXException("invalid boolean format for hidden"); + + } // end else if + // else ignore any unknown attributes + + } // end for + + if (!id_seen) + throw new SAXException(" ID not specified"); + if (!parent_seen) + throw new SAXException(" parent ID not specified"); + if (!index_seen) + throw new SAXException(" index not specified"); + if (!linecount_seen) + throw new SAXException(" line count not specified"); + if (!author_seen) + throw new SAXException(" author name not specified"); + if (!date_seen) + throw new SAXException(" date not specified"); + + m_pseud = new StringBuffer(); + m_text = new StringBuffer(); + + } // end constructor + + /*==================================================================== + * External operations + *==================================================================== + */ + + final void dispose() + { + if (m_att_data!=null) + { // delete the temp file + m_att_data.delete(); + m_att_data = null; + + } // end if + + } // end dispose + + final void markScribbled(Attributes attrs) throws SAXException + { + boolean by_seen = false, date_seen = false; + for (int i=0; i user name specified twice"); + m_scribble_by = attrs.getValue(i); + by_seen = true; + + } // end if + else if (name.equals("date")) + { // get the scribbled date + try + { // parse the ISO 8601 date + if (date_seen) + throw new SAXException(" date specified twice"); + m_scribble_date = s_datefmt.parse(attrs.getValue(i)); + date_seen = true; + + } // end try + catch (ParseException e) + { // date parsing failed + throw new SAXException("invalid date format for date",e); + + } // end catch + + } // end else if + // else ignore any unknown attributes + + } // end for + + if (!by_seen) + throw new SAXException(" user name not specified"); + if (!date_seen) + throw new SAXException(" date not specified"); + + } // end markScribbled + + final void appendPseud(char[] ch, int start, int length) + { + m_pseud.append(ch,start,length); + + } // end appendPseud + + final void appendText(char[] ch, int start, int length) + { + m_text.append(ch,start,length); + + } // end appendText + + final OutputStream openAttachment(Attributes attrs) throws SAXException + { + boolean length_seen = false, type_seen = false, filename_seen = false; + for (int i=0; i length specified twice"); + m_att_length = Integer.parseInt(attrs.getValue(i)); + length_seen = true; + + } // end if + catch (NumberFormatException e) + { // this sucks! + throw new SAXException("invalid numeric format for length"); + + } // end catch + + } // end if + else if (name.equals("type")) + { // get the MIME type + if (type_seen) + throw new SAXException(" MIME type specified twice"); + m_att_type = attrs.getValue(i); + type_seen = true; + + } // end else if + else if (name.equals("filename")) + { // get the filename + if (filename_seen) + throw new SAXException(" filename specified twice"); + m_att_filename = attrs.getValue(i); + filename_seen = true; + + } // end else if + // else ignore any unknown attributes + + } // end for + + if (!length_seen) + throw new SAXException(" length not specified"); + if (!type_seen) + throw new SAXException(" MIME type not specified"); + if (!filename_seen) + throw new SAXException(" filename not specified"); + + boolean need_delete = false; + try + { // create the attachment data file and open it + m_att_data = File.createTempFile("Attachment",null); + need_delete = true; + return new FileOutputStream(m_att_data); + + } // end try + catch (IOException e) + { // record the event and continue + recordEvent("unable to open temporary file for attachment \"" + m_att_filename + "\"",e); + m_att_length = -1; + m_att_type = null; + m_att_filename = null; + if (need_delete) + m_att_data.delete(); + m_att_data = null; + + } // end catch + + return null; + + } // end openAttachment + + final void postThis() throws SAXException + { + /* + private String m_att_type = null; + private String m_att_filename = null; + */ + + // get the creator UID + int creator_uid = getUIDForUser(m_author); + if (creator_uid==-1) + { // add a note to the beginning of the text and change the creator UID + m_text.insert(0,"(Originally posted by \"" + m_author + "\")\r\n\r\n"); + m_line_count += 2; + creator_uid = m_uid; + + } // end if + + // get the scribble UID + int scribble_uid = -1; + if (m_scribble_by!=null) + { // get the UID of the scribbling user + scribble_uid = getUIDForUser(m_scribble_by); + if (scribble_uid==-1) + scribble_uid = m_uid; + + } // end if + + Statement stmt_x = null; + PreparedStatement stmt = null; + try + { // lock the tables we'll need + stmt_x = m_conn.createStatement(); + stmt_x.executeUpdate("LOCK TABLES confs WRITE, confsettings WRITE, topics WRITE, topicsettings WRITE, " + + "posts WRITE, postdata WRITE, postattach WRITE;"); + + // get the new index for the post + stmt = m_conn.prepareStatement("SELECT top_message + 1 FROM topics WHERE topicid = ?;"); + stmt.setInt(1,m_cur_topic); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) + m_index = rs.getInt(1); + else + m_index = 0; + SQLUtil.shutdown(rs); + + // prepare the initial post entry + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("INSERT INTO posts (parent, topicid, num, linecount, creator_uid, posted, " + + "hidden, pseud) VALUES (?, ?, ?, ?, ?, ?, ?, ?);"); + stmt.setLong(1,getPostMapping(m_parent_in)); + stmt.setInt(2,m_cur_topic); + stmt.setInt(3,m_index); + stmt.setInt(4,m_line_count); + stmt.setInt(5,creator_uid); + SQLUtil.setFullDateTime(stmt,6,m_date); + stmt.setInt(7,m_hidden ? 1 : 0); + stmt.setString(8,m_pseud.toString()); + stmt.executeUpdate(); + + // get the new post ID + long pid; + rs = stmt.executeQuery("SELECT LAST_INSERT_ID();"); + if (rs.next()) + pid = rs.getLong(1); + else + throw new SAXException("internal error: cannot get new post ID"); + setPostMapping(m_id_in,pid); + + // add the post data + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("INSERT INTO postdata(postid, data) VALUES (?, ?);"); + stmt.setLong(1,pid); + stmt.setString(2,m_text.toString()); + stmt.executeUpdate(); + + if (m_scribble_date!=null) + { // update the post to reflect scribble dates + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("UPDATE posts SET scribble_uid = ?, scribble_date = ? WHERE postid = ?;"); + stmt.setInt(1,scribble_uid); + SQLUtil.setFullDateTime(stmt,2,m_scribble_date); + stmt.setLong(3,pid); + stmt.executeUpdate(); + + } // end if + + // update the topic to reflect the last update + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("UPDATE topics SET top_message = ?, lastupdate = ? WHERE topicid = ?;"); + stmt.setInt(1,m_index); + SQLUtil.setFullDateTime(stmt,2,m_date); + stmt.setInt(3,m_cur_topic); + stmt.executeUpdate(); + + // update the topic settings to reflect the last post date + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("UPDATE topicsettings SET last_post = ? WHERE topicid = ? AND uid = ?;"); + SQLUtil.setFullDateTime(stmt,1,m_date); + stmt.setInt(2,m_cur_topic); + stmt.setInt(3,creator_uid); + if (stmt.executeUpdate()<1) + { // We didn't have a topic settings record yet; create one. + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("INSERT INTO topicsettings (topicid, uid, last_post) VALUES (?, ?, ?);"); + stmt.setInt(1,m_cur_topic); + stmt.setInt(2,creator_uid); + SQLUtil.setFullDateTime(stmt,3,m_date); + stmt.executeUpdate(); + + } // end if + + // Update the "last update" date of the conference. + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("UPDATE confs SET lastupdate = ? WHERE confid = ?;"); + SQLUtil.setFullDateTime(stmt,1,m_date); + stmt.setInt(2,m_conf_id); + + // Update the "last post" timestamp in conference settings. + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("UPDATE confsettings SET last_read = ?, last_post = ? WHERE confid = ? " + + "AND uid = ?;"); + SQLUtil.setFullDateTime(stmt,1,m_date); + SQLUtil.setFullDateTime(stmt,2,m_date); + stmt.setInt(3,m_conf_id); + stmt.setInt(4,creator_uid); + if (stmt.executeUpdate()<1) + { // no "confsettings" record found - create one + SQLUtil.shutdown(stmt); + stmt = m_conn.prepareStatement("INSERT INTO confsettings (confid, uid, last_read, last_post) " + + "VALUES (?, ?, ?, ?);"); + stmt.setInt(1,m_conf_id); + stmt.setInt(2,creator_uid); + SQLUtil.setFullDateTime(stmt,3,m_date); + SQLUtil.setFullDateTime(stmt,4,m_date); + + } // end if + + if (m_att_type!=null) + { // there's an attachment to be stored! First see if we need to compress the data. + InputStream stm = null; + int stg_method, stmlen; + try + { // handle all the IO manipulations here + if (m_engine.isNoCompressMimeType(m_att_type)) + { // don't compress, just copy the file data directly + stm = new FileInputStream(m_att_data); + stmlen = m_att_length; + stg_method = 0; + + } // end if + else + { // create the buffer to compress into + ByteArrayOutputStream bytestm = new ByteArrayOutputStream(m_att_length); + GZIPOutputStream gzipstm = new GZIPOutputStream(bytestm); + IOUtil.copy(new FileInputStream(m_att_data),gzipstm); + gzipstm.finish(); + byte[] buffer = bytestm.toByteArray(); + stmlen = buffer.length; + stm = new ByteArrayInputStream(buffer); + IOUtil.shutdown(gzipstm); + stg_method = 1; + + } // end else + + // add the attachment record to the database! + stmt = m_conn.prepareStatement("INSERT INTO postattach(postid, datalen, last_hit, stgmethod, " + + "filename, mimetype, data) VALUES (?, ?, ?, ?, ?, ?, ?);"); + stmt.setLong(1,pid); + stmt.setInt(2,m_att_length); + SQLUtil.setFullDateTime(stmt,3,new java.util.Date()); + stmt.setInt(4,stg_method); + stmt.setString(5,m_att_filename); + stmt.setString(6,m_att_type); + stmt.setBinaryStream(7,stm,stmlen); + stmt.executeUpdate(); + + } // end try + catch (IOException e) + { // bail out from attachment if we screw this up + recordEvent("error getting/compressing attachment data",e); + return; + + } // end catch + + } // end if + + } // end try + catch (SQLException e) + { // record a database error here + recordEvent("error posting message",e); + + } // end catch + finally + { // close everything down + SQLUtil.unlockTables(m_conn); + SQLUtil.shutdown(stmt_x); + SQLUtil.shutdown(stmt); + + } // end finally + + } // end postThis + + } // end class PostData + + /*-------------------------------------------------------------------------------- + * Inner class containing XML listener + *-------------------------------------------------------------------------------- + */ + + private class Listener extends DefaultHandler + { + /*==================================================================== + * Attributes + *==================================================================== + */ + + private TopicData m_topicdata = null; + private PostData m_postdata = null; + private OutputStream m_attachment = null; + private byte[] m_leftover = null; + + /*==================================================================== + * Constructor + *==================================================================== + */ + + Listener() + { + super(); + + } // end constructor + + /*==================================================================== + * Internal operations + *==================================================================== + */ + + private final void handleAttachmentChars(char[] ch, int start, int length) throws SAXException + { + if (m_attachment==null) + return; // no attachment - don't bother + + byte[] code_data = null; + try + { // convert from an array of characters to an array of bytes + String tmp = new String(ch,start,length); + code_data = tmp.getBytes("US-ASCII"); + + } // end try + catch (UnsupportedEncodingException e) + { // this should never happen + throw new SAXException("?!?!?!? WTF? US-ASCII is OK!",e); + + } // end catch + + int len_leftover = (m_leftover!=null) ? m_leftover.length : 0; + if (len_leftover==0) + m_leftover = null; + int t = (len_leftover + code_data.length) / 4; + int ilen = t * 4; + int len_new_leftover = (len_leftover + code_data.length) - ilen; + + byte[] input_data = null; + if (ilen>0) + { // create a perfect input group of 4-byte blocks + input_data = new byte[ilen]; + if (len_leftover>0) + System.arraycopy(m_leftover,0,input_data,0,len_leftover); + System.arraycopy(code_data,0,input_data,len_leftover,ilen - len_leftover); + + } // end if + + if (len_new_leftover>0) + { // set the leftovers aside + m_leftover = new byte[len_new_leftover]; + System.arraycopy(code_data,code_data.length - len_new_leftover,m_leftover,0,len_new_leftover); + + } // end if + else // no leftovers + m_leftover = null; + + if (ilen==0) + return; // no data to write + + try + { // decode the base-64 data and write it! + m_attachment.write(Base64.decodeBase64(input_data)); + + } // end try + catch (IOException e) + { // error during the decode or write + throw new SAXException("error decoding and writing attachment data",e); + + } // end catch + + } // end handleAttachmentChars + + private final void finishAttachment() throws SAXException + { + if ((m_leftover!=null) && (m_leftover.length==0)) + m_leftover = null; + if (m_leftover==null) + return; + + try + { // decode the base-64 data and write it! + m_attachment.write(Base64.decodeBase64(m_leftover)); + + } // end try + catch (IOException e) + { // error during the decode or write + throw new SAXException("error decoding and writing attachment data",e); + + } // end catch + finally + { // null out the leftover array + m_leftover = null; + + } // end finally + + } // end finishAttachment + + /*==================================================================== + * Overrides from class DefaultHandler + *==================================================================== + */ + + public void setDocumentLocator(Locator locator) + { + m_locator = locator; + + } // end setDocumentLocator + + public void startDocument() + { + m_state = ST_PRESTART; + m_cur_topic = -1; + + } // end startDocument + + public void endDocument() throws SAXException + { + if (m_state!=ST_END) + throw new SAXException("no content found"); + m_locator = null; + + } // end endDocument + + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException + { + String name = resolveName(localName,qName); + switch (m_state) + { + case ST_PRESTART: + if (!(name.equals("vcif"))) + throw new SAXException(" expected"); + m_state = ST_INSTART; + break; + + case ST_INSTART: + if (!(name.equals("topic"))) + throw new SAXException(" expected"); + m_topicdata = new TopicData(attributes); + m_state = ST_INTOPIC_1; + break; + + case ST_INTOPIC_1: + if (!(name.equals("topicname"))) + throw new SAXException(" expected"); + m_state = ST_INTOPIC_NAME; + break; + + case ST_INTOPIC: + if (!(name.equals("post"))) + throw new SAXException(" expected"); + if (m_cur_topic>0) + m_postdata = new PostData(attributes); + m_state = ST_INPOST; + break; + + case ST_INPOST: + if (name.equals("scribbled")) + { // mark post as scribbled + if (m_postdata!=null) + m_postdata.markScribbled(attributes); + m_state = ST_SCRIBBLETAG; + + } // end if + else if (name.equals("pseud")) + m_state = ST_PSEUD; + else if (name.equals("text")) + m_state = ST_TEXT; + else if (name.equals("attachment")) + { // open the attachment + if (m_postdata!=null) + m_attachment = m_postdata.openAttachment(attributes); + m_state = ST_ATTACHMENT; + + } // end else if + else + throw new SAXException(", , , or expected"); + break; + + default: + throw new SAXException("startElement state error (state = " + m_state + ", elt = \"" + name + "\")"); + + } // end switch + + } // end startElement + + public void endElement(String uri, String localName, String qName) throws SAXException + { + String name = resolveName(localName,qName); + switch (m_state) + { + case ST_INSTART: + if (!(name.equals("vcif"))) + throw new SAXException(" expected"); + m_state = ST_END; + break; + + case ST_INTOPIC_NAME: + if (!(name.equals("topicname"))) + throw new SAXException(" expected"); + if (m_topicdata==null) + throw new SAXException("internal error: no topic data"); + m_topicdata.resolveTopic(); + m_topicdata = null; + m_state = ST_INTOPIC; + break; + + case ST_INTOPIC: + if (!(name.equals("topic"))) + throw new SAXException(" expected"); + m_cur_topic = -1; + m_state = ST_INSTART; + break; + + case ST_INPOST: + if (!(name.equals("post"))) + throw new SAXException(" expected"); + if (m_postdata!=null) + { // post complete - make it + try + { // post the message! + m_postdata.postThis(); + + } // end try + finally + { // make sure we dispose the postdata + m_postdata.dispose(); + m_postdata = null; + + } // end finally + + } // end if + + m_state = ST_INTOPIC; + break; + + case ST_SCRIBBLETAG: + if (!(name.equals("scribbled"))) + throw new SAXException(" expected"); + m_state = ST_INPOST; + break; + + case ST_PSEUD: + if (!(name.equals("pseud"))) + throw new SAXException(" expected"); + m_state = ST_INPOST; + break; + + case ST_TEXT: + if (!(name.equals("text"))) + throw new SAXException(" expected"); + m_state = ST_INPOST; + break; + + case ST_ATTACHMENT: + if (!(name.equals("attachment"))) + throw new SAXException(" expected"); + try + { // finish the attachment + finishAttachment(); + + } // end try + finally + { // close it all down + IOUtil.shutdown(m_attachment); + m_attachment = null; + m_state = ST_INPOST; + + } // end finally + break; + + default: + throw new SAXException("endElement state error (state = " + m_state + ", elt = \"" + name + "\")"); + + } // end switch + + } // end endElement + + public void characters(char[] ch, int start, int length) throws SAXException + { + switch (m_state) + { + case ST_INTOPIC_NAME: + if (m_topicdata!=null) + m_topicdata.appendNameData(ch,start,length); + break; + + case ST_PSEUD: + if (m_postdata!=null) + m_postdata.appendPseud(ch,start,length); + break; + + case ST_TEXT: + if (m_postdata!=null) + m_postdata.appendText(ch,start,length); + break; + + case ST_ATTACHMENT: + handleAttachmentChars(ch,start,length); + break; + + default: + break; // ignore characters + + } // end switch + + } // end characters + + public void warning(SAXParseException e) throws SAXException + { + recordEvent("parser warning",e); + super.warning(e); + + } // end warning + + public void error(SAXParseException e) throws SAXException + { + recordEvent("parser error",e); + super.error(e); + + } // end error + + public void fatalError(SAXParseException e) throws SAXException + { + recordEvent("parser fatal error",e); + super.fatalError(e); + + } // end fatalError + + } // end class Listener + + /*-------------------------------------------------------------------------------- + * Static data members + *-------------------------------------------------------------------------------- + */ + + private static Logger logger = Logger.getLogger(ConferencingImporter.class); + + private static final DateFormat s_datefmt; + + private static final int ST_PRESTART = 0; + private static final int ST_INSTART = 1; + private static final int ST_END = 2; + private static final int ST_INTOPIC_1 = 3; + private static final int ST_INTOPIC_NAME = 4; + private static final int ST_INTOPIC = 5; + private static final int ST_INPOST = 6; + private static final int ST_SCRIBBLETAG = 7; + private static final int ST_PSEUD = 8; + private static final int ST_TEXT = 9; + private static final int ST_ATTACHMENT = 10; + + /*-------------------------------------------------------------------------------- + * Attributes + *-------------------------------------------------------------------------------- + */ + + private int m_uid; // UID we're creating stuff with + private int m_conf_id; // conference ID we're importing to + private int m_match_method; // conference match method + private boolean m_create_new; // create new topic if it doesn't exist? + private Connection m_conn; // SQL database connection + private EngineBackend m_engine; // the engine + private LinkedList m_events = null; // the events reported by the parser + private Locator m_locator = null; // locator for events + private int m_state; // current state of the parser + private int m_cur_topic; // current topic ID + private HashMap m_post_map; // mapping from old to new post IDs + + /*-------------------------------------------------------------------------------- + * Constructor + *-------------------------------------------------------------------------------- + */ + + ConferencingImporter(int uid, int conf_id, int match_method, boolean create_new, Connection conn, + EngineBackend engine) + { + m_uid = uid; + m_conf_id = conf_id; + m_match_method = match_method; + m_create_new = create_new; + m_conn = conn; + m_engine = engine; + m_post_map = new HashMap(); + + } // end constructor + + /*-------------------------------------------------------------------------------- + * Internal operations + *-------------------------------------------------------------------------------- + */ + + private static final String resolveName(String local_name, String q_name) + { + if ((local_name!=null) && (local_name.length()>0)) + return local_name; + else + return q_name; + + } // end resolveName + + private final int getUIDForUser(String username) + { + PreparedStatement stmt = null; + ResultSet rs = null; + try + { // straightforward query + stmt = m_conn.prepareStatement("SELECT uid FROM users WHERE username = ?;"); + stmt.setString(1,username); + rs = stmt.executeQuery(); + if (rs.next()) + return rs.getInt(1); + else + return -1; + + } // end try + catch (SQLException e) + { // whoops: big error! + recordEvent("error getting UID for user name \"" + username + "\"",e); + return -1; + + } // end catch + finally + { // shut everything down + SQLUtil.shutdown(rs); + SQLUtil.shutdown(stmt); + + } // end finally + + } // end getUIDForUser + + private final void recordEvent(String message) + { + StringBuffer buf = new StringBuffer("["); + buf.append(m_locator.getLineNumber()).append(", ").append(m_locator.getColumnNumber()).append("] "); + buf.append(message); + if (m_events==null) + m_events = new LinkedList(); + m_events.addLast(buf.toString()); + + } // end recordEvent + + private final void recordEvent(String message, Throwable t) + { + StringBuffer buf = new StringBuffer("["); + buf.append(m_locator.getLineNumber()).append(", ").append(m_locator.getColumnNumber()).append("] "); + buf.append(message).append(" - ").append(t.getMessage()); + if (m_events==null) + m_events = new LinkedList(); + m_events.addLast(buf.toString()); + + } // end recordEvent + + private final long getPostMapping(long old) + { + if (old==0) + return 0; + Long rc = (Long)(m_post_map.get(new Long(old))); + return (rc==null) ? 0 : rc.longValue(); + + } // end getPostMapping + + private final void setPostMapping(long old, long nu) + { + m_post_map.put(new Long(old),new Long(nu)); + + } // end setPostMapping + + /*-------------------------------------------------------------------------------- + * External operations + *-------------------------------------------------------------------------------- + */ + + final List importMessages(InputStream xmlstm) throws DataException + { + try + { // create a SAX parser and let it loose on the input data with our listener + SAXParserFactory fact = SAXParserFactory.newInstance(); + fact.setNamespaceAware(false); + fact.setValidating(false); + SAXParser parser = fact.newSAXParser(); + parser.parse(xmlstm,new Listener()); + + } // end try + catch (ParserConfigurationException e) + { // configuration error + throw new DataException("Error configuring XML parser for message import: " + e.getMessage(),e); + + } // end catch + catch (SAXException e) + { // give an error message + throw new DataException("Error importing messages: " + e.getMessage(),e); + + } // end catch + catch (IOException e) + { // I/O error in parsing! + throw new DataException("Error importing messages: " + e.getMessage(),e); + + } // end catch + + if (m_events==null) + return Collections.EMPTY_LIST; + ArrayList rc = new ArrayList(m_events); + m_events = null; + return Collections.unmodifiableList(rc); + + } // end importMessages + + /*-------------------------------------------------------------------------------- + * Static initializer + *-------------------------------------------------------------------------------- + */ + + static + { // create an ISO 8601 date formatter + SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + df.setTimeZone(new SimpleTimeZone(0,"UTC")); + df.setLenient(false); + s_datefmt = df; + + } // end static initializer + +} // end class ConferencingImporter diff --git a/src/com/silverwrist/venice/core/impl/PublishedMessageImpl.java b/src/com/silverwrist/venice/core/impl/PublishedMessageImpl.java index 54bc420..2827387 100644 --- a/src/com/silverwrist/venice/core/impl/PublishedMessageImpl.java +++ b/src/com/silverwrist/venice/core/impl/PublishedMessageImpl.java @@ -9,9 +9,9 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001-02 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ @@ -293,6 +293,13 @@ class PublishedMessageImpl implements TopicMessageContext } // end getAttachmentData + public int getAttachmentData(OutputStream here) throws AccessError, DataException, IOException + { + // FUTURE: allow publishing paperclips? + throw new AccessError("There is no attachment data for this message."); + + } // end getAttachmentData + public boolean canHide() { return false; diff --git a/src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java b/src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java index 28bae7c..dd99bb6 100644 --- a/src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java +++ b/src/com/silverwrist/venice/core/impl/TopicMessageUserContextImpl.java @@ -9,9 +9,9 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001-02 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ @@ -200,6 +200,40 @@ class TopicMessageUserContextImpl implements TopicMessageContext } // end refresh + /*-------------------------------------------------------------------------------- + * Semi-internal operations + *-------------------------------------------------------------------------------- + */ + + final String getScribblingUser() throws DataException + { + if (scribble_uid==-1) + return null; + + Connection conn = null; + try + { // use a database connection to get the user name + conn = env.getConnection(); + refresh(conn); + if (nuked) + return null; // post nuked! + return quickGetUserName(conn,scribble_uid); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error reading user name: " + e.getMessage(),e); + throw new DataException("unable to retrieve user name: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + SQLUtil.shutdown(conn); + + } // end finally + + } // end getScribblingUser + /*-------------------------------------------------------------------------------- * Implementations from interface TopicMessageContext *-------------------------------------------------------------------------------- @@ -392,6 +426,8 @@ class TopicMessageUserContextImpl implements TopicMessageContext public InputStream getAttachmentData() throws AccessError, DataException { + if (logger.isDebugEnabled()) + logger.debug("getAttachmentData() for post " + postid); if (nuked || (scribble_date!=null)) { // this would be an exercise in futility! logger.error("cannot attach to a nuked or scribbled message"); @@ -401,6 +437,7 @@ class TopicMessageUserContextImpl implements TopicMessageContext Connection conn = null; Statement stmt = null; + ResultSet rs = null; InputStream rc = null; try { // open up a database connection @@ -434,7 +471,7 @@ class TopicMessageUserContextImpl implements TopicMessageContext sql.append("SELECT data FROM postattach WHERE postid = ").append(postid).append(';'); // Execute the query! - ResultSet rs = stmt.executeQuery(sql.toString()); + rs = stmt.executeQuery(sql.toString()); if (!(rs.next())) { // there is no attachment data! logger.error("no attachment data to get"); @@ -450,10 +487,12 @@ class TopicMessageUserContextImpl implements TopicMessageContext switch (stgmethod) { // where's our input really coming from case 0: // copy verbatim + logger.debug("stgmethod 0 = straight copy"); real_input = sqldata; break; case 1: // gunzip it first + logger.debug("stgmethod 1 = GZIP decompress"); real_input = new GZIPInputStream(sqldata); break; @@ -463,7 +502,10 @@ class TopicMessageUserContextImpl implements TopicMessageContext } // end switch // Copy to a new stream. - rc = new ByteArrayInputStream(IOUtil.load(real_input)); + byte[] ary = IOUtil.load(real_input); + if (logger.isDebugEnabled()) + logger.debug("loaded data of length " + ary.length); + rc = new ByteArrayInputStream(ary); } // end try catch (SQLException e) @@ -480,6 +522,7 @@ class TopicMessageUserContextImpl implements TopicMessageContext } // end catch finally { // make sure we release the connection before we go + SQLUtil.shutdown(rs); SQLUtil.shutdown(stmt); SQLUtil.shutdown(conn); @@ -489,6 +532,102 @@ class TopicMessageUserContextImpl implements TopicMessageContext } // end getAttachmentData + public int getAttachmentData(OutputStream here) throws AccessError, DataException, IOException + { + if (logger.isDebugEnabled()) + logger.debug("getAttachmentData() for post " + postid); + if (nuked || (scribble_date!=null)) + { // this would be an exercise in futility! + logger.error("cannot attach to a nuked or scribbled message"); + throw new AccessError("You cannot attach data to a message that no longer exists."); + + } // end if + + Connection conn = null; + Statement stmt = null; + ResultSet rs = null; + try + { // open up a database connection + conn = env.getConnection(); + + // make sure we have current data + refresh(conn); + if (nuked || (scribble_date!=null)) + { // this would be an exercise in futility! + logger.error("cannot attach to a nuked or scribbled message"); + throw new AccessError("You cannot attach data to a message that no longer exists."); + + } // end if + + if (mimetype==null) + { // there is no attachment data! + logger.error("no attachment data to get"); + throw new AccessError("There is no attachment data for this message."); + + } // end if + + // This will cause a "hit" on the attachment data. Update that record. + stmt = conn.createStatement(); + StringBuffer sql = new StringBuffer("UPDATE postattach SET hits = hits + 1, last_hit = '"); + sql.append(SQLUtil.encodeDate(new java.util.Date())).append("' WHERE postid = ").append(postid); + sql.append(';'); + stmt.executeUpdate(sql.toString()); + + // Create the SQL we need to retrieve the attachment. + sql.setLength(0); + sql.append("SELECT data FROM postattach WHERE postid = ").append(postid).append(';'); + + // Execute the query! + rs = stmt.executeQuery(sql.toString()); + if (!(rs.next())) + { // there is no attachment data! + logger.error("no attachment data to get"); + throw new AccessError("There is no attachment data for this message."); + + } // end if + + // Since the InputStream we get from JDBC will probably go away when the connection is dropped, we + // need to make a temporary copy of it, to a ByteArrayOutputStream. (Attachments can only be + // 1 Mb in size, so this shouldn't be a big problem.) + InputStream sqldata = rs.getBinaryStream(1); + InputStream real_input; + switch (stgmethod) + { // where's our input really coming from + case 0: // copy verbatim + logger.debug("stgmethod 0 = straight copy"); + real_input = sqldata; + break; + + case 1: // gunzip it first + logger.debug("stgmethod 1 = GZIP decompress"); + real_input = new GZIPInputStream(sqldata); + break; + + default: + throw new DataException("Unknown storage method value: " + stgmethod); + + } // end switch + + // copy the data and return number of bytes copied + return IOUtil.copy(real_input,here); + + } // end try + catch (SQLException e) + { // turn this into a DataException + logger.error("DB error retrieving attachment: " + e.getMessage(),e); + throw new DataException("unable to retrieve attachment data: " + e.getMessage(),e); + + } // end catch + finally + { // make sure we release the connection before we go + SQLUtil.shutdown(rs); + SQLUtil.shutdown(stmt); + SQLUtil.shutdown(conn); + + } // end finally + + } // end getAttachmentData + public boolean canHide() { return ( ((creator_uid==env.getUserID()) && (!env.isAnonymous())) diff --git a/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java b/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java index 1bfb7a2..647312d 100644 --- a/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java +++ b/src/com/silverwrist/venice/core/impl/TopicUserContextImpl.java @@ -9,9 +9,9 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001-02 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ @@ -201,6 +201,17 @@ class TopicUserContextImpl implements TopicContext } // end loadBozo + /*-------------------------------------------------------------------------------- + * Semi-internal functions + *-------------------------------------------------------------------------------- + */ + + final int getTopMessage() + { + return top_message; + + } // end getTopMessage + /*-------------------------------------------------------------------------------- * Implementations from interface TopicContext *-------------------------------------------------------------------------------- diff --git a/src/com/silverwrist/venice/db/SQLUtil.java b/src/com/silverwrist/venice/db/SQLUtil.java index b5214ef..5657a27 100644 --- a/src/com/silverwrist/venice/db/SQLUtil.java +++ b/src/com/silverwrist/venice/db/SQLUtil.java @@ -9,9 +9,9 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001-02 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ diff --git a/src/com/silverwrist/venice/ui/conf/AttachmentContent.java b/src/com/silverwrist/venice/ui/conf/AttachmentContent.java index 08dad82..a203d70 100644 --- a/src/com/silverwrist/venice/ui/conf/AttachmentContent.java +++ b/src/com/silverwrist/venice/ui/conf/AttachmentContent.java @@ -17,11 +17,9 @@ */ package com.silverwrist.venice.ui.conf; -import java.io.InputStream; -import java.io.IOException; +import java.io.*; import com.silverwrist.venice.core.TopicMessageContext; -import com.silverwrist.venice.except.AccessError; -import com.silverwrist.venice.except.DataException; +import com.silverwrist.venice.except.*; import com.silverwrist.venice.ui.ContentExecute; import com.silverwrist.venice.ui.LinkTypes; import com.silverwrist.venice.ui.RequestExec; @@ -40,7 +38,7 @@ public class AttachmentContent extends ThrowableContent implements ContentExecut private InputStream stm; /*-------------------------------------------------------------------------------- - * Constructor + * Constructors *-------------------------------------------------------------------------------- */ @@ -54,6 +52,26 @@ public class AttachmentContent extends ThrowableContent implements ContentExecut } // end constructor + public AttachmentContent(String data, String type, String filename) + { + super(); + this.type = type; + this.filename = filename; + try + { // get the string data and output it + byte[] d = data.getBytes("UTF-8"); + this.length = d.length; + this.stm = new ByteArrayInputStream(d); + + } // end try + catch (UnsupportedEncodingException e) + { // this is not supposed to happen + throw new InternalStateError("encoding not found?!?!?!?"); + + } // end catch + + } // end constructor + /*-------------------------------------------------------------------------------- * Implementations from interface ContentExecute *-------------------------------------------------------------------------------- diff --git a/src/com/silverwrist/venice/ui/servlet/BaseServlet.java b/src/com/silverwrist/venice/ui/servlet/BaseServlet.java index c5cefce..84f499c 100644 --- a/src/com/silverwrist/venice/ui/servlet/BaseServlet.java +++ b/src/com/silverwrist/venice/ui/servlet/BaseServlet.java @@ -9,9 +9,9 @@ * * The Original Code is the Venice Web Communities System. * - * The Initial Developer of the Original Code is Eric J. Bowersox , + * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are - * Copyright (C) 2001-04 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + * Copyright (C) 2001-2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ @@ -166,6 +166,21 @@ public abstract class BaseServlet extends HttpServlet my_output = new ErrorBox("Internal Error!", "No content returned for request: " + the_request.getVerb() + ": /" + default_location,null); + else if (my_output instanceof RuntimeException) + { // throw all RuntimeExceptions we get at this level + logger.error("BaseServlet caught runtime exception" + my_output.getClass().getName() + + " in commonProcess",(Throwable)my_output); + throw new ServletException((Throwable)my_output); + + } // end else if + else if (my_output instanceof VirtualMachineError) + { // OutOfMemoryError, StackOverflowError, and such come here + System.gc(); // garbage collect so we have enough space to handle the error + logger.error("Virtual machine failure " + my_output.getClass().getName() + " in commonProcess", + (Throwable)my_output); + throw new ServletException((Throwable)my_output); + + } // end else if } // end try catch (ThrowableContent tc) diff --git a/web/format/conf/export.jsp b/web/format/conf/export.jsp new file mode 100644 index 0000000..ec8b1b3 --- /dev/null +++ b/web/format/conf/export.jsp @@ -0,0 +1,60 @@ +<%-- + The contents of this file are subject to the Mozilla Public License Version 1.1 + (the "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at . + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + WARRANTY OF ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + The Original Code is the Venice Web Communities System. + + The Initial Developer of the Original Code is Eric J. Bowersox , + for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + + Contributor(s): +--%> +<%@ page import = "java.util.*" %> +<%@ page import = "com.silverwrist.venice.core.*" %> +<%@ page import = "com.silverwrist.venice.ui.conf.CurrentConference" %> +<%@ page import = "com.silverwrist.venice.ui.view.JSPView" %> +<%@ taglib uri="/tlds/util" prefix="util" %> +<%@ taglib uri="/tlds/community" prefix="comm" %> +<%@ taglib uri="/tlds/conference" prefix="conf" %> +<% + JSPView view = JSPView.get(request); + final List topics = (List)(view.getRequestAttribute("topic.list")); +%> + + Export Messages + Conference: + + + + + + +
+ + <% for (Iterator it=topics.iterator(); it.hasNext(); ) { %> + <% TopicContext t = (TopicContext)(it.next()); %> + + + + + + <% } // end for %> +
+ Select Topics to Export: +
+ + <%= t.getTopicNumber() %>. + <%= t.getName() %> +

+ +
+    +
+ +
diff --git a/web/format/conf/import.jsp b/web/format/conf/import.jsp new file mode 100644 index 0000000..170e46b --- /dev/null +++ b/web/format/conf/import.jsp @@ -0,0 +1,53 @@ +<%-- + The contents of this file are subject to the Mozilla Public License Version 1.1 + (the "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at . + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + WARRANTY OF ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + The Original Code is the Venice Web Communities System. + + The Initial Developer of the Original Code is Eric J. Bowersox , + for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + + Contributor(s): +--%> +<%@ page import = "java.util.*" %> +<%@ page import = "com.silverwrist.venice.core.*" %> +<%@ page import = "com.silverwrist.venice.ui.conf.CurrentConference" %> +<%@ page import = "com.silverwrist.venice.ui.view.JSPView" %> +<%@ taglib uri="/tlds/util" prefix="util" %> +<%@ taglib uri="/tlds/community" prefix="comm" %> +<%@ taglib uri="/tlds/conference" prefix="conf" %> +<% + JSPView view = JSPView.get(request); +%> + + Import Messages + Conference: + + + + + + +
+
+ Content data to be uploaded: +
+ + Match existing topics in the conference: +
+ + Create new topics if they don't exist +
+
+    +
+
diff --git a/web/format/conf/import_results.jsp b/web/format/conf/import_results.jsp new file mode 100644 index 0000000..ab989e5 --- /dev/null +++ b/web/format/conf/import_results.jsp @@ -0,0 +1,48 @@ +<%-- + The contents of this file are subject to the Mozilla Public License Version 1.1 + (the "License"); you may not use this file except in compliance with the License. + You may obtain a copy of the License at . + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT + WARRANTY OF ANY KIND, either express or implied. See the License for the specific + language governing rights and limitations under the License. + + The Original Code is the Venice Web Communities System. + + The Initial Developer of the Original Code is Eric J. Bowersox , + for Silverwrist Design Studios. Portions created by Eric J. Bowersox are + Copyright (C) 2004 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. + + Contributor(s): +--%> +<%@ page import = "java.util.*" %> +<%@ page import = "com.silverwrist.venice.core.*" %> +<%@ page import = "com.silverwrist.venice.ui.conf.CurrentConference" %> +<%@ page import = "com.silverwrist.venice.ui.view.JSPView" %> +<%@ taglib uri="/tlds/util" prefix="util" %> +<%@ taglib uri="/tlds/community" prefix="comm" %> +<%@ taglib uri="/tlds/conference" prefix="conf" %> +<% + JSPView view = JSPView.get(request); + List msgs = (List)(view.getRequestAttribute("output.list")); +%> +Import Messages - Results + + Results of Import + to Conference: + + +
+ + conf/manage_conf.js.vs?cc=&conf= + Return to Manage Conference Menu +

+ <% if (msgs.isEmpty()) { %> + No error messages from import. + <% } else { %> + <% for (Iterator it=msgs.iterator(); it.hasNext(); ) { %> + <% String s = (String)(it.next()); %> + <%= s %>
+ <% } // end for %> + <% } // end if %> +

diff --git a/web/images/classic/export.jpg b/web/images/classic/export.jpg new file mode 100644 index 0000000000000000000000000000000000000000..611c6a8820cb3d2e6298c51c2500306b334ce95d GIT binary patch literal 2616 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 bhTgFZ27Hk8HZ9lZEupcK*iLkaVom=GP4%KX literal 0 HcmV?d00001 diff --git a/web/images/classic/import.jpg b/web/images/classic/import.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6324dc078792d1db3d3fb82f8133a41d956114c7 GIT binary patch literal 2579 zcmbu1i8s^>AI5((V~=6%d)6_O%2-22CG?A#l&mWdcQON=oiBnDBI zkiD`_BH?1JAyd|vSNFW{dH;a>Jm-ARdCv2FKCE%p3?N{68GRW5fk1%8u>sZ*AZB{Y z$2-*Lnp{{wsK1<(zmJ?b8e_+r1WbRv{VV;1{42oEF{=%LaRK6h9Rvgez%UR524Zy{ z=YPP!|HZ|@$<1@z=nDWKFgpYc=HNI1`ltRG**IYGoEjGaE z6%>_`+97{t(uTesZ>5i+02p$7{wH=e5E#P2#mRl#8pDpsY;4CYPIm79k|As`c@2R5 zqP?JkH;0fgo)c-}5T7ZkX!_*!(58~+t=X?yW=^C3@l)ctxN5;cI= za1KugW$X!C|6gv0xQ&~U-}V1iqm(Z`P+&XbbRv_;@%=i#TDA(#C^9&$Gz&4R);Sa; zBxxt01GBL^i)lLQ z7N8EsPwaX7#tVXt3*vgBG72A!%e&luyfr5JRT=+xGfApDKT$d6aHs3eGRG4vsr~(5 z@3Gsb#ORKK)nusz%i~>WjpJ|~WUg-0J%*}+JEM@Y@MhioMb8!K28U1+M(4Zp(o;(5bSCcWHCUWeh;k?*_*ZHK7rC|x zq*sHT@NMIVJA0XnF=^ppM|C=Td@SI!?AB7)g&0Rt(-$iqZ>2HSd%y5icF#c1HyHiC z^roDdZTmi8aQue8wCoDSXMfNu$ZlPO(cyA?we?UOPrtlA9VoVc;|jd|h;YFpEZJqL z|50UeY1Rmm=cz@?^zP+{+LII2hHx!GX)SL&&lz|J*)yKgPp!jl|Q-}K=T7S_6DgSt&>`kxj`OuKxRCWk^f z+$Sy>`=&F}UPr@ShLyJq*mG(u)*8tlGOwl2tCyPVU%s|?u1q6fILB71;x30Y+C)g} zok#K?P$W5Pr6@i@z=o>9+f=N8<3-DviaJ2@hm#Nl=T%4=J$Veew6fn{Be~HdPZIk1o8xji|APgtfgldpY8T`_?~mdK4)CX{BW`{S>UGYkP(X9Sq3Q;I0^AY5nP zQ3106R4(&%Lp#>G^`yS-o|7&5gUp*Z{O3UR-zS=4bgI-TI;RJvdcB(ljRsS8 z6Hje_vD`j^d9`d9Bv|z53h_X#Vxun@d%KF9J3gZTs(7K#sVK$-MNHy zNUY0d0ZyyQDdd*YQ3bZq3Q>RrY<;@U1@Kp_aiUJ1sm|r1ye)HLZtLjkyZR-W8t=xPOca~JJ6L{Qf8a@Nn4!e z*Z0^uh$yPbETw-3>RLFPOaInU-8==S@vP8(urCMS7FS1&Rp4=EF7Ie)qaOAF}6Tp$30&GjP)$^_+q=sd~i|CiNdU_*QlY~j+{d}uVK7t`fvV}mW3P6+wDHTDo(ZsYgyp!~Kfp_CZI6WS<%N$hpn}3t zBVZveF*3e>-NDo-)^=C=^T!o2ZydumAsMx1pgKLZqH?IwmxRSwrN?o$6gIWSd}!Bb zZZB{*AUNy}lvXy~RyFaMx6CRzC9JFpzA##fh!X@Ut#NnT>+*>zNWdlK0OmD9f043$ i08!H{?g2G7d(*?iL{C(2XeAF%TDA?^oQ{fRef$r0$aFja literal 0 HcmV?d00001 diff --git a/web/images/gelcap/export.jpg b/web/images/gelcap/export.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c84e5e5c7be320ef4fd8e539f5f94fe59e55adb GIT binary patch literal 3039 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 literal 0 HcmV?d00001 diff --git a/web/images/gelcap/import.jpg b/web/images/gelcap/import.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8d12ff0c68618f9ff6a931b05914e68d541af7e8 GIT binary patch literal 3204 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 m{P(s*na?f@uCX= literal 0 HcmV?d00001