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: DatabaseContent.java 3634 2007-01-08 21:42:24Z gbevin $
007: */
008: package com.uwyn.rife.cmf.dam.contentmanagers;
009:
010: import com.uwyn.rife.database.*;
011: import com.uwyn.rife.database.queries.*;
012:
013: import com.uwyn.rife.cmf.Content;
014: import com.uwyn.rife.cmf.ContentRepository;
015: import com.uwyn.rife.cmf.MimeType;
016: import com.uwyn.rife.cmf.dam.ContentDataUser;
017: import com.uwyn.rife.cmf.dam.ContentManager;
018: import com.uwyn.rife.cmf.dam.ContentStore;
019: import com.uwyn.rife.cmf.dam.contentmanagers.exceptions.InstallContentErrorException;
020: import com.uwyn.rife.cmf.dam.contentmanagers.exceptions.RemoveContentErrorException;
021: import com.uwyn.rife.cmf.dam.contentmanagers.exceptions.UnknownContentRepositoryException;
022: import com.uwyn.rife.cmf.dam.contentmanagers.exceptions.UnsupportedMimeTypeException;
023: import com.uwyn.rife.cmf.dam.contentstores.DatabaseImageStoreFactory;
024: import com.uwyn.rife.cmf.dam.contentstores.DatabaseRawStoreFactory;
025: import com.uwyn.rife.cmf.dam.contentstores.DatabaseTextStoreFactory;
026: import com.uwyn.rife.cmf.dam.exceptions.ContentManagerException;
027: import com.uwyn.rife.cmf.transform.ContentTransformer;
028: import com.uwyn.rife.database.exceptions.DatabaseException;
029: import com.uwyn.rife.datastructures.Pair;
030: import com.uwyn.rife.engine.ElementSupport;
031: import com.uwyn.rife.tools.InnerClassException;
032: import java.sql.ResultSet;
033: import java.sql.SQLException;
034: import java.sql.Timestamp;
035: import java.sql.Types;
036: import java.util.ArrayList;
037: import java.util.HashMap;
038: import java.util.Map;
039: import javax.servlet.http.HttpServletResponse;
040:
041: public abstract class DatabaseContent extends DbQueryManager implements
042: ContentManager {
043: protected ArrayList<ContentStore> mStores = null;
044: protected HashMap<MimeType, ContentStore> mMimeMapping = null;
045:
046: public DatabaseContent(Datasource datasource) {
047: super (datasource);
048:
049: mStores = new ArrayList<ContentStore>();
050: mStores.add(DatabaseTextStoreFactory
051: .getInstance(getDatasource()));
052: mStores.add(DatabaseImageStoreFactory
053: .getInstance(getDatasource()));
054: mStores.add(DatabaseRawStoreFactory
055: .getInstance(getDatasource()));
056:
057: mMimeMapping = new HashMap<MimeType, ContentStore>();
058: for (ContentStore store : mStores) {
059: for (MimeType mime_type : store.getSupportedMimeTypes()) {
060: mMimeMapping.put(mime_type, store);
061: }
062: }
063: }
064:
065: public abstract DatabaseContentInfo getContentInfo(String location)
066: throws ContentManagerException;
067:
068: protected boolean _install(
069: CreateSequence createSequenceContentRepository,
070: CreateSequence createSequenceContentInfo,
071: CreateTable createTableContentRepository,
072: CreateTable createTableContentInfo,
073: CreateTable createTableContentAttribute,
074: CreateTable createTableContentProperty)
075: throws ContentManagerException {
076: assert createSequenceContentRepository != null;
077: assert createSequenceContentInfo != null;
078: assert createTableContentRepository != null;
079: assert createTableContentInfo != null;
080: assert createTableContentAttribute != null;
081: assert createTableContentProperty != null;
082:
083: try {
084: executeUpdate(createSequenceContentRepository);
085: executeUpdate(createSequenceContentInfo);
086: executeUpdate(createTableContentRepository);
087: executeUpdate(createTableContentInfo);
088: executeUpdate(createTableContentAttribute);
089: executeUpdate(createTableContentProperty);
090:
091: createRepository(ContentRepository.DEFAULT);
092:
093: for (ContentStore store : mStores) {
094: store.install();
095: }
096: } catch (DatabaseException e) {
097: throw new InstallContentErrorException(e);
098: }
099:
100: return true;
101: }
102:
103: protected boolean _remove(
104: DropSequence dropSequenceContentRepository,
105: DropSequence dropSequenceContentInfo,
106: DropTable dropTableContentRepository,
107: DropTable dropTableContentInfo,
108: DropTable dropTableContentAttribute,
109: DropTable dropTableContentProperty)
110: throws ContentManagerException {
111: assert dropSequenceContentRepository != null;
112: assert dropSequenceContentInfo != null;
113: assert dropTableContentRepository != null;
114: assert dropTableContentInfo != null;
115: assert dropTableContentAttribute != null;
116: assert dropTableContentProperty != null;
117:
118: try {
119: for (ContentStore store : mStores) {
120: store.remove();
121: }
122:
123: executeUpdate(dropTableContentProperty);
124: executeUpdate(dropTableContentAttribute);
125: executeUpdate(dropTableContentInfo);
126: executeUpdate(dropTableContentRepository);
127: executeUpdate(dropSequenceContentInfo);
128: executeUpdate(dropSequenceContentRepository);
129: } catch (DatabaseException e) {
130: throw new RemoveContentErrorException(e);
131: }
132:
133: return true;
134: }
135:
136: protected String getValueColumnName() {
137: return "value";
138: }
139:
140: protected boolean _createRepository(
141: final SequenceValue getContentRepositoryId,
142: final Insert storeContentRepository, final String name)
143: throws ContentManagerException {
144: if (null == name)
145: throw new IllegalArgumentException("name can't be null");
146: if (0 == name.length())
147: throw new IllegalArgumentException("name can't be empty");
148:
149: assert getContentRepositoryId != null;
150: assert storeContentRepository != null;
151:
152: Boolean result = null;
153:
154: try {
155: result = inTransaction(new DbTransactionUser() {
156: public Boolean useTransaction()
157: throws InnerClassException {
158: // get new repository id
159: final int id = executeGetFirstInt(getContentRepositoryId);
160:
161: // store the content
162: return executeUpdate(storeContentRepository,
163: new DbPreparedStatementHandler() {
164: public void setParameters(
165: DbPreparedStatement statement) {
166: statement
167: .setInt("repositoryId", id)
168: .setString("name", name);
169: }
170: }) > 0;
171: }
172: });
173: } catch (InnerClassException e) {
174: throw (ContentManagerException) e.getCause();
175: }
176:
177: return result != null && result.booleanValue();
178: }
179:
180: protected boolean _containsRepository(
181: final Select containsContentRepository, final String name)
182: throws ContentManagerException {
183: if (null == name)
184: throw new IllegalArgumentException("name can't be null");
185:
186: assert containsContentRepository != null;
187:
188: final String repository;
189: if (0 == name.length()) {
190: repository = ContentRepository.DEFAULT;
191: } else {
192: repository = name;
193: }
194:
195: return executeGetFirstInt(containsContentRepository,
196: new DbPreparedStatementHandler() {
197: public void setParameters(
198: DbPreparedStatement statement) {
199: statement.setString("name", repository);
200: }
201: }) > 0;
202: }
203:
204: protected Pair<String, String> splitLocation(String location) {
205: if (null == location)
206: throw new IllegalArgumentException("location can't be null");
207: if (0 == location.length())
208: throw new IllegalArgumentException(
209: "location can't be empty");
210:
211: int colon_index = location.indexOf(":");
212:
213: String repository = null;
214: String path = null;
215: if (colon_index != -1) {
216: repository = location.substring(0, colon_index);
217: path = location.substring(colon_index + 1);
218: } else {
219: path = location;
220: }
221:
222: if (null == repository || 0 == repository.length()) {
223: repository = ContentRepository.DEFAULT;
224: }
225:
226: if (0 == path.length())
227: throw new IllegalArgumentException("path can't be empty");
228: if (!path.startsWith("/"))
229: throw new IllegalArgumentException(
230: "path needs to be absolute");
231:
232: return new Pair<String, String>(repository, path);
233: }
234:
235: protected boolean _storeContent(final SequenceValue getContentId,
236: final Select getContentRepositoryId,
237: final Insert storeContentInfo,
238: final Insert storeContentAttribute,
239: final Insert storeContentProperty, String location,
240: final Content content, final ContentTransformer transformer)
241: throws ContentManagerException {
242: if (null == content)
243: throw new IllegalArgumentException("content can't be null");
244:
245: final Pair<String, String> split_location = splitLocation(location);
246:
247: assert getContentId != null;
248: assert getContentRepositoryId != null;
249: assert storeContentInfo != null;
250: assert storeContentAttribute != null;
251: assert storeContentProperty != null;
252:
253: final ContentStore store = mMimeMapping.get(content
254: .getMimeType());
255: if (null == store) {
256: throw new UnsupportedMimeTypeException(content
257: .getMimeType());
258: }
259:
260: Boolean result = null;
261:
262: try {
263: result = inTransaction(new DbTransactionUser() {
264: public Boolean useTransaction()
265: throws InnerClassException {
266: // get new content id
267: final int id = executeGetFirstInt(getContentId);
268:
269: // get repository id
270: final int repository_id = executeGetFirstInt(
271: getContentRepositoryId,
272: new DbPreparedStatementHandler() {
273: public void setParameters(
274: DbPreparedStatement statement) {
275: statement.setString("repository",
276: split_location.getFirst());
277: }
278: });
279:
280: // verify the existance of the repository
281: if (-1 == repository_id) {
282: throwException(new UnknownContentRepositoryException(
283: split_location.getFirst()));
284: }
285:
286: // store the content
287: if (executeUpdate(storeContentInfo,
288: new DbPreparedStatementHandler() {
289: public void setParameters(
290: DbPreparedStatement statement) {
291: statement
292: .setInt("contentId", id)
293: .setInt("repositoryId",
294: repository_id)
295: .setString(
296: "path",
297: split_location
298: .getSecond())
299: .setString(
300: "mimeType",
301: content
302: .getMimeType()
303: .toString())
304: .setBoolean(
305: "fragment",
306: content
307: .isFragment());
308: if (content.hasName()) {
309: statement.setString("name",
310: content.getName());
311: } else {
312: statement.setNull("name",
313: Types.VARCHAR);
314: }
315: }
316: }) > 0) {
317: // store the attributes if there are some
318: if (content.hasAttributes()) {
319: for (Map.Entry<String, String> attribute : content
320: .getAttributes().entrySet()) {
321: final String name = attribute.getKey();
322: final String value = attribute
323: .getValue();
324:
325: executeUpdate(
326: storeContentAttribute,
327: new DbPreparedStatementHandler() {
328: public void setParameters(
329: DbPreparedStatement statement) {
330: statement
331: .setInt(
332: "contentId",
333: id)
334: .setString(
335: "name",
336: name)
337: .setString(
338: getValueColumnName(),
339: value);
340: }
341: });
342: }
343: }
344:
345: // put the actual content data in the content store
346: try {
347: if (!store.storeContentData(id, content,
348: transformer)) {
349: rollback();
350: }
351: } catch (ContentManagerException e) {
352: throwException(e);
353: }
354:
355: // store the content data properties if there are some
356: if (content.hasProperties()) {
357: for (Map.Entry<String, String> property : content
358: .getProperties().entrySet()) {
359: final String name = property.getKey();
360: final String value = property
361: .getValue();
362:
363: executeUpdate(
364: storeContentProperty,
365: new DbPreparedStatementHandler() {
366: public void setParameters(
367: DbPreparedStatement statement) {
368: statement
369: .setInt(
370: "contentId",
371: id)
372: .setString(
373: "name",
374: name)
375: .setString(
376: getValueColumnName(),
377: value);
378: }
379: });
380: }
381: }
382:
383: return true;
384: }
385:
386: return false;
387: }
388: });
389: } catch (InnerClassException e) {
390: throw (ContentManagerException) e.getCause();
391: }
392:
393: return result != null && result.booleanValue();
394: }
395:
396: protected boolean _deleteContent(final Select getContentInfo,
397: final Delete deleteContentInfo,
398: final Delete deleteContentAttributes,
399: final Delete deleteContentProperties, String location)
400: throws ContentManagerException {
401: final Pair<String, String> split_location = splitLocation(location);
402:
403: assert getContentInfo != null;
404: assert deleteContentInfo != null;
405: assert deleteContentAttributes != null;
406: assert deleteContentProperties != null;
407:
408: Boolean result = null;
409:
410: try {
411: result = inTransaction(new DbTransactionUser() {
412: public Boolean useTransaction()
413: throws InnerClassException {
414: return executeFetchAll(getContentInfo,
415: new DbRowProcessor() {
416: public boolean processRow(
417: ResultSet resultSet)
418: throws SQLException {
419: final int contentid = resultSet
420: .getInt("contentId");
421:
422: MimeType mimetype = MimeType
423: .getMimeType(resultSet
424: .getString("mimeType"));
425:
426: ContentStore store = mMimeMapping
427: .get(mimetype);
428: if (null == store) {
429: throw new UnsupportedMimeTypeException(
430: mimetype);
431: }
432:
433: if (!store
434: .deleteContentData(contentid)) {
435: rollback();
436: }
437:
438: executeUpdate(
439: deleteContentAttributes,
440: new DbPreparedStatementHandler() {
441: public void setParameters(
442: DbPreparedStatement statement) {
443: statement
444: .setInt(
445: "contentId",
446: contentid);
447: }
448: });
449:
450: executeUpdate(
451: deleteContentProperties,
452: new DbPreparedStatementHandler() {
453: public void setParameters(
454: DbPreparedStatement statement) {
455: statement
456: .setInt(
457: "contentId",
458: contentid);
459: }
460: });
461:
462: if (0 == executeUpdate(
463: deleteContentInfo,
464: new DbPreparedStatementHandler() {
465: public void setParameters(
466: DbPreparedStatement statement) {
467: statement
468: .setInt(
469: "contentId",
470: contentid);
471: }
472: })) {
473: rollback();
474: }
475:
476: return true;
477: }
478: }, new DbPreparedStatementHandler() {
479: public void setParameters(
480: DbPreparedStatement statement) {
481: statement
482: .setString(
483: "repository",
484: split_location
485: .getFirst())
486: .setString(
487: "path",
488: split_location
489: .getSecond());
490: }
491: });
492: }
493: });
494: } catch (InnerClassException e) {
495: throw (ContentManagerException) e.getCause();
496: }
497:
498: return result != null && result.booleanValue();
499: }
500:
501: private Pair<String, String> splitPath(String path) {
502: assert path != null;
503:
504: int slash_index = path.lastIndexOf('/');
505: String path_part = path.substring(0, slash_index);
506: String name_part = path.substring(slash_index + 1);
507:
508: return new Pair<String, String>(path_part, name_part);
509: }
510:
511: protected <ResultType> ResultType _useContentData(
512: Select retrieveContent, String location,
513: ContentDataUser user) throws ContentManagerException {
514: if (null == user)
515: throw new IllegalArgumentException("user can't be null");
516:
517: final Pair<String, String> split_location = splitLocation(location);
518:
519: assert retrieveContent != null;
520:
521: DatabaseContentInfo content_info = executeFetchFirstBean(
522: retrieveContent, DatabaseContentInfo.class,
523: new DbPreparedStatementHandler() {
524: public void setParameters(
525: DbPreparedStatement statement) {
526: Pair<String, String> path_parts = splitPath(split_location
527: .getSecond());
528: statement.setString("repository",
529: split_location.getFirst()).setString(
530: "path", split_location.getSecond())
531: .setString("pathpart",
532: path_parts.getFirst())
533: .setString("namepart",
534: path_parts.getSecond());
535: }
536: });
537:
538: if (null == content_info) {
539: return null;
540: }
541:
542: MimeType mime_type = MimeType.getMimeType(content_info
543: .getMimeType());
544: ContentStore store = mMimeMapping.get(mime_type);
545: if (null == store) {
546: throw new UnsupportedMimeTypeException(mime_type);
547: }
548:
549: return (ResultType) store.useContentData(content_info
550: .getContentId(), user);
551: }
552:
553: protected boolean _hasContentData(Select retrieveContent,
554: String location) throws ContentManagerException {
555: final Pair<String, String> split_location = splitLocation(location);
556:
557: assert retrieveContent != null;
558:
559: DatabaseContentInfo content_info = executeFetchFirstBean(
560: retrieveContent, DatabaseContentInfo.class,
561: new DbPreparedStatementHandler() {
562: public void setParameters(
563: DbPreparedStatement statement) {
564: Pair<String, String> path_parts = splitPath(split_location
565: .getSecond());
566: statement.setString("repository",
567: split_location.getFirst()).setString(
568: "path", split_location.getSecond())
569: .setString("pathpart",
570: path_parts.getFirst())
571: .setString("namepart",
572: path_parts.getSecond());
573: }
574: });
575:
576: if (null == content_info) {
577: return false;
578: }
579:
580: MimeType mime_type = MimeType.getMimeType(content_info
581: .getMimeType());
582: ContentStore store = mMimeMapping.get(mime_type);
583: if (null == store) {
584: throw new UnsupportedMimeTypeException(mime_type);
585: }
586:
587: return store.hasContentData(content_info.getContentId());
588: }
589:
590: protected DatabaseContentInfo _getContentInfo(
591: Select getContentInfo, Select getContentAttributes,
592: Select getContentProperties, String location)
593: throws ContentManagerException {
594: final Pair<String, String> split_location = splitLocation(location);
595:
596: assert getContentInfo != null;
597: assert getContentAttributes != null;
598: assert getContentProperties != null;
599:
600: final DatabaseContentInfo content_info = executeFetchFirstBean(
601: getContentInfo, DatabaseContentInfo.class,
602: new DbPreparedStatementHandler() {
603: public void setParameters(
604: DbPreparedStatement statement) {
605: Pair<String, String> path_parts = splitPath(split_location
606: .getSecond());
607: statement.setString("repository",
608: split_location.getFirst()).setString(
609: "path", split_location.getSecond())
610: .setString("pathpart",
611: path_parts.getFirst())
612: .setString("namepart",
613: path_parts.getSecond());
614: }
615: });
616:
617: if (content_info != null) {
618: // get the content attributes
619: ContentAttributesProcessor processor_attributes = new ContentAttributesProcessor();
620: executeFetchAll(getContentAttributes, processor_attributes,
621: new DbPreparedStatementHandler() {
622: public void setParameters(
623: DbPreparedStatement statement) {
624: statement.setInt("contentId", content_info
625: .getContentId());
626: }
627: });
628: content_info.setAttributes(processor_attributes
629: .getAttributes());
630:
631: // get the content data properties
632: ContentPropertiesProcessor processor_properties = new ContentPropertiesProcessor();
633: executeFetchAll(getContentProperties, processor_properties,
634: new DbPreparedStatementHandler() {
635: public void setParameters(
636: DbPreparedStatement statement) {
637: statement.setInt("contentId", content_info
638: .getContentId());
639: }
640: });
641: content_info.setProperties(processor_properties
642: .getProperties());
643:
644: // retrieve the content store
645: MimeType mime_type = MimeType.getMimeType(content_info
646: .getMimeType());
647: ContentStore store = mMimeMapping.get(mime_type);
648: if (null == store) {
649: throw new UnsupportedMimeTypeException(mime_type);
650: }
651:
652: // retrieve the content size
653: content_info.setSize(store.getSize(content_info
654: .getContentId()));
655: }
656:
657: return content_info;
658: }
659:
660: protected void _serveContentData(ElementSupport element,
661: final String location) throws ContentManagerException {
662: if (null == element)
663: throw new IllegalArgumentException("element can't be null.");
664:
665: try {
666: splitLocation(location);
667: } catch (IllegalArgumentException e) {
668: element.defer();
669: return;
670: }
671:
672: DatabaseContentInfo content_info = null;
673: try {
674: content_info = getContentInfo(location);
675: } catch (IllegalArgumentException e) {
676: element.defer();
677: return;
678: }
679: if (null == content_info) {
680: element.defer();
681: return;
682: }
683:
684: // retrieve the content store
685: MimeType mime_type = MimeType.getMimeType(content_info
686: .getMimeType());
687: ContentStore store = mMimeMapping.get(mime_type);
688: if (null == store) {
689: throw new UnsupportedMimeTypeException(mime_type);
690: }
691:
692: // set cache headers
693: long if_modified_since = element
694: .getDateHeader("If-Modified-Since");
695: Timestamp last_modified = content_info.getCreated();
696: long last_modified_timestamp = (last_modified.getTime() / 1000) * 1000;
697: if (if_modified_since > 0
698: && if_modified_since >= last_modified_timestamp) {
699: element.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
700: return;
701: }
702:
703: // set general headers
704: element.setContentType(store.getContentType(content_info));
705: if (content_info.hasName()) {
706: element.addHeader("Content-Disposition",
707: "inline; filename=" + content_info.getName());
708: }
709: element.addHeader("Cache-Control", "must-revalidate");
710: element.addDateHeader("Expires",
711: System.currentTimeMillis() + 60 * 60 * 1000);
712: element.addDateHeader("Last-Modified", last_modified_timestamp);
713:
714: store.serveContentData(element, content_info.getContentId());
715: }
716:
717: protected String _getContentForHtml(String location,
718: ElementSupport element, String serveContentExitName)
719: throws ContentManagerException {
720: DatabaseContentInfo content_info = null;
721: try {
722: content_info = getContentInfo(location);
723: } catch (IllegalArgumentException e) {
724: return "";
725: }
726: if (null == content_info) {
727: return "";
728: }
729:
730: // retrieve the content store
731: MimeType mime_type = MimeType.getMimeType(content_info
732: .getMimeType());
733: ContentStore store = mMimeMapping.get(mime_type);
734: if (null == store) {
735: throw new UnsupportedMimeTypeException(mime_type);
736: }
737:
738: return store.getContentForHtml(content_info.getContentId(),
739: content_info, element, serveContentExitName);
740: }
741:
742: private class ContentAttributesProcessor extends DbRowProcessor {
743: private Map<String, String> mAttributes = null;
744:
745: public boolean processRow(ResultSet result) throws SQLException {
746: if (null == mAttributes) {
747: mAttributes = new HashMap<String, String>();
748: }
749:
750: mAttributes.put(result.getString("name"), result
751: .getString(getValueColumnName()));
752: return true;
753: }
754:
755: public Map<String, String> getAttributes() {
756: return mAttributes;
757: }
758: }
759:
760: private class ContentPropertiesProcessor extends DbRowProcessor {
761: private Map<String, String> mProperties = null;
762:
763: public boolean processRow(ResultSet result) throws SQLException {
764: if (null == mProperties) {
765: mProperties = new HashMap<String, String>();
766: }
767:
768: mProperties.put(result.getString("name"), result
769: .getString(getValueColumnName()));
770: return true;
771: }
772:
773: public Map<String, String> getProperties() {
774: return mProperties;
775: }
776: }
777: }
|