001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: DatabaseRawStore.java 3634 2007-01-08 21:42:24Z gbevin $
007: */
008: package com.uwyn.rife.cmf.dam.contentstores;
009:
010: import com.uwyn.rife.cmf.dam.contentstores.exceptions.*;
011: import com.uwyn.rife.database.*;
012: import com.uwyn.rife.database.queries.*;
013:
014: import com.uwyn.rife.cmf.Content;
015: import com.uwyn.rife.cmf.ContentInfo;
016: import com.uwyn.rife.cmf.MimeType;
017: import com.uwyn.rife.cmf.dam.ContentDataUser;
018: import com.uwyn.rife.cmf.dam.ContentStore;
019: import com.uwyn.rife.cmf.dam.exceptions.ContentManagerException;
020: import com.uwyn.rife.cmf.format.Formatter;
021: import com.uwyn.rife.cmf.format.exceptions.FormatException;
022: import com.uwyn.rife.cmf.transform.ContentTransformer;
023: import com.uwyn.rife.config.RifeConfig;
024: import com.uwyn.rife.database.exceptions.DatabaseException;
025: import com.uwyn.rife.engine.ElementSupport;
026: import com.uwyn.rife.tools.Convert;
027: import com.uwyn.rife.tools.ExceptionUtils;
028: import com.uwyn.rife.tools.FileUtils;
029: import com.uwyn.rife.tools.InnerClassException;
030: import java.io.BufferedInputStream;
031: import java.io.ByteArrayInputStream;
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.io.OutputStream;
035: import java.sql.ResultSet;
036: import java.sql.SQLException;
037: import java.util.ArrayList;
038: import java.util.Collection;
039: import java.util.Map;
040: import java.util.logging.Logger;
041: import javax.servlet.http.HttpServletResponse;
042:
043: public abstract class DatabaseRawStore extends DbQueryManager implements
044: ContentStore {
045: private ArrayList<MimeType> mMimeTypes = new ArrayList<MimeType>();
046:
047: public DatabaseRawStore(Datasource datasource) {
048: super (datasource);
049:
050: addMimeType(MimeType.RAW);
051: }
052:
053: protected void addMimeType(MimeType mimeType) {
054: mMimeTypes.add(mimeType);
055: }
056:
057: public Collection<MimeType> getSupportedMimeTypes() {
058: return mMimeTypes;
059: }
060:
061: public String getContentType(ContentInfo contentInfo) {
062: MimeType mimeType = MimeType.getMimeType(contentInfo
063: .getMimeType());
064: if (!getSupportedMimeTypes().contains(mimeType)) {
065: return null;
066: }
067:
068: Map<String, String> attributes = contentInfo.getAttributes();
069: if (attributes != null) {
070: if (attributes.containsKey("content-type")) {
071: return attributes.get("content-type");
072: }
073: }
074: if (contentInfo.hasName()) {
075: return RifeConfig.Mime.getMimeType(FileUtils
076: .getExtension(contentInfo.getName()));
077: }
078:
079: return null;
080: }
081:
082: public Formatter getFormatter(MimeType mimeType, boolean fragment) {
083: if (!getSupportedMimeTypes().contains(mimeType)) {
084: return null;
085: }
086: return mimeType.getFormatter();
087: }
088:
089: public String getContentForHtml(int id, ContentInfo info,
090: ElementSupport element, String serveContentExitName)
091: throws ContentManagerException {
092: return "";
093: }
094:
095: protected boolean _install(CreateTable createTableContentInfo,
096: CreateTable createTableContentChunk)
097: throws ContentManagerException {
098: assert createTableContentInfo != null;
099: assert createTableContentChunk != null;
100:
101: try {
102: executeUpdate(createTableContentInfo);
103: executeUpdate(createTableContentChunk);
104: } catch (DatabaseException e) {
105: throw new InstallContentStoreErrorException(e);
106: }
107:
108: return true;
109: }
110:
111: protected boolean _remove(DropTable dropTableContentInfo,
112: DropTable dropTableContentChunk)
113: throws ContentManagerException {
114: assert dropTableContentInfo != null;
115:
116: try {
117: executeUpdate(dropTableContentChunk);
118: executeUpdate(dropTableContentInfo);
119: } catch (DatabaseException e) {
120: throw new RemoveContentStoreErrorException(e);
121: }
122:
123: return true;
124: }
125:
126: protected boolean _deleteContentData(
127: final Delete deleteContentInfo,
128: final Delete deleteContentChunk, final int id)
129: throws ContentManagerException {
130: if (id < 0)
131: throw new IllegalArgumentException("id must be positive");
132:
133: assert deleteContentInfo != null;
134: assert deleteContentChunk != null;
135:
136: Boolean result = null;
137:
138: try {
139: result = inTransaction(new DbTransactionUser() {
140: public Boolean useTransaction()
141: throws InnerClassException {
142: if (0 == executeUpdate(deleteContentChunk,
143: new DbPreparedStatementHandler() {
144: public void setParameters(
145: DbPreparedStatement statement) {
146: statement.setInt("contentId", id);
147: }
148: })) {
149: return false;
150: }
151:
152: return 0 != executeUpdate(deleteContentInfo,
153: new DbPreparedStatementHandler() {
154: public void setParameters(
155: DbPreparedStatement statement) {
156: statement.setInt("contentId", id);
157: }
158: });
159:
160: }
161: });
162: } catch (DatabaseException e) {
163: throw new DeleteContentDataErrorException(id, e);
164: }
165:
166: return result != null && result.booleanValue();
167: }
168:
169: protected int _getSize(Select retrieveSize, final int id)
170: throws ContentManagerException {
171: if (id < 0)
172: throw new IllegalArgumentException("id must be positive");
173:
174: assert retrieveSize != null;
175:
176: try {
177: return executeGetFirstInt(retrieveSize,
178: new DbPreparedStatementHandler() {
179: public void setParameters(
180: DbPreparedStatement statement) {
181: statement.setInt("contentId", id);
182: }
183: });
184: } catch (DatabaseException e) {
185: throw new RetrieveSizeErrorException(id, e);
186: }
187: }
188:
189: protected boolean _hasContentData(Select hasContentData,
190: final int id) throws ContentManagerException {
191: if (id < 0)
192: throw new IllegalArgumentException("id must be positive");
193:
194: assert hasContentData != null;
195:
196: try {
197: return executeHasResultRows(hasContentData,
198: new DbPreparedStatementHandler() {
199: public void setParameters(
200: DbPreparedStatement statement) {
201: statement.setInt("contentId", id);
202: }
203: });
204: } catch (DatabaseException e) {
205: throw new HasContentDataErrorException(id, e);
206: }
207: }
208:
209: protected String getContentSizeColumnName() {
210: return "size";
211: }
212:
213: protected boolean _storeContentData(final Insert storeContentInfo,
214: final Insert storeContentChunk, final int id,
215: Content content, ContentTransformer transformer)
216: throws ContentManagerException {
217: if (id < 0)
218: throw new IllegalArgumentException("id must be positive");
219: if (content != null && content.getData() != null
220: && !(content.getData() instanceof InputStream)
221: && !(content.getData() instanceof byte[]))
222: throw new IllegalArgumentException(
223: "the content data must be of type InputStream or byte[]");
224:
225: assert storeContentInfo != null;
226: assert storeContentChunk != null;
227:
228: final InputStream typed_data;
229:
230: if (null == content || null == content.getData()) {
231: typed_data = null;
232: } else {
233: if (content.getData() instanceof byte[]) {
234: Content cloned_content = content.clone();
235: cloned_content.setData(new ByteArrayInputStream(
236: (byte[]) content.getData()));
237: cloned_content.setCachedLoadedData(null);
238: content = cloned_content;
239: }
240:
241: Formatter formatter = null;
242: if (!Convert.toBoolean(content.getAttribute("unformatted"),
243: false)) {
244: formatter = getFormatter(content.getMimeType(), content
245: .isFragment());
246: }
247:
248: if (formatter != null) {
249: try {
250: typed_data = (InputStream) formatter.format(
251: content, transformer);
252: } catch (FormatException e) {
253: throw new StoreContentDataErrorException(id, e);
254: }
255: } else {
256: typed_data = (InputStream) content.getData();
257: }
258: }
259:
260: // store the data
261: try {
262: Boolean success = inTransaction(new DbTransactionUser() {
263: public Object useTransaction()
264: throws InnerClassException {
265: try {
266: final int size = storeChunks(storeContentChunk,
267: id, typed_data);
268: if (size < 0) {
269: rollback();
270: }
271:
272: if (executeUpdate(storeContentInfo,
273: new DbPreparedStatementHandler() {
274: public void setParameters(
275: DbPreparedStatement statement) {
276: statement
277: .setInt("contentId", id)
278: .setInt(
279: getContentSizeColumnName(),
280: size);
281: }
282: }) <= 0) {
283: rollback();
284: }
285: } catch (IOException e) {
286: throwException(e);
287: }
288:
289: return true;
290: }
291: });
292:
293: return null != success && success.booleanValue();
294: } catch (InnerClassException e) {
295: throw new StoreContentDataErrorException(id, e.getCause());
296: } catch (DatabaseException e) {
297: throw new StoreContentDataErrorException(id, e);
298: }
299: }
300:
301: protected int storeChunks(Insert storeContentChunk, final int id,
302: InputStream data) throws IOException {
303: class Scope {
304: int size = 0;
305: int length = -1;
306: int ordinal = 0;
307: byte[] buffer = null;
308: }
309: final Scope s = new Scope();
310:
311: if (data != null) {
312: s.buffer = new byte[65535];
313: while ((s.length = data.read(s.buffer)) != -1) {
314: s.size += s.length;
315:
316: if (executeUpdate(storeContentChunk,
317: new DbPreparedStatementHandler() {
318: public void setParameters(
319: DbPreparedStatement statement) {
320: statement
321: .setInt("contentId", id)
322: .setInt("ordinal", s.ordinal)
323: .setBinaryStream(
324: "chunk",
325: new ByteArrayInputStream(
326: s.buffer),
327: s.length);
328: }
329: }) <= 0) {
330: return -1;
331: }
332:
333: s.ordinal++;
334: }
335: }
336:
337: return s.size;
338: }
339:
340: protected int storeChunksNoStream(Insert storeContentChunk,
341: final int id, InputStream data) throws IOException {
342: class Scope {
343: int size = 0;
344: int length = -1;
345: int ordinal = 0;
346: byte[] buffer = null;
347: byte[] buffer_swp = null;
348: }
349: final Scope s = new Scope();
350:
351: if (data != null) {
352: s.buffer = new byte[65535];
353: while ((s.length = data.read(s.buffer)) != -1) {
354: s.size += s.length;
355:
356: if (s.length < s.buffer.length) {
357: byte[] new_buffer = new byte[s.length];
358: System.arraycopy(s.buffer, 0, new_buffer, 0,
359: s.length);
360: s.buffer_swp = s.buffer;
361: s.buffer = new_buffer;
362: }
363:
364: if (executeUpdate(storeContentChunk,
365: new DbPreparedStatementHandler() {
366: public void setParameters(
367: DbPreparedStatement statement) {
368: statement.setInt("contentId", id)
369: .setInt("ordinal", s.ordinal)
370: .setBytes("chunk", s.buffer);
371: }
372: }) <= 0) {
373: return -1;
374: }
375:
376: if (s.buffer_swp != null) {
377: s.buffer = s.buffer_swp;
378: s.buffer_swp = null;
379: }
380:
381: s.ordinal++;
382: }
383: }
384:
385: return s.size;
386: }
387:
388: protected <ResultType> ResultType _useContentData(
389: Select retrieveContentChunks, final int id,
390: ContentDataUser user) throws ContentManagerException {
391: if (id < 0)
392: throw new IllegalArgumentException("id must be positive");
393: if (null == user)
394: throw new IllegalArgumentException("user can't be null");
395:
396: assert retrieveContentChunks != null;
397:
398: try {
399: InputStream data = RawContentStream.getInstance(this ,
400: retrieveContentChunks, id);
401: try {
402: return (ResultType) user.useContentData(data);
403: } finally {
404: if (data != null) {
405: try {
406: data.close();
407: } catch (IOException e) {
408: throw new UseContentDataErrorException(id, e);
409: }
410: }
411: }
412: } catch (DatabaseException e) {
413: throw new UseContentDataErrorException(id, e);
414: }
415: }
416:
417: protected DbPreparedStatement getStreamPreparedStatement(
418: Query query, DbConnection connection) {
419: DbPreparedStatement statement = connection
420: .getPreparedStatement(query,
421: ResultSet.TYPE_FORWARD_ONLY,
422: ResultSet.CONCUR_READ_ONLY,
423: ResultSet.CLOSE_CURSORS_AT_COMMIT);
424: statement.setFetchDirection(ResultSet.FETCH_FORWARD);
425: statement.setFetchSize(1);
426: return statement;
427: }
428:
429: protected void _serveContentData(
430: final Select retrieveContentChunks,
431: final ElementSupport element, final int id)
432: throws ContentManagerException {
433: if (null == element)
434: throw new IllegalArgumentException("element can't be null");
435:
436: if (id < 0) {
437: element.defer();
438: return;
439: }
440:
441: assert retrieveContentChunks != null;
442:
443: // set the content length header
444: final int size = getSize(id);
445: if (size < 0) {
446: element.defer();
447: return;
448: }
449: element.setContentLength(size);
450:
451: try {
452: Boolean success = executeQuery(retrieveContentChunks,
453: new DbPreparedStatementHandler() {
454: public DbPreparedStatement getPreparedStatement(
455: Query query, DbConnection connection) {
456: return getStreamPreparedStatement(query,
457: connection);
458: }
459:
460: public void setParameters(
461: DbPreparedStatement statement) {
462: statement.setInt("contentId", id);
463: }
464:
465: public Object concludeResults(
466: DbResultSet resultset)
467: throws SQLException {
468: if (!resultset.next()) {
469: return false;
470: }
471:
472: // output the content
473: OutputStream os = element.getOutputStream();
474: try {
475: serveChunks(resultset, os, size);
476:
477: os.flush();
478: } catch (IOException e) {
479: // don't do anything, the client has probably disconnected
480: }
481:
482: return true;
483: }
484: });
485:
486: if (null == success || !success.booleanValue()) {
487: element.defer();
488: }
489: } catch (DatabaseException e) {
490: Logger.getLogger("com.uwyn.rife.cmf").severe(
491: ExceptionUtils.getExceptionStackTrace(e));
492: element
493: .setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
494: }
495: }
496:
497: protected void serveChunks(DbResultSet resultset, OutputStream os,
498: int size) throws SQLException, IOException {
499: byte[] buffer = new byte[512];
500: do {
501: InputStream is = resultset.getBinaryStream("chunk");
502: BufferedInputStream buffered_raw_is = new BufferedInputStream(
503: is, 512);
504: int buffer_size = 0;
505: try {
506: while ((buffer_size = buffered_raw_is.read(buffer)) != -1) {
507: os.write(buffer, 0, buffer_size);
508: }
509: } catch (IOException e) {
510: // don't do anything, the client has probably disconnected
511: }
512: } while (resultset.next());
513: }
514: }
|