001: package org.tigris.scarab.screens;
002:
003: /* ================================================================
004: * Copyright (c) 2003 CollabNet. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are
008: * met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowlegement: "This product includes
019: * software developed by CollabNet <http://www.collab.net/>."
020: * Alternately, this acknowlegement may appear in the software itself, if
021: * and wherever such third-party acknowlegements normally appear.
022: *
023: * 4. The hosted project names must not be used to endorse or promote
024: * products derived from this software without prior written
025: * permission. For written permission, please contact info@collab.net.
026: *
027: * 5. Products derived from this software may not use the "Tigris" or
028: * "Scarab" names nor may "Tigris" or "Scarab" appear in their names without
029: * prior written permission of CollabNet.
030: *
031: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
032: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
033: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
034: * IN NO EVENT SHALL COLLABNET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
035: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
036: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
037: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
038: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
039: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
040: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
041: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
042: *
043: * ====================================================================
044: *
045: * This software consists of voluntary contributions made by many
046: * individuals on behalf of CollabNet.
047: */
048:
049: import java.io.Writer;
050: import java.io.IOException;
051: import java.io.PrintWriter;
052: import java.io.OutputStreamWriter;
053: import java.util.List;
054:
055: import org.apache.commons.lang.StringUtils;
056:
057: import org.apache.turbine.Turbine;
058: import org.apache.turbine.RunData;
059: import org.apache.turbine.TemplateContext;
060:
061: import org.tigris.scarab.util.ScarabUtil;
062: import org.tigris.scarab.util.export.ExportFormat;
063:
064: /**
065: * <p>Sends file contents directly to the output stream, setting the
066: * <code>Content-Type</code> and writing back to the browser a
067: * tab-delimited file (Excel digests this fine). We used to use <a
068: * href="http://jakarta.apache.org/poi/">POI</a> to compose an Excel
069: * binary data file, but its outrageous memory consumption didn't
070: * scale for large result sets. POI assembles the its output in
071: * memory. After study of the native OLE2 Excel file format, it
072: * appears very difficult to generate the file in another fashion.</p>
073: *
074: * <p>Regards output encoding, for now we're assuming the response
075: * stream is appropriately set upon fetching. Also, we're assuming
076: * that Excel will do the right thing on receipt of our TSV file with
077: * Japanese or other multibyte characters (we're not setting an
078: * encoding on the <code>Content-Type</code> we return). Both of the
079: * above to be verified.</p>
080: *
081: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
082: * @author <a href="mailto:stack@collab.net">St.Ack</a>
083: * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
084: * @since Scarab 1.0
085: */
086: class DataExport extends Default {
087: /**
088: * What to show if a cell is empty. The empty string is dealt
089: * with best by spreadsheet applications.
090: */
091: protected static final String NO_CONTENT = "";
092:
093: /**
094: * Sets the <code>Content-Type</code> header for the response.
095: * Since this assumes we're writing the reponse ourself, indicates
096: * no target to render by setting it to <code>null</code>.
097: */
098: public void doBuildTemplate(RunData data, TemplateContext context)
099: throws Exception {
100: super .doBuildTemplate(data, context);
101: String format = ScarabUtil.findValue(data,
102: ExportFormat.KEY_NAME);
103:
104: // look for a configuration toggle for the encoding to which to
105: // export. TODO : make this per request configurable (with a per-
106: // language default) to allow use of scarab in a multilingual
107: // environment.
108: if (ExportFormat.EXCEL_FORMAT.equalsIgnoreCase(format)) {
109: data.getResponse().setContentType(
110: "application/vnd.ms-excel");
111: } else {
112: // we want to set a charset on the response -- so clients
113: // can detect it properly -- if we have a known encoding
114: String encoding = getEncodingForExport(data);
115: String contentType = "text/plain";
116: if (encoding != null && !encoding.equals("")) {
117: contentType = contentType + "; charset=" + encoding;
118: }
119: data.getResponse().setContentType(contentType);
120: }
121: // Since we're streaming the TSV content directly from our
122: // data source, we don't know its length ahead of time.
123: //data.getResponse().setContentLength(?);
124:
125: // FIXME: Provide work hooks here...
126: //TSVPrinter printer = new TSVPrinter(data.getResponse().getWriter());
127: //writeHeading(printer, mitlist, l10n, rmuas);
128: //writeRows(printer, mitlist, l10n, scarabR, rmuas);
129:
130: // Above we sent the response, so no target to render
131: data.setTarget(null);
132: }
133:
134: /**
135: * This function encapsulates the logic of determining which encoding
136: * to use. Right now, the encoding isn't per-request, but that should
137: * be changed.
138: */
139: protected String getEncodingForExport(RunData data) {
140: String encoding = Turbine.getConfiguration().getString(
141: "scarab.dataexport.encoding");
142: return encoding;
143: }
144:
145: /**
146: * This function is available to subclasses -- it is used to provide
147: * a Writer based on the current request and the site configuration,
148: * taking encoding issues into consideration.
149: */
150: protected Writer getWriter(RunData data) throws IOException {
151: Writer writer = null;
152: String encoding = getEncodingForExport(data);
153: if (encoding != null && !encoding.equals("")) {
154: writer = new OutputStreamWriter(data.getResponse()
155: .getOutputStream(), encoding);
156: } else {
157: writer = data.getResponse().getWriter();
158: }
159: return writer;
160: }
161:
162: /**
163: * Escape any commas in passed string.
164: *
165: * @param s String to check.
166: * @return Passed string with commas escaped.
167: */
168: protected String escapeCommas(String s) {
169: // Not sure how to escape commas. What to use instead? Quote
170: // for now.
171: return quote(s);
172: }
173:
174: protected final boolean containsElements(List l) {
175: return l != null && !l.isEmpty();
176: }
177:
178: /**
179: * Quote the string argument.
180: *
181: * @param s Text to quote.
182: * @return Passed string, quoted.
183: */
184: private static String quote(String s) {
185: return '"' + StringUtils.replace(s, "\"", "\"\"") + '"';
186: }
187:
188: /**
189: * Uses a <code>PrintWriter</code> internally to do actual
190: * writing. If content with tabs and newlines in it is
191: * double-quoted, Excel does the Right Thing when parsing.
192: *
193: * @see <a href="http://ostermiller.org/utils/ExcelCSVPrinter.java.html">ExcelCSVPrinter.java</a>
194: */
195: protected class TSVPrinter {
196: /**
197: * Flag indicating start of a new line.
198: */
199: private boolean lineStart = true;
200:
201: /**
202: * Printer write on.
203: */
204: private PrintWriter printer = null;
205:
206: /**
207: * Creates a new instance.
208: *
209: * @param writer Writer to output to.
210: * @throws IllegalArgumentException If <code>writer</code> is
211: * <code>null</code>.
212: */
213: public TSVPrinter(Writer writer) {
214: if (writer == null) {
215: //TODO [HD] Shouldn't this be better a NPE ?
216: // That better hits the truth...
217: throw new IllegalArgumentException(
218: "TSVPrinter constructor requires a non-null writer"); //EXCEPTION
219: }
220:
221: if (writer instanceof PrintWriter) {
222: this .printer = (PrintWriter) writer;
223: } else {
224: this .printer = new PrintWriter(writer);
225: }
226: }
227:
228: /**
229: * Prints one field at a time.
230: */
231: public void print(String s) {
232: if (!lineStart) {
233: // Print a tab seperator before we print our field content.
234: printer.print('\t');
235: }
236: lineStart = false;
237:
238: if (StringUtils.isNotEmpty(s)) {
239: printer.print(escape(s));
240: }
241: }
242:
243: /**
244: * Must be called when done writing a line -- this prints a newline
245: * and flushes the printer.
246: */
247: public void println() {
248: printer.println();
249: printer.flush();
250: lineStart = true;
251: }
252:
253: /**
254: * Quote the string argument.
255: *
256: * @param s Text to quote.
257: * @return Passed string, quoted.
258: */
259: protected String quote(String s) {
260: return DataExport.quote(s);
261: }
262:
263: /**
264: * If the passed string has any problematic characters, quote the
265: * whole thing after escaping any quotes already present. Excel
266: * does the right thing parsing if it gets quoted content.
267: *
268: * @param s String to escape.
269: * @return An escaped version of the passed string.
270: */
271: private String escape(String s) {
272: if (StringUtils.isNotEmpty(s)) {
273: for (int i = 0; i < s.length(); i++) {
274: char c = s.charAt(i);
275: if (c == '"' || c == '\t' || c == '\n' || c == '\r') {
276: s = quote(s);
277: break;
278: }
279: }
280: }
281:
282: return s;
283: }
284: }
285: }
|