0001: /*
0002: * Copyright 2004 Outerthought bvba and Schaubroeck nv
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.outerj.daisy.repository.serverimpl;
0017:
0018: import java.io.InputStream;
0019: import java.math.BigDecimal;
0020: import java.sql.Connection;
0021: import java.sql.PreparedStatement;
0022: import java.sql.ResultSet;
0023: import java.sql.SQLException;
0024: import java.sql.Timestamp;
0025: import java.sql.Types;
0026: import java.util.*;
0027: import java.util.concurrent.TimeUnit;
0028: import java.util.concurrent.locks.Lock;
0029:
0030: import org.apache.xmlbeans.XmlObject;
0031: import org.outerj.daisy.blobstore.BlobIOException;
0032: import org.outerj.daisy.blobstore.NonExistingBlobException;
0033: import org.outerj.daisy.jdbcutil.JdbcHelper;
0034: import org.outerj.daisy.plugin.PluginHandle;
0035: import org.outerj.daisy.repository.AccessException;
0036: import org.outerj.daisy.repository.AvailableVariant;
0037: import org.outerj.daisy.repository.ChangeType;
0038: import org.outerj.daisy.repository.CollectionDeletedException;
0039: import org.outerj.daisy.repository.CollectionNotFoundException;
0040: import org.outerj.daisy.repository.ConcurrentUpdateException;
0041: import org.outerj.daisy.repository.Document;
0042: import org.outerj.daisy.repository.DocumentCollection;
0043: import org.outerj.daisy.repository.DocumentLockedException;
0044: import org.outerj.daisy.repository.DocumentNotFoundException;
0045: import org.outerj.daisy.repository.DocumentVariantNotFoundException;
0046: import org.outerj.daisy.repository.Field;
0047: import org.outerj.daisy.repository.HierarchyPath;
0048: import org.outerj.daisy.repository.Link;
0049: import org.outerj.daisy.repository.LockInfo;
0050: import org.outerj.daisy.repository.LockType;
0051: import org.outerj.daisy.repository.PartDataSource;
0052: import org.outerj.daisy.repository.Repository;
0053: import org.outerj.daisy.repository.RepositoryEventType;
0054: import org.outerj.daisy.repository.RepositoryException;
0055: import org.outerj.daisy.repository.ValueType;
0056: import org.outerj.daisy.repository.VariantKey;
0057: import org.outerj.daisy.repository.VersionKey;
0058: import org.outerj.daisy.repository.VersionState;
0059: import org.outerj.daisy.repository.acl.AclPermission;
0060: import org.outerj.daisy.repository.acl.AclResultInfo;
0061: import org.outerj.daisy.repository.commonimpl.AuthenticatedUser;
0062: import org.outerj.daisy.repository.commonimpl.AvailableVariantImpl;
0063: import org.outerj.daisy.repository.commonimpl.CommonCollectionManager;
0064: import org.outerj.daisy.repository.commonimpl.DocId;
0065: import org.outerj.daisy.repository.commonimpl.DocumentCollectionImpl;
0066: import org.outerj.daisy.repository.commonimpl.DocumentImpl;
0067: import org.outerj.daisy.repository.commonimpl.DocumentStrategy;
0068: import org.outerj.daisy.repository.commonimpl.DocumentVariantImpl;
0069: import org.outerj.daisy.repository.commonimpl.FieldImpl;
0070: import org.outerj.daisy.repository.commonimpl.LinkImpl;
0071: import org.outerj.daisy.repository.commonimpl.LockInfoImpl;
0072: import org.outerj.daisy.repository.commonimpl.PartImpl;
0073: import org.outerj.daisy.repository.commonimpl.RepositoryImpl;
0074: import org.outerj.daisy.repository.commonimpl.VersionImpl;
0075: import org.outerj.daisy.repository.commonimpl.schema.RepositorySchemaImpl;
0076: import org.outerj.daisy.repository.schema.FieldType;
0077: import org.outerj.daisy.repository.serverimpl.linkextraction.LinkExtractorHelper;
0078: import org.outerj.daisy.repository.serverimpl.linkextraction.LinkInfo;
0079: import org.outerj.daisy.repository.serverimpl.model.LocalSchemaStrategy;
0080: import org.outerj.daisy.repository.spi.local.PreSaveHook;
0081: import org.outerx.daisy.x10.DocumentCreatedDocument;
0082: import org.outerx.daisy.x10.DocumentDeletedDocument;
0083: import org.outerx.daisy.x10.DocumentDocument;
0084: import org.outerx.daisy.x10.DocumentUpdatedDocument;
0085: import org.outerx.daisy.x10.DocumentVariantCreatedDocument;
0086: import org.outerx.daisy.x10.DocumentVariantDeletedDocument;
0087: import org.outerx.daisy.x10.DocumentVariantUpdatedDocument;
0088: import org.outerx.daisy.x10.VersionUpdatedDocument;
0089: import org.outerx.daisy.x10.VersionDocument.Version;
0090:
0091: // Implementation notes:
0092: // - Updates on a document are synchronized by selecting the document record from the
0093: // documents table with a lock clause (e.g. select ... for update)
0094: public class LocalDocumentStrategy extends AbstractLocalStrategy
0095: implements DocumentStrategy {
0096:
0097: public LocalDocumentStrategy(
0098: LocalRepositoryManager.Context context,
0099: AuthenticatedUser systemUser, JdbcHelper jdbcHelper) {
0100: super (context, systemUser, jdbcHelper);
0101: }
0102:
0103: public Document load(DocId docId, long branchId, long languageId,
0104: AuthenticatedUser user) throws RepositoryException {
0105: // check here that branchId and languageId are not -1, since the loadDocumentInTransaction actually
0106: // allows them to be -1 to avoid the variant to be loaded, but we don't want to make this functionality public
0107: if (branchId == -1 || languageId == -1)
0108: throw new RepositoryException(
0109: "branchId and languageId parameters should not be -1");
0110:
0111: List<Runnable> executeAfterCommit = new ArrayList<Runnable>(2);
0112: Connection conn = null;
0113: try {
0114: conn = context.getDataSource().getConnection();
0115: jdbcHelper.startTransaction(conn);
0116:
0117: DocumentImpl document = loadDocumentInTransaction(user,
0118: docId, branchId, languageId, conn,
0119: executeAfterCommit);
0120:
0121: conn.commit();
0122: executeRunnables(executeAfterCommit);
0123:
0124: AclResultInfo aclInfo = context.getCommonRepository()
0125: .getAccessManager().getAclInfoOnLive(systemUser,
0126: user.getId(), user.getActiveRoleIds(),
0127: document);
0128:
0129: return DocumentAccessUtil.protectDocument(aclInfo,
0130: document, docId, user, context
0131: .getCommonRepository(), this );
0132: } catch (Throwable e) {
0133: jdbcHelper.rollback(conn);
0134: if (e instanceof DocumentNotFoundException
0135: || e instanceof AccessException
0136: || e instanceof DocumentVariantNotFoundException)
0137: throw (RepositoryException) e;
0138: else
0139: throw new RepositoryException("Error loading document "
0140: + docId.toString() + ".", e);
0141: } finally {
0142: jdbcHelper.closeConnection(conn);
0143: }
0144: }
0145:
0146: /**
0147: * Loads a document inside an already started connection / transaction.
0148: * It also DOES NOT check access rights.
0149: *
0150: * If either the branchId or languageId is -1, a document object without loaded variant will be returned.
0151: * This is for internal use only.
0152: */
0153: private DocumentImpl loadDocumentInTransaction(
0154: AuthenticatedUser user, DocId docId, long branchId,
0155: long languageId, Connection conn,
0156: List<Runnable> executeAfterCommit)
0157: throws RepositoryException {
0158: PreparedStatement stmt = null;
0159: try {
0160: stmt = conn
0161: .prepareStatement("select created, last_modified, last_modifier, owner, private, reference_lang_id, updatecount from documents where id = ? and ns_id = ?");
0162: stmt.setLong(1, docId.getSeqId());
0163: stmt.setLong(2, docId.getNsId());
0164: ResultSet rs = stmt.executeQuery();
0165: if (!rs.next())
0166: throw new DocumentNotFoundException(docId.toString());
0167:
0168: DocumentImpl document = new DocumentImpl(this , context
0169: .getCommonRepository(), user, -1, branchId,
0170: languageId);
0171: DocumentImpl.IntimateAccess documentInt = document
0172: .getIntimateAccess(this );
0173: long referenceLanguageId = jdbcHelper.getNullableIdField(
0174: rs, "reference_lang_id");
0175: documentInt.load(docId, rs.getTimestamp("last_modified"),
0176: rs.getLong("last_modifier"), rs
0177: .getTimestamp("created"), rs
0178: .getLong("owner"),
0179: rs.getBoolean("private"),
0180: rs.getLong("updatecount"), referenceLanguageId);
0181: rs.close();
0182: stmt.close();
0183:
0184: if (branchId != -1 && languageId != -1)
0185: loadVariant(document, documentInt, conn,
0186: executeAfterCommit);
0187:
0188: return document;
0189: } catch (DocumentNotFoundException e) {
0190: throw e;
0191: } catch (DocumentVariantNotFoundException e) {
0192: throw e;
0193: } catch (Throwable e) {
0194: throw new RepositoryException("Error loading document.", e);
0195: } finally {
0196: jdbcHelper.closeStatement(stmt);
0197: }
0198: }
0199:
0200: private void loadVariant(DocumentImpl document,
0201: DocumentImpl.IntimateAccess documentInt, Connection conn,
0202: List<Runnable> executeAfterCommit)
0203: throws RepositoryException, SQLException {
0204: DocumentVariantImpl variant = documentInt.getVariant();
0205: DocumentVariantImpl.IntimateAccess variantInt = variant
0206: .getIntimateAccess(this );
0207: PreparedStatement stmt = null;
0208: try {
0209: stmt = conn
0210: .prepareStatement("select doctype_id, retired, lastversion_id, liveversion_id, last_modified, last_modifier, updatecount,"
0211: + " created_from_branch_id, created_from_lang_id, created_from_version_id, last_major_change_version_id, live_major_change_version_id from document_variants where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
0212: DocId docId = documentInt.getDocId();
0213: stmt.setLong(1, docId.getSeqId());
0214: stmt.setLong(2, docId.getNsId());
0215: stmt.setLong(3, variant.getBranchId());
0216: stmt.setLong(4, variant.getLanguageId());
0217: ResultSet rs = stmt.executeQuery();
0218:
0219: if (!rs.next())
0220: throw new DocumentVariantNotFoundException(document
0221: .getId(), getBranchName(variant.getBranchId()),
0222: variant.getBranchId(), getLanguageName(variant
0223: .getLanguageId()), variant
0224: .getLanguageId());
0225:
0226: variantInt.load(rs.getLong("doctype_id"), rs
0227: .getBoolean("retired"), rs
0228: .getLong("lastversion_id"), rs
0229: .getLong("liveversion_id"), rs
0230: .getTimestamp("last_modified"), rs
0231: .getLong("last_modifier"), rs
0232: .getLong("created_from_branch_id"), rs
0233: .getLong("created_from_lang_id"), rs
0234: .getLong("created_from_version_id"), jdbcHelper
0235: .getNullableIdField(rs,
0236: "last_major_change_version_id"), jdbcHelper
0237: .getNullableIdField(rs,
0238: "live_major_change_version_id"), rs
0239: .getLong("updatecount"));
0240:
0241: loadName(variant, variantInt, docId, conn);
0242: loadCustomFields(variant, variantInt, docId, conn);
0243: loadParts(variant, variantInt, docId, conn);
0244: loadFields(variant, variantInt, docId, conn);
0245: loadLinks(variant, variantInt, docId, conn);
0246: loadCollections(variant, variantInt, docId, conn);
0247: loadSummary(variant, variantInt, docId, conn);
0248:
0249: LockInfoImpl lockInfo = loadLock(docId, document
0250: .getBranchId(), document.getLanguageId(), conn,
0251: executeAfterCommit);
0252: variantInt.setLockInfo(lockInfo);
0253: } finally {
0254: jdbcHelper.closeStatement(stmt);
0255: }
0256: }
0257:
0258: private void loadCollections(DocumentVariantImpl variant,
0259: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
0260: Connection conn) throws RepositoryException {
0261: Collection<DocumentCollectionImpl> collections = loadCollections(
0262: variant, docId, conn);
0263: if (collections != null) {
0264: for (DocumentCollectionImpl collection : collections) {
0265: variantInt.addCollection(collection);
0266: }
0267: }
0268: }
0269:
0270: private void loadName(DocumentVariantImpl variant,
0271: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
0272: Connection conn) throws SQLException, RepositoryException {
0273: PreparedStatement stmt = null;
0274: try {
0275: stmt = conn
0276: .prepareStatement("select name from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and id = ?");
0277: stmt.setLong(1, docId.getSeqId());
0278: stmt.setLong(2, docId.getNsId());
0279: stmt.setLong(3, variant.getBranchId());
0280: stmt.setLong(4, variant.getLanguageId());
0281: stmt.setLong(5, variantInt.getLastVersionId());
0282: ResultSet rs = stmt.executeQuery();
0283: if (!rs.next())
0284: throw new RepositoryException(
0285: "Strange: no version record found for "
0286: + getFormattedVariant(variant.getKey(),
0287: variantInt.getLastVersionId()));
0288: variantInt.setName(rs.getString(1));
0289: } finally {
0290: jdbcHelper.closeStatement(stmt);
0291: }
0292: }
0293:
0294: private void loadCustomFields(DocumentVariantImpl variant,
0295: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
0296: Connection conn) throws SQLException {
0297: PreparedStatement stmt = null;
0298: try {
0299: stmt = conn
0300: .prepareStatement("select name, value from customfields where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
0301: stmt.setLong(1, docId.getSeqId());
0302: stmt.setLong(2, docId.getNsId());
0303: stmt.setLong(3, variant.getBranchId());
0304: stmt.setLong(4, variant.getLanguageId());
0305: ResultSet rs = stmt.executeQuery();
0306: while (rs.next()) {
0307: variantInt.setCustomField(rs.getString(1), rs
0308: .getString(2));
0309: }
0310: } finally {
0311: jdbcHelper.closeStatement(stmt);
0312: }
0313: }
0314:
0315: private void loadParts(DocumentVariantImpl variant,
0316: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
0317: Connection conn) throws SQLException {
0318: Collection<PartImpl> parts = loadParts(variant, variantInt,
0319: docId, variantInt.getLastVersionId(), conn);
0320: for (PartImpl part : parts) {
0321: variantInt.addPart(part);
0322: }
0323: }
0324:
0325: private Collection<PartImpl> loadParts(DocumentVariantImpl variant,
0326: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
0327: long versionId, Connection conn) throws SQLException {
0328: PreparedStatement stmt = null;
0329: try {
0330: stmt = conn
0331: .prepareStatement("select blob_id, mimetype, filename, parttype_id, blob_size, changed_in_version from parts where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and version_id = ?");
0332: stmt.setLong(1, docId.getSeqId());
0333: stmt.setLong(2, docId.getNsId());
0334: stmt.setLong(3, variant.getBranchId());
0335: stmt.setLong(4, variant.getLanguageId());
0336: stmt.setLong(5, versionId);
0337: ResultSet rs = stmt.executeQuery();
0338:
0339: List<PartImpl> parts = new ArrayList<PartImpl>(5);
0340: while (rs.next()) {
0341: PartImpl part = new PartImpl(variantInt, rs
0342: .getLong("parttype_id"), versionId, rs
0343: .getLong("changed_in_version"));
0344: PartImpl.IntimateAccess partInt = part
0345: .getIntimateAccess(this );
0346: partInt.setBlobKey(rs.getString("blob_id"));
0347: partInt.setMimeType(rs.getString("mimetype"));
0348: partInt.setFileName(rs.getString("filename"));
0349: partInt.setSize(rs.getLong("blob_size"));
0350: parts.add(part);
0351: }
0352: return parts;
0353: } finally {
0354: jdbcHelper.closeStatement(stmt);
0355: }
0356: }
0357:
0358: private void loadFields(DocumentVariantImpl variant,
0359: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
0360: Connection conn) throws SQLException {
0361: Collection<FieldImpl> fields = loadFields(variant, docId,
0362: variantInt.getLastVersionId(), conn);
0363: for (FieldImpl field : fields) {
0364: variantInt.addField(field);
0365: }
0366: }
0367:
0368: private Collection<FieldImpl> loadFields(
0369: DocumentVariantImpl variant, DocId docId, long versionId,
0370: Connection conn) throws SQLException {
0371: PreparedStatement stmt = null;
0372: try {
0373: stmt = conn
0374: .prepareStatement("select fieldtype_id, stringvalue, datevalue, datetimevalue, integervalue, floatvalue, decimalvalue, booleanvalue, link_docid, ns.name_ as link_ns, link_branchid, link_langid, hier_seq from thefields left join daisy_namespaces ns on (thefields.link_nsid = ns.id) where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and version_id = ? order by fieldtype_id, value_seq, hier_seq");
0375: stmt.setLong(1, docId.getSeqId());
0376: stmt.setLong(2, docId.getNsId());
0377: stmt.setLong(3, variant.getBranchId());
0378: stmt.setLong(4, variant.getLanguageId());
0379: stmt.setLong(5, versionId);
0380: ResultSet rs = stmt.executeQuery();
0381:
0382: List<FieldImpl> fields = new ArrayList<FieldImpl>(5);
0383: List<Object> multiValueValues = new ArrayList<Object>(4);
0384: List<Object> hierPathValues = new ArrayList<Object>(4);
0385: FieldType previousFieldType = null;
0386: while (rs.next()) {
0387: long fieldTypeId = rs.getLong("fieldtype_id");
0388: FieldType fieldType;
0389: if (previousFieldType == null
0390: || previousFieldType.getId() != fieldTypeId) {
0391: try {
0392: fieldType = context.getRepositorySchema()
0393: .getFieldTypeById(fieldTypeId, false,
0394: systemUser);
0395: } catch (RepositoryException e) {
0396: throw new RuntimeException(
0397: DocumentImpl.ERROR_ACCESSING_REPOSITORY_SCHEMA,
0398: e);
0399: }
0400: } else {
0401: fieldType = previousFieldType;
0402: }
0403:
0404: if (previousFieldType != null
0405: && previousFieldType.getId() != fieldTypeId) {
0406: finishMultiValueAndHierarchicalFields(
0407: previousFieldType, hierPathValues,
0408: multiValueValues, fields, variant,
0409: versionId);
0410: }
0411:
0412: ValueType valueType = fieldType.getValueType();
0413: LocalSchemaStrategy.FieldValueGetter valueGetter = LocalSchemaStrategy
0414: .getValueGetter(valueType);
0415: Object value = valueGetter.getValue(rs);
0416: if (fieldType.isHierarchical() /* and either multi value or not */) {
0417: if (hierPathValues.size() > 0
0418: && rs.getLong("hier_seq") == 1) {
0419: // test the impossible
0420: if (!fieldType.isMultiValue())
0421: throw new RuntimeException(
0422: "Assertion failure: got restarted hierarchical path sequence for a field which is not multivalue ("
0423: + getFormattedVariant(
0424: variant.getKey(),
0425: versionId)
0426: + ", field type "
0427: + fieldType.getName() + ")");
0428: HierarchyPath hierarchyPath = new HierarchyPath(
0429: hierPathValues.toArray());
0430: multiValueValues.add(hierarchyPath);
0431: hierPathValues.clear();
0432: }
0433: hierPathValues.add(value);
0434: } else if (fieldType.isMultiValue()) {
0435: multiValueValues.add(value);
0436: } else {
0437: FieldImpl field = new FieldImpl(variant
0438: .getIntimateAccess(this ),
0439: fieldType.getId(), value);
0440: fields.add(field);
0441: }
0442:
0443: previousFieldType = fieldType;
0444: }
0445:
0446: if (previousFieldType != null)
0447: finishMultiValueAndHierarchicalFields(
0448: previousFieldType, hierPathValues,
0449: multiValueValues, fields, variant, versionId);
0450:
0451: return fields;
0452: } finally {
0453: jdbcHelper.closeStatement(stmt);
0454: }
0455: }
0456:
0457: private void finishMultiValueAndHierarchicalFields(
0458: FieldType fieldType, List<Object> hierPathValues,
0459: List<Object> multiValueValues, List<FieldImpl> fields,
0460: DocumentVariantImpl variant, long versionId) {
0461: if (hierPathValues.size() > 0) {
0462: HierarchyPath hierarchyPath = new HierarchyPath(
0463: hierPathValues.toArray());
0464: if (!fieldType.isMultiValue()) {
0465: FieldImpl field = new FieldImpl(variant
0466: .getIntimateAccess(this ), fieldType.getId(),
0467: hierarchyPath);
0468: fields.add(field);
0469: } else {
0470: multiValueValues.add(hierarchyPath);
0471: }
0472: hierPathValues.clear();
0473: }
0474: if (multiValueValues.size() > 0) {
0475: if (!fieldType.isMultiValue())
0476: throw new RuntimeException(
0477: "Assertion failure: retrieved multiple values for a field which is not multivalue ("
0478: + getFormattedVariant(variant.getKey(),
0479: versionId)
0480: + ", field type "
0481: + fieldType.getName() + ")");
0482: Object[] values = multiValueValues.toArray();
0483: FieldImpl field = new FieldImpl(variant
0484: .getIntimateAccess(this ), fieldType.getId(), values);
0485: fields.add(field);
0486: multiValueValues.clear();
0487: }
0488:
0489: }
0490:
0491: private void loadLinks(DocumentVariantImpl variant,
0492: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
0493: Connection conn) throws SQLException {
0494: Collection<LinkImpl> links = loadLinks(variant, docId,
0495: variantInt.getLastVersionId(), conn);
0496: for (LinkImpl link : links) {
0497: variantInt.addLink(link);
0498: }
0499: }
0500:
0501: private Collection<LinkImpl> loadLinks(DocumentVariantImpl variant,
0502: DocId docId, long versionId, Connection conn)
0503: throws SQLException {
0504: PreparedStatement stmt = null;
0505: try {
0506: stmt = conn
0507: .prepareStatement("select title, target from links where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and version_id = ? order by id");
0508: stmt.setLong(1, docId.getSeqId());
0509: stmt.setLong(2, docId.getNsId());
0510: stmt.setLong(3, variant.getBranchId());
0511: stmt.setLong(4, variant.getLanguageId());
0512: stmt.setLong(5, versionId);
0513: ResultSet rs = stmt.executeQuery();
0514:
0515: List<LinkImpl> links = new ArrayList<LinkImpl>(5);
0516: while (rs.next()) {
0517: LinkImpl link = new LinkImpl(rs.getString(1), rs
0518: .getString(2));
0519: links.add(link);
0520: }
0521: rs.close();
0522:
0523: return links;
0524: } finally {
0525: jdbcHelper.closeStatement(stmt);
0526: }
0527: }
0528:
0529: private void loadSummary(DocumentVariantImpl variant,
0530: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
0531: Connection conn) throws SQLException {
0532: PreparedStatement stmt = null;
0533: try {
0534: stmt = conn
0535: .prepareStatement("select summary from summaries where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
0536: stmt.setLong(1, docId.getSeqId());
0537: stmt.setLong(2, docId.getNsId());
0538: stmt.setLong(3, variant.getBranchId());
0539: stmt.setLong(4, variant.getLanguageId());
0540: ResultSet rs = stmt.executeQuery();
0541: String summary = null;
0542: if (rs.next()) {
0543: summary = rs.getString(1);
0544: }
0545: variantInt.setSummary(summary);
0546: } finally {
0547: jdbcHelper.closeStatement(stmt);
0548: }
0549: }
0550:
0551: public void store(DocumentImpl document) throws RepositoryException {
0552: if (!logger.isDebugEnabled()) {
0553: logger.debug("storing " + document.getName() + "("
0554: + document.getId() + "@" + document.getBranchId()
0555: + ":" + document.getLanguageId() + ":"
0556: + document.getLastVersionId() + ")");
0557: }
0558: DocumentImpl.IntimateAccess documentInt = document
0559: .getIntimateAccess(this );
0560: DocumentVariantImpl variant = documentInt.getVariant();
0561: DocumentVariantImpl.IntimateAccess variantInt = variant
0562: .getIntimateAccess(this );
0563:
0564: // Check if the user has write access to the document variant
0565: if (!variant.isNew()) {
0566: // Note that here we retrieve the ACL info using the document ID,
0567: // thus giving the ACL info before document modifications. This is necessary
0568: // because otherwise someone could modify field values to get write access
0569: // to the document, even if before they didn't have such access.
0570: AclResultInfo aclInfo = context.getCommonRepository()
0571: .getAccessManager().getAclInfoOnLive(
0572: systemUser,
0573: documentInt.getCurrentUser().getId(),
0574: documentInt.getCurrentUser()
0575: .getActiveRoleIds(),
0576: documentInt.getDocId(),
0577: document.getBranchId(),
0578: document.getLanguageId());
0579: if (!aclInfo.isAllowed(AclPermission.WRITE))
0580: throw new AccessException(
0581: "Write access denied for document "
0582: + document.getId()
0583: + ", branch "
0584: + getBranchLabel(document.getBranchId())
0585: + ", language "
0586: + getLanguageLabel(document
0587: .getLanguageId())
0588: + " for user "
0589: + documentInt.getCurrentUser()
0590: .getLogin() + " (ID: "
0591: + documentInt.getCurrentUser().getId()
0592: + ").");
0593: }
0594:
0595: // Check that the author can't save or create a document to which he himself has no access
0596: AclResultInfo newAclInfo = context
0597: .getCommonRepository()
0598: .getAccessManager()
0599: .getAclInfoOnLive(
0600: systemUser,
0601: documentInt.getCurrentUser().getId(),
0602: documentInt.getCurrentUser().getActiveRoleIds(),
0603: document);
0604:
0605: if (!newAclInfo.isFullyAllowed(AclPermission.READ))
0606: throw new RepositoryException(
0607: "The document cannot be saved because the user would not have read access to it anymore.");
0608:
0609: if (!newAclInfo.isAllowed(AclPermission.WRITE))
0610: throw new RepositoryException(
0611: "The document cannot be saved because the user would not have write access to it anymore.");
0612:
0613: // Execute pre-save hooks
0614: List<PluginHandle<PreSaveHook>> preSaveHooks = context
0615: .getPreSaveHooks();
0616: if (preSaveHooks.size() > 0) {
0617: for (PluginHandle<PreSaveHook> preSaveHook : preSaveHooks) {
0618: Repository userRepository = new RepositoryImpl(context
0619: .getCommonRepository(), documentInt
0620: .getCurrentUser());
0621: try {
0622: preSaveHook.getPlugin().process(document,
0623: userRepository);
0624: } catch (Throwable e) {
0625: // Note: can't use document.getVariantKey() here in log statement since the document might not have an ID yet.
0626: logger.error(
0627: "Error executing pre-save-hook "
0628: + preSaveHook.getName()
0629: + " on document "
0630: + document.getId()
0631: + ", branch "
0632: + getBranchLabel(document
0633: .getBranchId())
0634: + ", language "
0635: + getLanguageLabel(document
0636: .getLanguageId()), e);
0637: }
0638: }
0639: }
0640:
0641: // Check new version state
0642: boolean canPublish = newAclInfo
0643: .isAllowed(AclPermission.PUBLISH);
0644: if (document.getNewVersionState() == VersionState.PUBLISH
0645: && !canPublish)
0646: document.setNewVersionState(VersionState.DRAFT);
0647:
0648: String id = document.getId();
0649: boolean isDocumentNew = document.isNew();
0650: DocId docId = documentInt.getDocId(); // will be null for new docs
0651: boolean isVariantNew = variant.isNew();
0652: List<PartUpdate> partUpdates = null;
0653:
0654: // start database stuff
0655: List<Runnable> executeAfterCommit = new ArrayList<Runnable>(2);
0656: Connection conn = null;
0657: PreparedStatement stmt = null;
0658: try {
0659: conn = context.getDataSource().getConnection();
0660: jdbcHelper.startTransaction(conn);
0661:
0662: if (document.getReferenceLanguageId() != -1) {
0663: try {
0664: context.getCommonRepository().getVariantManager()
0665: .getLanguage(
0666: document.getReferenceLanguageId(),
0667: false, systemUser);
0668: } catch (RepositoryException r) {
0669: throw new RepositoryException(
0670: "The specified referenceLanguage does not exist (no language with id "
0671: + document.getReferenceLanguageId()
0672: + ")");
0673: }
0674: }
0675:
0676: // load the old document (if any), needed to generate event later on
0677: // this is our last chance to do this
0678: DocumentImpl oldDocument = null;
0679: if (!isDocumentNew && !variant.isNew()) {
0680: oldDocument = loadDocumentInTransaction(documentInt
0681: .getCurrentUser(), docId,
0682: variant.getBranchId(), variant.getLanguageId(),
0683: conn, executeAfterCommit);
0684: } else if (!isDocumentNew && variant.isNew()) {
0685: oldDocument = loadDocumentInTransaction(documentInt
0686: .getCurrentUser(), docId, -1, -1, conn,
0687: executeAfterCommit);
0688: }
0689:
0690: Date now = new Date();
0691:
0692: //
0693: // PART 1: Store the document itself
0694: //
0695:
0696: // Note: documentNewUpdateCount should always contain the correct update count of the document, even if
0697: // the document itself is not modified, because we need this further on
0698: long documentNewUpdateCount = document.getUpdateCount();
0699:
0700: if (isDocumentNew) {
0701: if (documentInt.getRequestedDocId() != null) {
0702: docId = documentInt.getRequestedDocId();
0703: // The below is also checked by the database by a primary key constraint, but
0704: // we check it here on beforehand in order to throw a nicer error message.
0705: stmt = conn
0706: .prepareStatement("select 1 from documents where id = ? and ns_id = ?");
0707: stmt.setLong(1, docId.getSeqId());
0708: stmt.setLong(2, docId.getNsId());
0709: ResultSet rs = stmt.executeQuery();
0710: if (rs.next()) {
0711: throw new RepositoryException(
0712: "It was requested to create a document with ID "
0713: + docId.toString()
0714: + ", however the repository already contains a document with this ID.");
0715: }
0716: stmt.close();
0717: } else {
0718: long newDocSeqId = context.getNextDocumentId();
0719: String newDocNamespace = context
0720: .getRepositoryNamespace();
0721: docId = DocId.getDocId(newDocSeqId,
0722: newDocNamespace, context
0723: .getCommonRepository());
0724: }
0725: id = docId.toString();
0726: documentNewUpdateCount = 1;
0727:
0728: stmt = conn
0729: .prepareStatement("insert into documents(id, ns_id, id_search, created, owner, reference_lang_id, private, last_modified, last_modifier, updatecount) values(?,?,?,?,?,?,?,?,?,?)");
0730: stmt.setLong(1, docId.getSeqId());
0731: stmt.setLong(2, docId.getNsId());
0732: stmt.setString(3, docId.getSeqId() + "-"
0733: + docId.getNsId());
0734: stmt.setTimestamp(4, new Timestamp(now.getTime()));
0735: stmt.setLong(5, document.getOwner());
0736: jdbcHelper.setNullableIdField(stmt, 6, document
0737: .getReferenceLanguageId());
0738: stmt.setBoolean(7, document.isPrivate());
0739: stmt.setTimestamp(8, new Timestamp(now.getTime()));
0740: stmt.setLong(9, documentInt.getCurrentUser().getId()); // same as owner of course
0741: stmt.setLong(10, documentNewUpdateCount);
0742:
0743: stmt.execute();
0744: stmt.close();
0745:
0746: // create event record
0747: XmlObject eventDescription = createNewDocumentEvent(
0748: document, docId, now, 1L);
0749: eventHelper.createEvent(eventDescription,
0750: "DocumentCreated", conn);
0751: } else if (document.needsSaving()) {
0752: // check the document is really there and not modified by someone else
0753: // use lock clause to avoid others updating the document while we're at it.
0754: stmt = conn
0755: .prepareStatement("select updatecount from documents where id = ? and ns_id = ? "
0756: + jdbcHelper.getSharedLockClause());
0757: stmt.setLong(1, docId.getSeqId());
0758: stmt.setLong(2, docId.getNsId());
0759: ResultSet rs = stmt.executeQuery();
0760: if (!rs.next()) {
0761: throw new RepositoryException(
0762: "The document with ID "
0763: + docId
0764: + " does not exist in the repository.");
0765: } else {
0766: // Note that even if a user has an exclusive editing lock, this lock doesn't apply to the
0767: // document record and thus it is possible that saving will fail. However, in most frontend
0768: // applications the editing of a variant should be separate from the editing of shared document
0769: // properties, so that a user will not run into the aforementioned situation.
0770: long dbUpdateCount = rs.getLong(1);
0771: if (dbUpdateCount != document.getUpdateCount())
0772: throw new ConcurrentUpdateException(
0773: Document.class.getName(), document
0774: .getId());
0775: }
0776: rs.close();
0777: stmt.close();
0778:
0779: documentNewUpdateCount = document.getUpdateCount() + 1;
0780:
0781: // update document record
0782: stmt = conn
0783: .prepareStatement("update documents set last_modified = ?, last_modifier = ?, private = ?, owner = ?, reference_lang_id = ?, updatecount = ? where id = ? and ns_id = ?");
0784: stmt.setTimestamp(1, new Timestamp(now.getTime()));
0785: stmt.setLong(2, documentInt.getCurrentUser().getId());
0786: stmt.setBoolean(3, document.isPrivate());
0787: stmt.setLong(4, document.getOwner());
0788: if (document.getReferenceLanguageId() == -1L) {
0789: stmt.setNull(5, Types.BIGINT);
0790: } else {
0791: stmt.setLong(5, document.getReferenceLanguageId());
0792: }
0793: stmt.setLong(6, documentNewUpdateCount);
0794: stmt.setLong(7, docId.getSeqId());
0795: stmt.setLong(8, docId.getNsId());
0796: stmt.executeUpdate();
0797: stmt.close();
0798:
0799: // create event record
0800: XmlObject eventDescription = createDocumentUpdatedEvent(
0801: oldDocument, document, now,
0802: documentNewUpdateCount);
0803: eventHelper.createEvent(eventDescription,
0804: "DocumentUpdated", conn);
0805:
0806: // free document record for others to update (so that they don't have to wait until the variant is
0807: // stored too.
0808: conn.commit();
0809: executeRunnables(executeAfterCommit);
0810:
0811: documentInt.saved(docId, now, document.getCreated(),
0812: documentNewUpdateCount);
0813:
0814: context.getCommonRepository().fireRepositoryEvent(
0815: RepositoryEventType.DOCUMENT_UPDATED, id,
0816: document.getUpdateCount());
0817: }
0818:
0819: //
0820: // PART 2: Store the document variant
0821: //
0822:
0823: checkValidSyncedWith(conn, docId, document.getBranchId(),
0824: document.getNewSyncedWith());
0825:
0826: boolean newVersion = false;
0827: if (variant.isNew()) {
0828: stmt = conn
0829: .prepareStatement("insert into document_variants(doc_id, ns_id, branch_id, lang_id, link_search, variant_search, doctype_id, retired, lastversion_id, liveversion_id, last_modified, last_modifier, updatecount, created_from_branch_id, created_from_lang_id, created_from_version_id) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
0830: stmt.setLong(1, docId.getSeqId());
0831: stmt.setLong(2, docId.getNamespaceId());
0832: stmt.setLong(3, document.getBranchId());
0833: stmt.setLong(4, document.getLanguageId());
0834: stmt.setString(5, getLinkSearchString(docId.getSeqId(),
0835: docId.getNamespaceId(), document.getBranchId(),
0836: document.getLanguageId()));
0837: stmt.setString(6, getVariantSearchString(document
0838: .getBranchId(), document.getLanguageId()));
0839: stmt.setLong(7, variant.getDocumentTypeId());
0840: stmt.setBoolean(8, variant.isRetired());
0841: stmt.setLong(9, -1);
0842: stmt.setLong(10, -1);
0843: stmt.setTimestamp(11, new Timestamp(now.getTime()));
0844: stmt.setLong(12, documentInt.getCurrentUser().getId());
0845: stmt.setLong(13, 1L);
0846: stmt.setLong(14, variant.getCreatedFromBranchId());
0847: stmt.setLong(15, variant.getCreatedFromLanguageId());
0848: stmt.setLong(16, variant.getCreatedFromVersionId());
0849:
0850: stmt.execute();
0851: stmt.close();
0852: newVersion = true;
0853: } else if (variant.needsSaving()) {
0854: // check the document variant is really there and not modified by someone else
0855: // use lock clause to avoid others updating the document while we're at it.
0856: stmt = conn
0857: .prepareStatement("select updatecount from document_variants where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? "
0858: + jdbcHelper.getSharedLockClause());
0859: stmt.setLong(1, docId.getSeqId());
0860: stmt.setLong(2, docId.getNsId());
0861: stmt.setLong(3, document.getBranchId());
0862: stmt.setLong(4, document.getLanguageId());
0863: ResultSet rs = stmt.executeQuery();
0864: if (!rs.next()) {
0865: throw new RepositoryException(
0866: "The document variant for "
0867: + variant.getKey()
0868: + " does not exist in the repository.");
0869: } else {
0870: long dbUpdateCount = rs.getLong(1);
0871: if (dbUpdateCount != variant.getUpdateCount())
0872: throw new ConcurrentUpdateException(
0873: Document.class.getName() + "-variant",
0874: document.getId()
0875: + "~"
0876: + getBranchName(document
0877: .getBranchId())
0878: + "~"
0879: + getLanguageName(document
0880: .getLanguageId()));
0881: }
0882: rs.close();
0883: stmt.close();
0884: newVersion = true;
0885:
0886: // check that if there is a pessimistic lock on the document variant, it is owned by the
0887: // current user. Also make that the lock record is selected with a lock so that
0888: // no one can create a lock while the document variant is being saved.
0889: LockInfo lockInfo = loadLock(docId, document
0890: .getBranchId(), document.getLanguageId(), conn,
0891: executeAfterCommit);
0892: if (lockInfo.hasLock()
0893: && lockInfo.getType() == LockType.PESSIMISTIC
0894: && lockInfo.getUserId() != documentInt
0895: .getCurrentUser().getId()) {
0896: String lockOwnerLogin = "";
0897: try {
0898: lockOwnerLogin = context.getCommonRepository()
0899: .getUserManager().getUserLogin(
0900: lockInfo.getUserId());
0901: } catch (Throwable e) {
0902: // ignore
0903: }
0904: throw new DocumentLockedException(id, lockInfo
0905: .getUserId(), lockOwnerLogin);
0906: }
0907: }
0908:
0909: long lastVersionId = -1;
0910: long liveVersionId = -1;
0911: boolean summaryNeedsUpdating = false;
0912: String summary = null;
0913: long variantNewUpdateCount = -1;
0914:
0915: if (newVersion) {
0916:
0917: // store the various changes
0918: if (variantInt.hasCustomFieldChanges())
0919: storeCustomFields(document, docId, conn);
0920:
0921: if (variantInt.hasCollectionChanges())
0922: storeCollections(variant, docId, conn);
0923:
0924: if (variant.needsNewVersion()) {
0925: checkChangeCommentSize(document
0926: .getNewChangeComment());
0927:
0928: lastVersionId = createVersion(document, variantInt,
0929: docId, now, conn);
0930: partUpdates = storeParts(variant, variantInt,
0931: docId, lastVersionId, conn);
0932: storeFields(document, docId, lastVersionId, conn);
0933: storeLinks(document, docId, lastVersionId, conn);
0934: if (document.getNewVersionState() == VersionState.PUBLISH)
0935: summaryNeedsUpdating = true;
0936: } else {
0937: lastVersionId = variantInt.getLastVersionId();
0938: }
0939:
0940: liveVersionId = getLastPublishedVersionId(conn, docId,
0941: document.getBranchId(), document
0942: .getLanguageId());
0943:
0944: long lastMajorChangeVersionId = getLastMajorChangeVersionId(
0945: conn, docId, document.getBranchId(), document
0946: .getLanguageId());
0947: long liveMajorChangeVersionId = getLastPublishedMajorChangeVersionId(
0948: conn, docId, document.getBranchId(), document
0949: .getLanguageId(), liveVersionId);
0950:
0951: variantNewUpdateCount = variant.getUpdateCount() + 1;
0952:
0953: // update document_variants record
0954: stmt = conn
0955: .prepareStatement("update document_variants set last_modified = ?, last_modifier = ?, lastversion_id = ?, liveversion_id = ?, retired = ?, updatecount = ?, doctype_id = ?,"
0956: + " last_major_change_version_id = ?, live_major_change_version_id = ?"
0957: + " where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
0958: stmt.setTimestamp(1, new Timestamp(now.getTime()));
0959: stmt.setLong(2, documentInt.getCurrentUser().getId());
0960: stmt.setLong(3, lastVersionId);
0961: stmt.setLong(4, liveVersionId);
0962: stmt.setBoolean(5, document.isRetired());
0963: stmt.setLong(6, variantNewUpdateCount);
0964: stmt.setLong(7, document.getDocumentTypeId());
0965: jdbcHelper.setNullableIdField(stmt, 8,
0966: lastMajorChangeVersionId);
0967: jdbcHelper.setNullableIdField(stmt, 9,
0968: liveMajorChangeVersionId);
0969: stmt.setLong(10, docId.getSeqId());
0970: stmt.setLong(11, docId.getNsId());
0971: stmt.setLong(12, document.getBranchId());
0972: stmt.setLong(13, document.getLanguageId());
0973:
0974: stmt.executeUpdate();
0975: stmt.close();
0976:
0977: // extract links
0978: LinkExtractorHelper linkExtractorHelper = new LinkExtractorHelper(
0979: document, docId, liveVersionId, lastVersionId,
0980: true, systemUser, context);
0981: Collection<LinkInfo> links = linkExtractorHelper
0982: .extract();
0983: storeExtractedLinks(conn, docId,
0984: document.getBranchId(), document
0985: .getLanguageId(), links);
0986:
0987: // create summary
0988: if (summaryNeedsUpdating) {
0989: summary = storeSummary(conn, document, docId, -1);
0990: } else {
0991: summary = document.getSummary();
0992: }
0993:
0994: // store event record (this is used for guaranteed, asynchronous events)
0995: // This is done as part of the transaction to be sure that it is not lost: either
0996: // the document variant and the event record are stored, or both are not.
0997: XmlObject eventDescription;
0998: if (isVariantNew) {
0999: eventDescription = createNewVariantEvent(document,
1000: docId, now, summary, variantNewUpdateCount,
1001: documentNewUpdateCount);
1002: } else {
1003: eventDescription = createVariantUpdatedEvent(
1004: oldDocument, document, now, summary,
1005: lastVersionId, variantNewUpdateCount,
1006: documentNewUpdateCount);
1007: }
1008:
1009: eventHelper.createEvent(eventDescription,
1010: isVariantNew ? "DocumentVariantCreated"
1011: : "DocumentVariantUpdated", conn);
1012: }
1013:
1014: conn.commit();
1015: executeRunnables(executeAfterCommit);
1016:
1017: // now that everything's saved correctly, update document object status
1018: if (newVersion)
1019: variantInt.saved(lastVersionId, liveVersionId, now,
1020: summary, variantNewUpdateCount);
1021: if (isDocumentNew)
1022: documentInt.saved(docId, now, now,
1023: documentNewUpdateCount);
1024: } catch (Throwable e) {
1025: jdbcHelper.rollback(conn);
1026: if (partUpdates != null) {
1027: for (PartUpdate partUpdate : partUpdates) {
1028: partUpdate.rollback();
1029: }
1030: }
1031: if (e instanceof DocumentLockedException)
1032: throw (DocumentLockedException) e;
1033: else if (e instanceof AccessException)
1034: throw (AccessException) e;
1035: else
1036: throw new RepositoryException(
1037: "Problem storing document.", e);
1038: } finally {
1039: jdbcHelper.closeStatement(stmt);
1040: jdbcHelper.closeConnection(conn);
1041: }
1042:
1043: // fire synchronous events
1044: if (isDocumentNew)
1045: context.getCommonRepository().fireRepositoryEvent(
1046: RepositoryEventType.DOCUMENT_CREATED, id,
1047: document.getUpdateCount());
1048:
1049: if (isVariantNew)
1050: context.getCommonRepository().fireRepositoryEvent(
1051: RepositoryEventType.DOCUMENT_VARIANT_CREATED,
1052: variant.getKey(), variant.getUpdateCount());
1053: else
1054: context.getCommonRepository().fireRepositoryEvent(
1055: RepositoryEventType.DOCUMENT_VARIANT_UPDATED,
1056: variant.getKey(), variant.getUpdateCount());
1057: }
1058:
1059: private void checkChangeCommentSize(String changeComment)
1060: throws RepositoryException {
1061: if (changeComment != null && changeComment.length() > 1023) {
1062: throw new RepositoryException(
1063: "Version change comments are not allowed to exceed 1023 characters.");
1064: }
1065: }
1066:
1067: private void checkValidSyncedWith(Connection conn, DocId docId,
1068: long docBranchId, VersionKey newSyncedWith)
1069: throws RepositoryException, SQLException {
1070: if (newSyncedWith != null) {
1071: if (newSyncedWith.getLanguageId() == -1) {
1072: if (newSyncedWith.getVersionId() != -1)
1073: throw new RepositoryException(
1074: "language and version for syncedWith must both be equal to -1 or both be larger than 0");
1075: } else {
1076: long lastSyncedWithVersionId = getLastVersionId(conn,
1077: docId, docBranchId, newSyncedWith
1078: .getLanguageId());
1079: if (lastSyncedWithVersionId == 0
1080: || newSyncedWith.getVersionId() < 0
1081: || lastSyncedWithVersionId < newSyncedWith
1082: .getVersionId())
1083: throw new RepositoryException(
1084: "Synced with can not be set to non-existing version "
1085: + newSyncedWith);
1086: }
1087: }
1088: }
1089:
1090: private long getLastMajorChangeVersionId(Connection conn,
1091: DocId docId, long branchId, long languageId)
1092: throws SQLException {
1093: PreparedStatement stmt = null;
1094: try {
1095: stmt = conn
1096: .prepareStatement("select max(id) from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and change_type = ?");
1097: stmt.setLong(1, docId.getSeqId());
1098: stmt.setLong(2, docId.getNsId());
1099: stmt.setLong(3, branchId);
1100: stmt.setLong(4, languageId);
1101: stmt.setString(5, ChangeType.MAJOR.getCode());
1102: ResultSet rs = stmt.executeQuery();
1103: rs.next();
1104: long lastMajorChangeVersionId = jdbcHelper
1105: .getNullableIdField(rs, 1);
1106: return lastMajorChangeVersionId == 0 ? -1
1107: : lastMajorChangeVersionId;
1108: } finally {
1109: jdbcHelper.closeStatement(stmt);
1110: }
1111: }
1112:
1113: private long getLastPublishedMajorChangeVersionId(Connection conn,
1114: DocId docId, long branchId, long languageId,
1115: long liveVersionId) throws SQLException {
1116: PreparedStatement stmt = null;
1117: try {
1118: stmt = conn
1119: .prepareStatement("select max(id) from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and change_type = ? and id <= ?");
1120: stmt.setLong(1, docId.getSeqId());
1121: stmt.setLong(2, docId.getNsId());
1122: stmt.setLong(3, branchId);
1123: stmt.setLong(4, languageId);
1124: stmt.setString(5, ChangeType.MAJOR.getCode());
1125: stmt.setLong(6, liveVersionId);
1126: ResultSet rs = stmt.executeQuery();
1127: rs.next();
1128: long liveMajorChangeVersionId = jdbcHelper
1129: .getNullableIdField(rs, 1);
1130: return liveMajorChangeVersionId == 0 ? -1
1131: : liveMajorChangeVersionId;
1132: } finally {
1133: jdbcHelper.closeStatement(stmt);
1134: }
1135: }
1136:
1137: private void storeExtractedLinks(Connection conn, DocId docId,
1138: long branchId, long languageId, Collection<LinkInfo> links)
1139: throws SQLException {
1140: PreparedStatement stmt = null;
1141: try {
1142: stmt = conn
1143: .prepareStatement("delete from extracted_links where source_doc_id = ? and source_ns_id = ? and source_branch_id = ? and source_lang_id = ?");
1144: stmt.setLong(1, docId.getSeqId());
1145: stmt.setLong(2, docId.getNsId());
1146: stmt.setLong(3, branchId);
1147: stmt.setLong(4, languageId);
1148: stmt.execute();
1149: stmt.close();
1150:
1151: if (links.size() > 0) {
1152: stmt = conn
1153: .prepareStatement("insert into extracted_links(source_doc_id, source_ns_id, source_branch_id, source_lang_id, source_parttype_id, target_doc_id, target_ns_id, target_branch_id, target_lang_id, target_version_id, linktype, in_last_version, in_live_version) values(?,?,?,?,?,?,?,?,?,?,?,?,?)");
1154:
1155: for (LinkInfo linkInfo : links) {
1156:
1157: stmt.setLong(1, linkInfo.sourceDocSeqId);
1158: stmt.setLong(2, linkInfo.sourceDocNsId);
1159: stmt.setLong(3, linkInfo.sourceBranchId);
1160: stmt.setLong(4, linkInfo.sourceLanguageId);
1161: stmt.setLong(5, linkInfo.sourcePartTypeId);
1162: stmt.setLong(6, linkInfo.targetDocSeqId);
1163: stmt.setLong(7, linkInfo.targetDocNsId);
1164: stmt.setLong(8, linkInfo.targetBranchId);
1165: stmt.setLong(9, linkInfo.targetLanguageId);
1166: stmt.setLong(10, linkInfo.targetVersionId);
1167: stmt.setString(11, linkInfo.linkType.getCode());
1168: stmt.setBoolean(12, linkInfo.occursInLastVersion);
1169: stmt.setBoolean(13, linkInfo.occursInLiveVersion);
1170:
1171: stmt.addBatch();
1172: }
1173:
1174: stmt.executeBatch();
1175: stmt.close();
1176: }
1177: } finally {
1178: jdbcHelper.closeStatement(stmt);
1179: }
1180: }
1181:
1182: /**
1183: *
1184: * @param versionId live version id, or -1 if the live-version-to-be is the data in the document object
1185: */
1186: private String storeSummary(Connection conn, DocumentImpl document,
1187: DocId docId, long versionId) throws SQLException {
1188: PreparedStatement stmt = null;
1189: try {
1190: String summary = null;
1191: try {
1192: AuthenticatedUser user = document.getIntimateAccess(
1193: this ).getCurrentUser();
1194: summary = context.getDocumentSummarizer().getSummary(
1195: document,
1196: versionId,
1197: new RepositorySchemaImpl(context
1198: .getRepositorySchema(), user));
1199: } catch (Exception e) {
1200: logger
1201: .error(
1202: "Error creating summary for document ID "
1203: + docId
1204: + ", branch "
1205: + getBranchLabel(document
1206: .getBranchId())
1207: + ", language "
1208: + getLanguageLabel(document
1209: .getLanguageId()), e);
1210: }
1211:
1212: // check if a summary record exists
1213: stmt = conn
1214: .prepareStatement("select 1 from summaries where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1215: stmt.setLong(1, docId.getSeqId());
1216: stmt.setLong(2, docId.getNsId());
1217: stmt.setLong(3, document.getBranchId());
1218: stmt.setLong(4, document.getLanguageId());
1219: ResultSet rs = stmt.executeQuery();
1220: boolean storedSummaryExists = rs.next();
1221: stmt.close();
1222:
1223: if (summary != null && storedSummaryExists) {
1224: stmt = conn
1225: .prepareStatement("update summaries set summary=? where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1226: stmt.setString(1, summary);
1227: stmt.setLong(2, docId.getSeqId());
1228: stmt.setLong(3, docId.getNsId());
1229: stmt.setLong(4, document.getBranchId());
1230: stmt.setLong(5, document.getLanguageId());
1231: stmt.executeUpdate();
1232: stmt.close();
1233: } else if (summary != null && !storedSummaryExists) {
1234: stmt = conn
1235: .prepareStatement("insert into summaries(doc_id, ns_id, branch_id, lang_id, summary) values(?,?,?,?,?)");
1236: stmt.setLong(1, docId.getSeqId());
1237: stmt.setLong(2, docId.getNsId());
1238: stmt.setLong(3, document.getBranchId());
1239: stmt.setLong(4, document.getLanguageId());
1240: stmt.setString(5, summary);
1241: stmt.execute();
1242: stmt.close();
1243: } else if (summary == null && storedSummaryExists) {
1244: stmt = conn
1245: .prepareStatement("delete from summaries where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1246: stmt.setLong(1, docId.getSeqId());
1247: stmt.setLong(2, docId.getNsId());
1248: stmt.setLong(3, document.getBranchId());
1249: stmt.setLong(4, document.getLanguageId());
1250: stmt.execute();
1251: stmt.close();
1252: }
1253: return summary;
1254: } finally {
1255: jdbcHelper.closeStatement(stmt);
1256: }
1257: }
1258:
1259: private void storeCollections(DocumentVariantImpl variant,
1260: DocId docId, Connection conn) throws SQLException,
1261: RepositoryException {
1262: // first we need to remove this document variant from all the collections it belongs to
1263: clearCollections(docId, variant.getBranchId(), variant
1264: .getLanguageId(), conn);
1265: // now we can add the document variant to its updated set of collections
1266: addDocumentToCollections(variant.getIntimateAccess(this )
1267: .getDocumentCollectionImpls(), docId, variant
1268: .getBranchId(), variant.getLanguageId(), conn);
1269: }
1270:
1271: private void clearCollections(DocId docId, long branchId,
1272: long languageId, Connection conn) throws SQLException {
1273: PreparedStatement stmt = null;
1274:
1275: try {
1276: stmt = conn
1277: .prepareStatement("delete from document_collections where document_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1278: stmt.setLong(1, docId.getSeqId());
1279: stmt.setLong(2, docId.getNsId());
1280: stmt.setLong(3, branchId);
1281: stmt.setLong(4, languageId);
1282: stmt.executeUpdate();
1283: } finally {
1284: jdbcHelper.closeStatement(stmt);
1285: }
1286: }
1287:
1288: private long createVersion(DocumentImpl document,
1289: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
1290: Date now, Connection conn) throws SQLException {
1291: PreparedStatement stmt = null;
1292: try {
1293: long newVersionId = 1;
1294:
1295: if (!document.isNew()) {
1296: // get the number of the previous last version
1297: stmt = conn
1298: .prepareStatement("select max(id) from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1299: stmt.setLong(1, docId.getSeqId());
1300: stmt.setLong(2, docId.getNsId());
1301: stmt.setLong(3, document.getBranchId());
1302: stmt.setLong(4, document.getLanguageId());
1303: ResultSet rs = stmt.executeQuery();
1304: rs.next();
1305: newVersionId = rs.getLong(1) + 1;
1306: rs.close();
1307: stmt.close();
1308: }
1309:
1310: long totalPartSize = 0L;
1311: PartImpl[] parts = variantInt.getPartImpls();
1312: for (PartImpl part : parts)
1313: totalPartSize += part.getSize();
1314:
1315: long userId = variantInt.getCurrentUser().getId();
1316: stmt = conn
1317: .prepareStatement("insert into document_versions(id, doc_id, ns_id, branch_id, lang_id, name, created_on, created_by, state, synced_with_lang_id, synced_with_version_id, synced_with_search, change_type, change_comment, last_modified, last_modifier, total_size_of_parts) values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
1318: stmt.setLong(1, newVersionId);
1319: stmt.setLong(2, docId.getSeqId());
1320: stmt.setLong(3, docId.getNsId());
1321: stmt.setLong(4, document.getBranchId());
1322: stmt.setLong(5, document.getLanguageId());
1323: stmt.setString(6, document.getName());
1324: stmt.setTimestamp(7, new Timestamp(now.getTime()));
1325: stmt.setLong(8, userId);
1326: stmt.setString(9, document.getNewVersionState().getCode());
1327: VersionKey syncedWith = document.getNewSyncedWith();
1328: jdbcHelper.setNullableIdField(stmt, 10,
1329: syncedWith == null ? -1 : syncedWith
1330: .getLanguageId());
1331: jdbcHelper
1332: .setNullableIdField(stmt, 11,
1333: syncedWith == null ? -1 : syncedWith
1334: .getVersionId());
1335: if (syncedWith != null)
1336: stmt.setString(12, getLinkSearchString(
1337: docId.getSeqId(), docId.getNsId(), document
1338: .getBranchId(), syncedWith
1339: .getLanguageId()));
1340: else
1341: stmt.setNull(12, Types.VARCHAR);
1342: stmt.setString(13, document.getNewChangeType().getCode());
1343: stmt.setString(14, document.getNewChangeComment());
1344: stmt.setTimestamp(15, new Timestamp(now.getTime()));
1345: stmt.setLong(16, userId);
1346: stmt.setLong(17, totalPartSize);
1347: stmt.execute();
1348: stmt.close();
1349:
1350: return newVersionId;
1351: } finally {
1352: jdbcHelper.closeStatement(stmt);
1353: }
1354: }
1355:
1356: private void storeCustomFields(DocumentImpl document, DocId docId,
1357: Connection conn) throws SQLException {
1358: PreparedStatement stmt = null;
1359: try {
1360: if (!document.isNew()) {
1361: stmt = conn
1362: .prepareStatement("delete from customfields where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1363: stmt.setLong(1, docId.getSeqId());
1364: stmt.setLong(2, docId.getNsId());
1365: stmt.setLong(3, document.getBranchId());
1366: stmt.setLong(4, document.getLanguageId());
1367: stmt.execute();
1368: stmt.close();
1369: }
1370:
1371: for (Map.Entry<String, String> entry : document
1372: .getCustomFields().entrySet()) {
1373: stmt = conn
1374: .prepareStatement("insert into customfields(doc_id, ns_id, branch_id, lang_id, name, value) values(?,?,?,?,?,?)");
1375: stmt.setLong(1, docId.getSeqId());
1376: stmt.setLong(2, docId.getNsId());
1377: stmt.setLong(3, document.getBranchId());
1378: stmt.setLong(4, document.getLanguageId());
1379: stmt.setString(5, entry.getKey());
1380: stmt.setString(6, entry.getValue());
1381: stmt.execute();
1382: }
1383: } finally {
1384: jdbcHelper.closeStatement(stmt);
1385: }
1386: }
1387:
1388: private List<PartUpdate> storeParts(DocumentVariantImpl variant,
1389: DocumentVariantImpl.IntimateAccess variantInt, DocId docId,
1390: long versionId, Connection conn) throws SQLException,
1391: RepositoryException {
1392: PreparedStatement stmt = null;
1393: try {
1394: PartImpl[] parts = variantInt.getPartImpls();
1395: List<PartUpdate> partUpdates = new ArrayList<PartUpdate>(
1396: parts.length);
1397: for (PartImpl part : parts) {
1398: PartImpl.IntimateAccess partInt = part
1399: .getIntimateAccess(this );
1400: String oldBlobKey = partInt.getBlobKey();
1401: String blobKey = partInt.getBlobKey();
1402: long changedInVersion = part.getDataChangedInVersion();
1403: if (partInt.isDataUpdated()) {
1404: // first store the blob
1405: try {
1406: PartDataSource partDataSource = partInt
1407: .getPartDataSource();
1408: blobKey = context.getBlobStore().store(
1409: partDataSource.createInputStream());
1410: } catch (Exception e) {
1411: throw new RepositoryException(
1412: "Error storing part data to blobstore.",
1413: e);
1414: }
1415: partUpdates.add(new PartUpdate(partInt, blobKey,
1416: oldBlobKey));
1417: // we update the object model here already, even though the transaction is not yet
1418: // committed, so that other components like the link extractor and the summarizer can
1419: // read out the new data. This change will be rolled back if the transaction changes
1420: // (see PartUpdate.rollback())
1421: partInt.setBlobKey(blobKey);
1422: changedInVersion = versionId;
1423: }
1424:
1425: // insert a record
1426: if (stmt == null) {
1427: stmt = conn
1428: .prepareStatement("insert into parts(doc_id, ns_id, branch_id, lang_id, version_id, blob_id, mimetype, filename, parttype_id, blob_size, changed_in_version) values(?,?,?,?,?,?,?,?,?,?,?)");
1429: }
1430:
1431: stmt.setLong(1, docId.getSeqId());
1432: stmt.setLong(2, docId.getNsId());
1433: stmt.setLong(3, variant.getBranchId());
1434: stmt.setLong(4, variant.getLanguageId());
1435: stmt.setLong(5, versionId);
1436: stmt.setString(6, blobKey);
1437: stmt.setString(7, part.getMimeType());
1438: stmt.setString(8, part.getFileName());
1439: stmt.setLong(9, part.getTypeId());
1440: stmt.setLong(10, part.getSize());
1441: stmt.setLong(11, changedInVersion);
1442: stmt.execute();
1443: }
1444: return partUpdates;
1445: } finally {
1446: jdbcHelper.closeStatement(stmt);
1447: }
1448: }
1449:
1450: private class PartUpdate {
1451: private final PartImpl.IntimateAccess partInt;
1452: private final String blobKey;
1453: private final String oldBlobKey;
1454: private final PartDataSource partDataSource;
1455:
1456: public PartUpdate(PartImpl.IntimateAccess partInt,
1457: String blobKey, String oldBlobKey) {
1458: this .partInt = partInt;
1459: this .blobKey = blobKey;
1460: this .oldBlobKey = oldBlobKey;
1461: this .partDataSource = partInt.getPartDataSource();
1462: partInt.setData(null);
1463: }
1464:
1465: public void rollback() {
1466: partInt.setBlobKey(oldBlobKey);
1467: partInt.setData(partDataSource);
1468: try {
1469: context.getBlobStore().delete(blobKey);
1470: } catch (Throwable e) {
1471: logger.error("Problem removing blob \"" + blobKey
1472: + "\" after failed document update. Ignoring.",
1473: e);
1474: }
1475: }
1476: }
1477:
1478: private void storeFields(DocumentImpl document, DocId docId,
1479: long versionId, Connection conn) throws SQLException {
1480: PreparedStatement stmt = null;
1481: try {
1482: // insert new records
1483: stmt = conn
1484: .prepareStatement("insert into thefields(doc_id, ns_id, branch_id, lang_id, version_id, fieldtype_id, value_seq, value_count, hier_seq, hier_count, stringvalue, datevalue, datetimevalue, integervalue, floatvalue, decimalvalue, booleanvalue, link_docid, link_nsid, link_searchdocid, link_branchid, link_searchbranchid, link_langid, link_searchlangid, link_search) values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
1485: stmt.setLong(1, docId.getSeqId());
1486: stmt.setLong(2, docId.getNsId());
1487: stmt.setLong(3, document.getBranchId());
1488: stmt.setLong(4, document.getLanguageId());
1489: stmt.setLong(5, versionId);
1490: Field[] fields = document.getFields().getArray();
1491: // Run over the fields ...
1492: for (Field field : fields) {
1493: stmt.setLong(6, field.getTypeId());
1494:
1495: ValueType fieldType = field.getValueType();
1496: Object[] multiValues = field.isMultiValue() ? (Object[]) field
1497: .getValue()
1498: : new Object[] { field.getValue() };
1499:
1500: stmt.setLong(8, multiValues.length); // value_count
1501:
1502: // Run over the multi-values ...
1503: for (int k = 0; k < multiValues.length; k++) {
1504: Object valueFromMultiValue = multiValues[k];
1505: Object[] pathValues = field.isHierarchical() ? ((HierarchyPath) valueFromMultiValue)
1506: .getElements()
1507: : new Object[] { valueFromMultiValue };
1508:
1509: stmt.setInt(7, k + 1); // value_seq
1510: stmt.setLong(10, pathValues.length); // hier_count
1511:
1512: // Run over the hierarhical path ...
1513: for (int p = 0; p < pathValues.length; p++) {
1514: Object value = pathValues[p];
1515:
1516: String stringValue = (String) (fieldType == ValueType.STRING ? value
1517: : null);
1518: Date dateValue = (fieldType == ValueType.DATE ? new java.sql.Date(
1519: ((Date) value).getTime())
1520: : null);
1521: Timestamp datetimeValue = (fieldType == ValueType.DATETIME ? new Timestamp(
1522: ((Date) value).getTime())
1523: : null);
1524: Long integerValue = (Long) (fieldType == ValueType.LONG ? value
1525: : null);
1526: Double floatValue = (Double) (fieldType == ValueType.DOUBLE ? value
1527: : null);
1528: BigDecimal decimalValue = (BigDecimal) (fieldType == ValueType.DECIMAL ? value
1529: : null);
1530: Boolean booleanValue = (Boolean) (fieldType == ValueType.BOOLEAN ? value
1531: : null);
1532:
1533: stmt.setInt(9, p + 1); // hier_seq
1534: stmt.setString(11, stringValue);
1535: stmt.setObject(12, dateValue);
1536: stmt.setObject(13, datetimeValue);
1537: stmt.setObject(14, integerValue);
1538: stmt.setObject(15, floatValue);
1539: stmt.setBigDecimal(16, decimalValue);
1540: stmt.setObject(17, booleanValue);
1541:
1542: if (fieldType == ValueType.LINK) {
1543: VariantKey variantKey = (VariantKey) value;
1544: long branchId = variantKey.getBranchId() == -1 ? document
1545: .getBranchId()
1546: : variantKey.getBranchId();
1547: long languageId = variantKey
1548: .getLanguageId() == -1 ? document
1549: .getLanguageId() : variantKey
1550: .getLanguageId();
1551:
1552: DocId linkDocId = DocId.parseDocId(
1553: variantKey.getDocumentId(), context
1554: .getCommonRepository());
1555:
1556: stmt.setLong(18, linkDocId.getSeqId());
1557: stmt.setLong(19, linkDocId.getNsId());
1558: stmt.setString(20, linkDocId.getSeqId()
1559: + "-" + linkDocId.getNsId());
1560: stmt.setLong(21, variantKey.getBranchId());
1561: stmt.setLong(22, branchId);
1562: stmt
1563: .setLong(23, variantKey
1564: .getLanguageId());
1565: stmt.setLong(24, languageId);
1566: stmt.setString(25, getLinkSearchString(
1567: linkDocId.getSeqId(), linkDocId
1568: .getNsId(), branchId,
1569: languageId));
1570: } else {
1571: stmt.setNull(18, Types.BIGINT);
1572: stmt.setNull(19, Types.BIGINT);
1573: stmt.setNull(20, Types.VARCHAR);
1574: stmt.setNull(21, Types.BIGINT);
1575: stmt.setNull(22, Types.BIGINT);
1576: stmt.setNull(23, Types.BIGINT);
1577: stmt.setNull(24, Types.BIGINT);
1578: stmt.setNull(25, Types.VARCHAR);
1579: }
1580:
1581: stmt.execute();
1582: }
1583: }
1584: }
1585: stmt.close();
1586: } finally {
1587: jdbcHelper.closeStatement(stmt);
1588: }
1589: }
1590:
1591: private String getLinkSearchString(long seqId, long nsId,
1592: long branchId, long langId) {
1593: return seqId + "-" + nsId + "@" + branchId + ":" + langId;
1594: }
1595:
1596: private String getVariantSearchString(long branchId, long langId) {
1597: return branchId + ":" + langId;
1598: }
1599:
1600: private void storeLinks(DocumentImpl document, DocId docId,
1601: long versionId, Connection conn) throws SQLException {
1602: PreparedStatement stmt = null;
1603: try {
1604: stmt = conn
1605: .prepareStatement("insert into links(id, doc_id, ns_id, branch_id, lang_id, version_id, title, target) values (?,?,?,?,?,?,?,?)");
1606: stmt.setLong(2, docId.getSeqId());
1607: stmt.setLong(3, docId.getNsId());
1608: stmt.setLong(4, document.getBranchId());
1609: stmt.setLong(5, document.getLanguageId());
1610: stmt.setLong(6, versionId);
1611: Link[] links = document.getLinks().getArray();
1612: for (int i = 0; i < links.length; i++) {
1613: stmt.setLong(1, i);
1614: stmt.setString(7, links[i].getTitle());
1615: stmt.setString(8, links[i].getTarget());
1616: stmt.execute();
1617: }
1618: stmt.close();
1619: } finally {
1620: jdbcHelper.closeStatement(stmt);
1621: }
1622: }
1623:
1624: public InputStream getBlob(DocId docId, long branchId,
1625: long languageId, long versionId, long partTypeId,
1626: AuthenticatedUser user) throws RepositoryException {
1627: Document document = context.getCommonRepository().getDocument(
1628: docId, branchId, languageId, false, user);
1629: return document.getVersion(versionId).getPart(partTypeId)
1630: .getDataStream();
1631: }
1632:
1633: public InputStream getBlob(String blobKey)
1634: throws RepositoryException {
1635: try {
1636: return context.getBlobStore().retrieve(blobKey);
1637: } catch (BlobIOException e) {
1638: throw new RepositoryException(
1639: "Problem retrieving blob data.", e);
1640: } catch (NonExistingBlobException e) {
1641: throw new RepositoryException(
1642: "Problem retrieving blob data.", e);
1643: }
1644: }
1645:
1646: public VersionImpl[] loadShallowVersions(DocumentVariantImpl variant)
1647: throws RepositoryException {
1648: DocumentVariantImpl.IntimateAccess variantInt = variant
1649: .getIntimateAccess(this );
1650: DocId docId = variantInt.getDocument().getIntimateAccess(this )
1651: .getDocId();
1652: VersionImpl[] versions = new VersionImpl[(int) variantInt
1653: .getLastVersionId()];
1654:
1655: Connection conn = null;
1656: PreparedStatement stmt = null;
1657: try {
1658: conn = context.getDataSource().getConnection();
1659: stmt = conn
1660: .prepareStatement("select id, created_on, created_by, name, state, synced_with_lang_id, synced_with_version_id, change_type, change_comment, last_modified, last_modifier, total_size_of_parts from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? order by id ASC");
1661: stmt.setLong(1, docId.getSeqId());
1662: stmt.setLong(2, docId.getNamespaceId());
1663: stmt.setLong(3, variant.getBranchId());
1664: stmt.setLong(4, variant.getLanguageId());
1665: ResultSet rs = stmt.executeQuery();
1666:
1667: for (int i = 0; i < versions.length; i++) {
1668: rs.next();
1669: versions[i] = loadShallowVersionFromResultSet(rs,
1670: docId, variant);
1671: }
1672: stmt.close();
1673: } catch (Throwable e) {
1674: throw new RepositoryException(
1675: "Error loading versions (document ID: "
1676: + variant.getDocumentId() + ", branch: "
1677: + getBranchLabel(variant.getBranchId())
1678: + ", language: "
1679: + getLanguageLabel(variant.getLanguageId())
1680: + ").", e);
1681: } finally {
1682: jdbcHelper.closeStatement(stmt);
1683: jdbcHelper.closeConnection(conn);
1684: }
1685:
1686: return versions;
1687: }
1688:
1689: public VersionImpl loadVersion(DocumentVariantImpl variant,
1690: long versionId) throws RepositoryException {
1691: Connection conn = null;
1692: PreparedStatement stmt = null;
1693: try {
1694: conn = context.getDataSource().getConnection();
1695: jdbcHelper.startTransaction(conn);
1696:
1697: return loadVersionInTransaction(conn, variant, versionId);
1698: } catch (Throwable e) {
1699: throw new RepositoryException("Error loading version ("
1700: + getFormattedVariant(variant.getKey(), versionId)
1701: + ").", e);
1702: } finally {
1703: jdbcHelper.closeStatement(stmt);
1704: jdbcHelper.closeConnection(conn);
1705: }
1706: }
1707:
1708: private VersionImpl loadVersionInTransaction(Connection conn,
1709: DocumentVariantImpl variant, long versionId)
1710: throws RepositoryException {
1711: DocumentVariantImpl.IntimateAccess variantInt = variant
1712: .getIntimateAccess(this );
1713: DocId docId = variantInt.getDocument().getIntimateAccess(this )
1714: .getDocId();
1715:
1716: PreparedStatement stmt = null;
1717: try {
1718: stmt = conn
1719: .prepareStatement("select id, created_on, created_by, name, state, synced_with_lang_id, synced_with_version_id, change_type, change_comment, last_modified, last_modifier, total_size_of_parts from document_versions where id = ? and doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1720: stmt.setLong(1, versionId);
1721: stmt.setLong(2, docId.getSeqId());
1722: stmt.setLong(3, docId.getNsId());
1723: stmt.setLong(4, variant.getBranchId());
1724: stmt.setLong(5, variant.getLanguageId());
1725:
1726: ResultSet rs = stmt.executeQuery();
1727: if (!rs.next())
1728: throw new RepositoryException(
1729: "Unexpected problem: record for version not found ("
1730: + getFormattedVariant(variant.getKey(),
1731: versionId) + ").");
1732:
1733: VersionImpl versionImpl = loadShallowVersionFromResultSet(
1734: rs, docId, variant);
1735: stmt.close();
1736:
1737: VersionImpl.IntimateAccess versionInt = versionImpl
1738: .getIntimateAccess(this );
1739:
1740: versionInt.setFields(loadFields(variant, docId, versionId,
1741: conn).toArray(new FieldImpl[0]));
1742: versionInt.setParts(loadParts(variant, variantInt, docId,
1743: versionId, conn).toArray(new PartImpl[0]));
1744: versionInt.setLinks(loadLinks(variant, docId, versionId,
1745: conn).toArray(new LinkImpl[0]));
1746:
1747: return versionImpl;
1748: } catch (Throwable e) {
1749: throw new RepositoryException("Error loading version ("
1750: + getFormattedVariant(variant.getKey(), versionId)
1751: + ").", e);
1752: } finally {
1753: jdbcHelper.closeStatement(stmt);
1754: }
1755: }
1756:
1757: private VersionImpl loadShallowVersionFromResultSet(ResultSet rs,
1758: DocId docId, DocumentVariantImpl variant)
1759: throws SQLException {
1760: DocumentVariantImpl.IntimateAccess variantInt = variant
1761: .getIntimateAccess(this );
1762:
1763: Date createdOn = rs.getTimestamp("created_on");
1764: long createdBy = rs.getLong("created_by");
1765: String documentName = rs.getString("name");
1766: long versionId = rs.getLong("id");
1767: VersionState versionState = VersionState.getByCode(rs
1768: .getString("state"));
1769:
1770: long syncedWithLanguageId = jdbcHelper.getNullableIdField(rs,
1771: "synced_with_lang_id");
1772: long syncedWithVersionId = jdbcHelper.getNullableIdField(rs,
1773: "synced_with_version_id");
1774: VersionKey syncedWith = null;
1775: if (syncedWithLanguageId != -1 && syncedWithVersionId != -1)
1776: syncedWith = new VersionKey(docId.toString(), variant
1777: .getBranchId(), syncedWithLanguageId,
1778: syncedWithVersionId);
1779:
1780: ChangeType changeType = ChangeType.getByCode(rs
1781: .getString("change_type"));
1782: String changeComment = rs.getString("change_comment");
1783:
1784: Date lastModified = rs.getTimestamp("last_modified");
1785: long lastModifier = rs.getLong("last_modifier");
1786: long totalSizeOfParts = rs.getLong("total_size_of_parts");
1787:
1788: return new VersionImpl(variantInt, versionId, documentName,
1789: createdOn, createdBy, versionState, syncedWith,
1790: changeType, changeComment, lastModified, lastModifier,
1791: totalSizeOfParts);
1792: }
1793:
1794: public void completeVersion(DocumentVariantImpl variant,
1795: VersionImpl version) throws RepositoryException {
1796: DocumentVariantImpl.IntimateAccess variantInt = variant
1797: .getIntimateAccess(this );
1798: DocId docId = variantInt.getDocument().getIntimateAccess(this )
1799: .getDocId();
1800:
1801: VersionImpl.IntimateAccess versionInt = version
1802: .getIntimateAccess(this );
1803: Connection conn = null;
1804: try {
1805: conn = context.getDataSource().getConnection();
1806: jdbcHelper.startTransaction(conn);
1807:
1808: PartImpl[] parts = loadParts(variant, variantInt, docId,
1809: version.getId(), conn).toArray(new PartImpl[0]);
1810: FieldImpl[] fields = loadFields(variant, docId,
1811: version.getId(), conn).toArray(new FieldImpl[0]);
1812: LinkImpl[] links = loadLinks(variant, docId,
1813: version.getId(), conn).toArray(new LinkImpl[0]);
1814:
1815: versionInt.setParts(parts);
1816: versionInt.setFields(fields);
1817: versionInt.setLinks(links);
1818: } catch (Throwable e) {
1819: throw new RepositoryException("Error loading version ("
1820: + getFormattedVariant(variant.getKey(), version
1821: .getId()) + ").", e);
1822: } finally {
1823: jdbcHelper.closeConnection(conn);
1824: }
1825: }
1826:
1827: public void storeVersion(DocumentImpl document,
1828: VersionImpl version, VersionState versionState,
1829: VersionKey syncedWith, ChangeType changeType,
1830: String changeComment) throws RepositoryException {
1831: if (logger.isDebugEnabled()) {
1832: logger.debug("setting version properties for "
1833: + version.getDocumentName() + "(" + version + ")");
1834: }
1835: checkChangeCommentSize(changeComment);
1836:
1837: DocumentImpl.IntimateAccess documentInt = document
1838: .getIntimateAccess(this );
1839: Connection conn = null;
1840: PreparedStatement stmt = null;
1841: try {
1842: DocId docId = documentInt.getDocId();
1843:
1844: conn = context.getDataSource().getConnection();
1845: jdbcHelper.startTransaction(conn);
1846:
1847: // lock the document variant while we're doing this
1848: stmt = conn
1849: .prepareStatement("select last_modified from document_variants where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? "
1850: + jdbcHelper.getSharedLockClause());
1851: stmt.setLong(1, docId.getSeqId());
1852: stmt.setLong(2, docId.getNsId());
1853: stmt.setLong(3, document.getBranchId());
1854: stmt.setLong(4, document.getLanguageId());
1855: stmt.execute();
1856: stmt.close();
1857:
1858: stmt = conn
1859: .prepareStatement("select state from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and id = ?");
1860: stmt.setLong(1, docId.getSeqId());
1861: stmt.setLong(2, docId.getNsId());
1862: stmt.setLong(3, document.getBranchId());
1863: stmt.setLong(4, document.getLanguageId());
1864: stmt.setLong(5, version.getId());
1865:
1866: ResultSet rs = stmt.executeQuery();
1867: rs.next();
1868: String state = rs.getString(1);
1869: stmt.close();
1870:
1871: // check ACL.
1872: AclResultInfo aclInfo = context.getCommonRepository()
1873: .getAccessManager().getAclInfoOnLive(
1874: systemUser,
1875: documentInt.getCurrentUser().getId(),
1876: documentInt.getCurrentUser()
1877: .getActiveRoleIds(),
1878: documentInt.getDocId(),
1879: document.getBranchId(),
1880: document.getLanguageId());
1881: if (!version.getState().getCode().equals(state)
1882: && !aclInfo.isAllowed(AclPermission.PUBLISH))
1883: throw new AccessException(
1884: "User "
1885: + documentInt.getCurrentUser()
1886: .getLogin()
1887: + " (ID: "
1888: + documentInt.getCurrentUser().getId()
1889: + ") is not allowed to change the state of versions of document ID "
1890: + document.getId() + ", branch ID "
1891: + document.getBranchId()
1892: + ", language ID "
1893: + document.getLanguageId());
1894:
1895: checkValidSyncedWith(conn, docId, document.getBranchId(),
1896: syncedWith);
1897:
1898: // Get the old version, this is our last chance to do it.
1899: VersionImpl oldVersion = loadVersionInTransaction(conn,
1900: document.getIntimateAccess(this ).getVariant(),
1901: version.getId());
1902:
1903: Date lastModified = new Date();
1904: long lastModifier = documentInt.getCurrentUser().getId();
1905: // update document_versions record
1906: stmt = conn
1907: .prepareStatement("update document_versions set state = ?, synced_with_lang_id = ?, synced_with_version_id = ?, synced_with_search = ?, change_type = ?, change_comment = ?, "
1908: + " last_modified = ?, last_modifier = ? where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and id = ?");
1909: stmt.setString(1, versionState.getCode());
1910: jdbcHelper.setNullableIdField(stmt, 2,
1911: syncedWith != null ? syncedWith.getLanguageId()
1912: : -1);
1913: jdbcHelper
1914: .setNullableIdField(stmt, 3,
1915: syncedWith != null ? syncedWith
1916: .getVersionId() : -1);
1917: if (syncedWith != null)
1918: stmt.setString(4, getLinkSearchString(docId.getSeqId(),
1919: docId.getNsId(), document.getBranchId(),
1920: syncedWith.getLanguageId()));
1921: else
1922: stmt.setNull(4, Types.VARCHAR);
1923: stmt.setString(5, changeType.getCode());
1924: stmt.setString(6, changeComment);
1925: stmt.setTimestamp(7, new Timestamp(lastModified.getTime()));
1926: stmt.setLong(8, lastModifier);
1927: stmt.setLong(9, docId.getSeqId());
1928: stmt.setLong(10, docId.getNsId());
1929: stmt.setLong(11, document.getBranchId());
1930: stmt.setLong(12, document.getLanguageId());
1931: stmt.setLong(13, version.getId());
1932: long updateCount = stmt.executeUpdate();
1933: if (updateCount != 1)
1934: throw new RepositoryException(
1935: "Assertion failed: wrong number of rows updated (when updating state):"
1936: + updateCount);
1937: stmt.close();
1938:
1939: // update live version id in document variant record
1940: // but first retrieve previous value to embed in change event
1941: stmt = conn
1942: .prepareStatement("select liveversion_id from document_variants where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1943: stmt.setLong(1, docId.getSeqId());
1944: stmt.setLong(2, docId.getNsId());
1945: stmt.setLong(3, document.getBranchId());
1946: stmt.setLong(4, document.getLanguageId());
1947: rs = stmt.executeQuery();
1948: rs.next();
1949: long previousLiveVersionId = rs.getLong(1);
1950: stmt.close();
1951:
1952: /*
1953: * About liveVersionId, liveSyncedWith, lastSyncedWith, liveMajorChangeVersionId, lastMajorChangeVersionId:
1954: * These fields do not always need to be recalculated (sometimes their value is unchanged) but the implementation is much cleaner
1955: * if we just calculate everything we need
1956: */
1957: long liveVersionId = getLastPublishedVersionId(conn, docId,
1958: document.getBranchId(), document.getLanguageId());
1959: long lastMajorChangeVersionId = getLastMajorChangeVersionId(
1960: conn, docId, document.getBranchId(), document
1961: .getLanguageId());
1962: long liveMajorChangeVersionId = getLastPublishedMajorChangeVersionId(
1963: conn, docId, document.getBranchId(), document
1964: .getLanguageId(), liveVersionId);
1965:
1966: stmt = conn
1967: .prepareStatement("update document_variants set liveversion_id = ?, last_major_change_version_id = ?,"
1968: + " live_major_change_version_id = ? where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
1969: stmt.setLong(1, liveVersionId);
1970: jdbcHelper.setNullableIdField(stmt, 2,
1971: lastMajorChangeVersionId);
1972: jdbcHelper.setNullableIdField(stmt, 3,
1973: liveMajorChangeVersionId);
1974: stmt.setLong(4, docId.getSeqId());
1975: stmt.setLong(5, docId.getNsId());
1976: stmt.setLong(6, document.getBranchId());
1977: stmt.setLong(7, document.getLanguageId());
1978: updateCount = stmt.executeUpdate();
1979: if (updateCount != 1)
1980: throw new RepositoryException(
1981: "Assertion failed: wrong number of rows updated (when updating live version id): "
1982: + updateCount);
1983: stmt.close();
1984:
1985: // update extracted links and summary text if live version changed
1986: String summary = null;
1987: boolean liveVersionChanged = previousLiveVersionId != liveVersionId;
1988: if (liveVersionChanged) {
1989: LinkExtractorHelper linkExtractorHelper = new LinkExtractorHelper(
1990: document, docId, liveVersionId, document
1991: .getLastVersionId(), false, systemUser,
1992: context);
1993: Collection<LinkInfo> links = linkExtractorHelper
1994: .extract();
1995: storeExtractedLinks(conn, docId,
1996: document.getBranchId(), document
1997: .getLanguageId(), links);
1998: summary = storeSummary(conn, document, docId,
1999: liveVersionId);
2000: }
2001:
2002: // insert an event record in the events table describing the change we've done
2003: VersionUpdatedDocument versionUpdatedDocument = createVersionUpdatedDocument(
2004: document, oldVersion, version,
2005: previousLiveVersionId, liveVersionId, lastModified,
2006: lastModifier, versionState, syncedWith, changeType,
2007: changeComment);
2008: eventHelper.createEvent(versionUpdatedDocument,
2009: "VersionUpdated", conn);
2010:
2011: conn.commit();
2012:
2013: version.getIntimateAccess(this ).stateChanged(versionState,
2014: syncedWith, changeType, changeComment,
2015: lastModified, lastModifier);
2016: if (liveVersionChanged)
2017: documentInt.getVariant().getIntimateAccess(this )
2018: .setSummary(summary);
2019: } catch (Throwable e) {
2020: jdbcHelper.rollback(conn);
2021: if (e instanceof AccessException)
2022: throw (AccessException) e;
2023: throw new RepositoryException(
2024: "Error trying to change version state.", e);
2025: } finally {
2026: jdbcHelper.closeStatement(stmt);
2027: jdbcHelper.closeConnection(conn);
2028: }
2029: context.getCommonRepository().fireRepositoryEvent(
2030: RepositoryEventType.VERSION_UPDATED,
2031: document.getVariantKey(), -1);
2032: }
2033:
2034: private VersionUpdatedDocument createVersionUpdatedDocument(
2035: DocumentImpl document, VersionImpl oldVersion,
2036: VersionImpl version, long previousLiveVersionId,
2037: long liveVersionId, Date lastModified, long lastModifier,
2038: VersionState versionState, VersionKey syncedWith,
2039: ChangeType changeType, String changeComment) {
2040: VersionUpdatedDocument versionUpdatedDocument = VersionUpdatedDocument.Factory
2041: .newInstance();
2042: VersionUpdatedDocument.VersionUpdated versionUpdated = versionUpdatedDocument
2043: .addNewVersionUpdated();
2044:
2045: versionUpdated.setDocumentId(document.getId());
2046: versionUpdated.setBranchId(document.getBranchId());
2047: versionUpdated.setLanguageId(document.getLanguageId());
2048: if (liveVersionId != previousLiveVersionId) {
2049: versionUpdated.setNewLiveVersionId(liveVersionId);
2050: }
2051:
2052: versionUpdated.addNewOldVersion().setVersion(
2053: oldVersion.getShallowXml().getVersion());
2054:
2055: Version newVersionXml = version.getShallowXml().getVersion();
2056: updateUpdatedVersionXml(newVersionXml, lastModified,
2057: lastModifier, versionState, syncedWith, changeType,
2058: changeComment);
2059: versionUpdated.addNewNewVersion().setVersion(newVersionXml);
2060:
2061: return versionUpdatedDocument;
2062: }
2063:
2064: /**
2065: * Updates the version XML so as if it would look if we retrieved the XML of the document after saving it.
2066: */
2067: private void updateUpdatedVersionXml(Version versionXml,
2068: Date lastModified, long lastModifier,
2069: VersionState versionState, VersionKey syncedWith,
2070: ChangeType changeType, String changeComment) {
2071: versionXml.setLastModified(getCalendar(lastModified));
2072: versionXml.setLastModifier(lastModifier);
2073: versionXml.setState(versionState.toString());
2074: if (syncedWith != null) {
2075: versionXml.setSyncedWithLanguageId(-1);
2076: versionXml.setSyncedWithVersionId(-1);
2077: } else {
2078: // we must explicitly clear it because it has been set before
2079: if (versionXml.isSetSyncedWithLanguageId()) {
2080: versionXml.unsetSyncedWithLanguageId();
2081: }
2082: if (versionXml.isSetSyncedWithVersionId()) {
2083: versionXml.unsetSyncedWithVersionId();
2084: }
2085: }
2086: versionXml.setChangeType(changeType.toString());
2087: versionXml.setChangeComment(changeComment);
2088: }
2089:
2090: private long getLastPublishedVersionId(Connection conn,
2091: DocId docId, long branchId, long languageId)
2092: throws SQLException {
2093: PreparedStatement stmt = null;
2094: try {
2095: stmt = conn
2096: .prepareStatement("select max(id) from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and state = 'P'");
2097: stmt.setLong(1, docId.getSeqId());
2098: stmt.setLong(2, docId.getNsId());
2099: stmt.setLong(3, branchId);
2100: stmt.setLong(4, languageId);
2101: ResultSet rs = stmt.executeQuery();
2102: rs.next();
2103: return jdbcHelper.getNullableIdField(rs, 1);
2104: } finally {
2105: jdbcHelper.closeStatement(stmt);
2106: }
2107: }
2108:
2109: private long getLastVersionId(Connection conn, DocId docId,
2110: long branchId, long languageId) throws SQLException {
2111: PreparedStatement stmt = null;
2112: try {
2113: stmt = conn
2114: .prepareStatement("select max(id) from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
2115: stmt.setLong(1, docId.getSeqId());
2116: stmt.setLong(2, docId.getNsId());
2117: stmt.setLong(3, branchId);
2118: stmt.setLong(4, languageId);
2119: ResultSet rs = stmt.executeQuery();
2120: rs.next();
2121: return jdbcHelper.getNullableIdField(rs, 1);
2122: } finally {
2123: jdbcHelper.closeStatement(stmt);
2124: }
2125: }
2126:
2127: public LockInfoImpl lock(DocumentVariantImpl variant,
2128: long duration, LockType lockType)
2129: throws RepositoryException {
2130: DocumentVariantImpl.IntimateAccess variantInt = variant
2131: .getIntimateAccess(this );
2132: DocId docId = variantInt.getDocument().getIntimateAccess(this )
2133: .getDocId();
2134:
2135: // Check if user has write access
2136: AclResultInfo aclInfo = context.getCommonRepository()
2137: .getAccessManager().getAclInfoOnLive(systemUser,
2138: variantInt.getCurrentUser().getId(),
2139: variantInt.getCurrentUser().getActiveRoleIds(),
2140: docId, variant.getBranchId(),
2141: variant.getLanguageId());
2142: if (!aclInfo.isAllowed(AclPermission.WRITE))
2143: throw new AccessException("Write access denied for user "
2144: + variantInt.getCurrentUser().getId() + " to "
2145: + getFormattedVariant(variant.getKey()));
2146:
2147: List<Runnable> executeAfterCommit = new ArrayList<Runnable>(2);
2148: Connection conn = null;
2149: PreparedStatement stmt = null;
2150: LockInfoImpl lockInfo = null;
2151: try {
2152: conn = context.getDataSource().getConnection();
2153: jdbcHelper.startTransaction(conn);
2154:
2155: // lock the database record for the document variant so that the document variant cannot be saved while
2156: // a lock is being taken
2157: stmt = conn
2158: .prepareStatement("select doc_id from document_variants where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? "
2159: + jdbcHelper.getSharedLockClause());
2160: stmt.setLong(1, docId.getSeqId());
2161: stmt.setLong(2, docId.getNsId());
2162: stmt.setLong(3, variant.getBranchId());
2163: stmt.setLong(4, variant.getLanguageId());
2164: stmt.execute();
2165: stmt.close();
2166:
2167: // first check if there is already a lock on this document variant
2168: lockInfo = loadLock(docId, variant.getBranchId(), variant
2169: .getLanguageId(), conn, executeAfterCommit);
2170:
2171: // if there is a pessimistic lock belonging to another user, we cannot override it.
2172: // In that case information about the current lock is returned.
2173: if (lockInfo.hasLock()
2174: && lockInfo.getType() == LockType.PESSIMISTIC
2175: && lockInfo.getUserId() != variantInt
2176: .getCurrentUser().getId()) {
2177: return lockInfo;
2178: }
2179:
2180: if (lockInfo.hasLock()) {
2181: // delete current lock record
2182: stmt = conn
2183: .prepareStatement("delete from locks where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
2184: stmt.setLong(1, docId.getSeqId());
2185: stmt.setLong(2, docId.getNsId());
2186: stmt.setLong(3, variant.getBranchId());
2187: stmt.setLong(4, variant.getLanguageId());
2188: stmt.execute();
2189: stmt.close();
2190: }
2191:
2192: lockInfo = new LockInfoImpl(variantInt.getCurrentUser()
2193: .getId(), new Date(), duration, lockType);
2194:
2195: // Test lock expire time fits in date storage column
2196: if (lockInfo.getDuration() >= 0) {
2197: long rest = Long.MAX_VALUE
2198: - lockInfo.getTimeAcquired().getTime();
2199: if (lockInfo.getDuration() >= rest)
2200: throw new RepositoryException(
2201: "Lock duration too long to fit in storage.");
2202: GregorianCalendar expires = new GregorianCalendar();
2203: expires.setTimeInMillis(lockInfo.getTimeAcquired()
2204: .getTime()
2205: + lockInfo.getDuration());
2206: if (expires.get(Calendar.YEAR) > 9999)
2207: throw new RepositoryException(
2208: "Lock duration too long: expiry date falls after the year 9999");
2209: }
2210:
2211: // insert the new lock
2212: stmt = conn
2213: .prepareStatement("insert into locks(doc_id, ns_id, branch_id, lang_id, user_id, locktype, time_acquired, duration, time_expires) values(?,?,?,?,?,?,?,?,?)");
2214: stmt.setLong(1, docId.getSeqId());
2215: stmt.setLong(2, docId.getNsId());
2216: stmt.setLong(3, variant.getBranchId());
2217: stmt.setLong(4, variant.getLanguageId());
2218: stmt.setLong(5, lockInfo.getUserId());
2219: stmt.setString(6, lockInfo.getType().getCode());
2220: stmt.setTimestamp(7, new Timestamp(lockInfo
2221: .getTimeAcquired().getTime()));
2222: stmt.setLong(8, lockInfo.getDuration());
2223: if (lockInfo.getDuration() >= 0)
2224: stmt.setTimestamp(9, new Timestamp(lockInfo
2225: .getTimeAcquired().getTime()
2226: + lockInfo.getDuration()));
2227: else
2228: stmt.setNull(9, Types.TIMESTAMP);
2229: stmt.execute();
2230: conn.commit();
2231: } catch (Throwable e) {
2232: jdbcHelper.rollback(conn);
2233: executeAfterCommit.clear();
2234: throw new RepositoryException("Error getting a lock on "
2235: + getFormattedVariant(variant.getKey()), e);
2236: } finally {
2237: jdbcHelper.closeStatement(stmt);
2238: jdbcHelper.closeConnection(conn);
2239: executeRunnables(executeAfterCommit);
2240: }
2241:
2242: // fire synchronous events
2243: context.getCommonRepository().fireRepositoryEvent(
2244: RepositoryEventType.LOCK_CHANGE, variant.getKey(), -1);
2245:
2246: return lockInfo;
2247: }
2248:
2249: public LockInfoImpl getLockInfo(DocumentVariantImpl variant)
2250: throws RepositoryException {
2251: Connection conn = null;
2252: try {
2253: DocId docId = variant.getIntimateAccess(this ).getDocument()
2254: .getIntimateAccess(this ).getDocId();
2255: conn = context.getDataSource().getConnection();
2256: jdbcHelper.startTransaction(conn);
2257: List<Runnable> executeAfterCommit = new ArrayList<Runnable>(
2258: 2);
2259: LockInfoImpl lockInfo = loadLock(docId, variant
2260: .getBranchId(), variant.getLanguageId(), conn,
2261: executeAfterCommit);
2262: conn.commit();
2263: executeRunnables(executeAfterCommit);
2264: return lockInfo;
2265: } catch (Exception e) {
2266: throw new RepositoryException("Error loading lock info.", e);
2267: } finally {
2268: jdbcHelper.closeConnection(conn);
2269: }
2270: }
2271:
2272: /**
2273: * Loads current lock info for a document variant. If the lock has meanwhile expired, it is
2274: * removed by this loading operation.
2275: *
2276: * <p>This removal however needs to be notified via an event, since otherwise the cache
2277: * will not get properly invalidated. However, this event should only be sent if and
2278: * especially after the connection has been committed (otherwise the cache gets invalidated,
2279: * and it might get refilled with old info before the commit). Therefore, the caller should
2280: * execute any Runnables added to the executeAfterCommit list after commit of the running transaction.
2281: * (Note: pay attention to return statements in the middle of methods).
2282: *
2283: * <p>To avoid that another thread modifies the lock information concurrently, access to
2284: * lock info should be serialized by taking a for-update lock on the document
2285: * variant record.
2286: */
2287: private LockInfoImpl loadLock(final DocId docId,
2288: final long branchId, final long languageId,
2289: Connection conn, List<Runnable> executeAfterCommit)
2290: throws SQLException {
2291: PreparedStatement stmt = null;
2292: try {
2293: stmt = conn
2294: .prepareStatement("select user_id, locktype, time_acquired, duration from locks where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
2295: stmt.setLong(1, docId.getSeqId());
2296: stmt.setLong(2, docId.getNsId());
2297: stmt.setLong(3, branchId);
2298: stmt.setLong(4, languageId);
2299: ResultSet rs = stmt.executeQuery();
2300: if (!rs.next())
2301: return new LockInfoImpl();
2302: long userId = rs.getLong("user_id");
2303: LockType locktype = LockType.getByCode(rs
2304: .getString("locktype"));
2305: Date timeAcquired = rs.getTimestamp("time_acquired");
2306: long duration = rs.getLong("duration");
2307: stmt.close();
2308:
2309: // check if the lock has meanwhile expired, and if so delete it
2310: if (duration != -1
2311: && timeAcquired.getTime() + duration < System
2312: .currentTimeMillis()) {
2313: if (logger.isDebugEnabled())
2314: logger
2315: .debug("Removing expired lock for user "
2316: + userId
2317: + " on "
2318: + getFormattedVariant(new VariantKey(
2319: docId.toString(), branchId,
2320: languageId)));
2321: // Note that we pass all lock information in the statement, not only the doc_id,
2322: // to be sure that we're deleting the record only in case nobody else has modified
2323: // it in the meantime
2324: stmt = conn
2325: .prepareStatement("delete from locks where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? and user_id = ? and locktype = ? and time_acquired = ? and duration = ?");
2326: stmt.setLong(1, docId.getSeqId());
2327: stmt.setLong(2, docId.getNsId());
2328: stmt.setLong(3, branchId);
2329: stmt.setLong(4, languageId);
2330: stmt.setLong(5, userId);
2331: stmt.setString(6, locktype.getCode());
2332: stmt.setTimestamp(7, new Timestamp(timeAcquired
2333: .getTime()));
2334: stmt.setLong(8, duration);
2335: stmt.execute();
2336: stmt.close();
2337:
2338: executeAfterCommit.add(new Runnable() {
2339: public void run() {
2340: // fire synchronous events
2341: context
2342: .getCommonRepository()
2343: .fireRepositoryEvent(
2344: RepositoryEventType.LOCK_CHANGE,
2345: new VariantKey(
2346: docId.toString(),
2347: branchId, languageId),
2348: -1);
2349: }
2350: });
2351: return new LockInfoImpl();
2352: }
2353: return new LockInfoImpl(userId, timeAcquired, duration,
2354: locktype);
2355: } finally {
2356: jdbcHelper.closeStatement(stmt);
2357: }
2358: }
2359:
2360: public LockInfoImpl releaseLock(DocumentVariantImpl variant)
2361: throws RepositoryException {
2362: DocumentVariantImpl.IntimateAccess variantInt = variant
2363: .getIntimateAccess(this );
2364: DocId docId = variantInt.getDocument().getIntimateAccess(this )
2365: .getDocId();
2366:
2367: // Check if user has write access
2368: AclResultInfo aclInfo = context.getCommonRepository()
2369: .getAccessManager().getAclInfoOnLive(systemUser,
2370: variantInt.getCurrentUser().getId(),
2371: variantInt.getCurrentUser().getActiveRoleIds(),
2372: docId, variant.getBranchId(),
2373: variant.getLanguageId());
2374: if (!aclInfo.isAllowed(AclPermission.WRITE))
2375: throw new AccessException("Write access denied for user "
2376: + variantInt.getCurrentUser().getId() + " to "
2377: + getFormattedVariant(variant.getKey()));
2378:
2379: List<Runnable> executeAfterCommit = new ArrayList<Runnable>(2);
2380: Connection conn = null;
2381: PreparedStatement stmt = null;
2382: try {
2383: conn = context.getDataSource().getConnection();
2384: jdbcHelper.startTransaction(conn);
2385:
2386: // lock the database record for the document variant in order to serialize lock access
2387: stmt = conn
2388: .prepareStatement("select doc_id from document_variants where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? "
2389: + jdbcHelper.getSharedLockClause());
2390: stmt.setLong(1, docId.getSeqId());
2391: stmt.setLong(2, docId.getNsId());
2392: stmt.setLong(3, variant.getBranchId());
2393: stmt.setLong(4, variant.getLanguageId());
2394: stmt.execute();
2395: stmt.close();
2396:
2397: LockInfoImpl lockInfo = loadLock(docId, variant
2398: .getBranchId(), variant.getLanguageId(), conn,
2399: executeAfterCommit);
2400: if (!lockInfo.hasLock())
2401: return lockInfo;
2402:
2403: if (lockInfo.getUserId() != variantInt.getCurrentUser()
2404: .getId())
2405: return lockInfo;
2406: else if (lockInfo.getUserId() != variantInt
2407: .getCurrentUser().getId()
2408: && !variantInt.getCurrentUser()
2409: .isInAdministratorRole())
2410: return lockInfo;
2411:
2412: stmt = conn
2413: .prepareStatement("delete from locks where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
2414: stmt.setLong(1, docId.getSeqId());
2415: stmt.setLong(2, docId.getNsId());
2416: stmt.setLong(3, variant.getBranchId());
2417: stmt.setLong(4, variant.getLanguageId());
2418: stmt.execute();
2419: conn.commit();
2420:
2421: // fire synchronous events
2422: context.getCommonRepository().fireRepositoryEvent(
2423: RepositoryEventType.LOCK_CHANGE,
2424: new VariantKey(variant.getDocumentId(), variant
2425: .getBranchId(), variant.getLanguageId()),
2426: -1);
2427:
2428: return new LockInfoImpl();
2429: } catch (Throwable e) {
2430: jdbcHelper.rollback(conn);
2431: executeAfterCommit.clear();
2432: throw new RepositoryException("Error removing lock.", e);
2433: } finally {
2434: jdbcHelper.closeStatement(stmt);
2435: jdbcHelper.closeConnection(conn);
2436: executeRunnables(executeAfterCommit);
2437: }
2438: }
2439:
2440: /**
2441: * Loads all the collections to which a specified document belongs.
2442: * @param variant the document variant we want to load the collections for
2443: * @return a java.util.Collection the document belongs to, null if no collections can be found
2444: * (which conceptually means the document only belongs to the <i>root collection</i>.
2445: */
2446:
2447: private Collection<DocumentCollectionImpl> loadCollections(
2448: DocumentVariantImpl variant, DocId docId, Connection conn)
2449: throws RepositoryException {
2450: PreparedStatement stmt = null;
2451: List<DocumentCollectionImpl> list = new ArrayList<DocumentCollectionImpl>();
2452: PreparedStatement substmt = null;
2453: boolean anotherRecordFound;
2454:
2455: try {
2456: DocumentVariantImpl.IntimateAccess variantInt = variant
2457: .getIntimateAccess(this );
2458:
2459: stmt = conn
2460: .prepareStatement("select collection_id from document_collections where document_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
2461: stmt.setLong(1, docId.getSeqId());
2462: stmt.setLong(2, docId.getNsId());
2463: stmt.setLong(3, variant.getBranchId());
2464: stmt.setLong(4, variant.getLanguageId());
2465: ResultSet rs = stmt.executeQuery();
2466:
2467: anotherRecordFound = rs.next();
2468: if (!anotherRecordFound) {
2469: return null;
2470: } else {
2471: CommonCollectionManager collectionManager = context
2472: .getCommonRepository().getCollectionManager();
2473: AuthenticatedUser user = variantInt.getCurrentUser();
2474: while (anotherRecordFound) {
2475: long collectionId = rs.getLong(1);
2476: try {
2477: list.add(collectionManager.getCollection(
2478: collectionId, false, user));
2479: } catch (CollectionNotFoundException e) {
2480: // ignore: this would mean the collection has been deleted since the transaction
2481: // that loads this document started
2482: }
2483: anotherRecordFound = rs.next();
2484: }
2485: }
2486:
2487: } catch (Throwable e) {
2488: logger.debug(e.getMessage());
2489: throw new RepositoryException("Error loading collections.",
2490: e);
2491: } finally {
2492: jdbcHelper.closeStatement(substmt);
2493: jdbcHelper.closeStatement(stmt);
2494: }
2495:
2496: return list;
2497: }
2498:
2499: private void addDocumentToCollections(
2500: DocumentCollectionImpl[] collArray, DocId docId,
2501: long branchId, long languageId, Connection conn)
2502: throws SQLException, RepositoryException {
2503: logger.debug("begin adding document to collection");
2504: PreparedStatement stmt = null;
2505:
2506: try {
2507: stmt = conn
2508: .prepareStatement("insert into document_collections(document_id, ns_id, branch_id, lang_id, collection_id) values (?,?,?,?,?)");
2509: for (DocumentCollectionImpl collToAddTo : collArray) {
2510: try {
2511: stmt.setLong(1, docId.getSeqId());
2512: stmt.setLong(2, docId.getNsId());
2513: stmt.setLong(3, branchId);
2514: stmt.setLong(4, languageId);
2515: stmt.setLong(5, collToAddTo.getId());
2516: stmt.executeUpdate();
2517: } catch (SQLException e) {
2518: /*
2519: * This exception can mean a couple of things.
2520: * First: it can be a genuine datastore exception.
2521: * if this is the case, we can rethrow it as such.
2522: *
2523: * Second: it is possible that an admin has removed
2524: * a collection and at the same time, someone was
2525: * editing a document. If this document belonged
2526: * to the removed collection, this will result in an
2527: * exception due to the constraint on the database.
2528: *
2529: * In this case we want to throw a specific exception.
2530: */
2531: try {
2532: context.getCommonRepository()
2533: .getCollectionManager().getCollection(
2534: collToAddTo.getId(), false,
2535: systemUser);
2536: } catch (RepositoryException e1) {
2537: if (e1 instanceof CollectionNotFoundException)
2538: throw new CollectionDeletedException(String
2539: .valueOf(collToAddTo.getId()));
2540: //we're not actually interested in e1, so we just rethrow the previous exception
2541: //because it was not caused by collection deletion.
2542: else
2543: throw e;
2544: }
2545: }
2546: }
2547: } finally {
2548: jdbcHelper.closeStatement(stmt);
2549: logger.debug("document addition to collection complete");
2550: }
2551: }
2552:
2553: private DocumentUpdatedDocument createDocumentUpdatedEvent(
2554: DocumentImpl oldDocument, DocumentImpl newDocument,
2555: Date lastModified, long newUpdateCount) {
2556: DocumentUpdatedDocument documentUpdatedDocument = DocumentUpdatedDocument.Factory
2557: .newInstance();
2558: DocumentUpdatedDocument.DocumentUpdated documentUpdated = documentUpdatedDocument
2559: .addNewDocumentUpdated();
2560: documentUpdated.addNewOldDocument().setDocument(
2561: oldDocument.getXmlWithoutVariant().getDocument());
2562:
2563: DocumentImpl.IntimateAccess newDocumentInt = newDocument
2564: .getIntimateAccess(this );
2565: DocumentDocument.Document newDocumentXml = newDocument
2566: .getXmlWithoutVariant().getDocument();
2567: updateUpdatedDocumentXml(newDocumentXml, lastModified,
2568: newDocumentInt.getCurrentUser().getId(), newUpdateCount);
2569: documentUpdated.addNewNewDocument().setDocument(newDocumentXml);
2570:
2571: return documentUpdatedDocument;
2572: }
2573:
2574: /**
2575: * Updates the document XML so as if it would look if we retrieved the XML of the document after saving it.
2576: */
2577: private void updateUpdatedDocumentXml(
2578: DocumentDocument.Document documentXml, Date lastModified,
2579: long lastModifier, long newUpdateCount) {
2580: documentXml.setUpdateCount(newUpdateCount);
2581: documentXml.setLastModified(getCalendar(lastModified));
2582: documentXml.setLastModifier(lastModifier);
2583: }
2584:
2585: private DocumentCreatedDocument createNewDocumentEvent(
2586: DocumentImpl document, DocId docId, Date lastModified,
2587: long newUpdateCount) {
2588: DocumentImpl.IntimateAccess documentInt = document
2589: .getIntimateAccess(this );
2590:
2591: DocumentCreatedDocument documentCreatedDocument = DocumentCreatedDocument.Factory
2592: .newInstance();
2593: DocumentCreatedDocument.DocumentCreated documentCreated = documentCreatedDocument
2594: .addNewDocumentCreated();
2595: DocumentCreatedDocument.DocumentCreated.NewDocument newDocument = documentCreated
2596: .addNewNewDocument();
2597:
2598: DocumentDocument.Document documentXml = document
2599: .getXmlWithoutVariant().getDocument();
2600: updateNewDocumentXml(documentXml, docId, lastModified,
2601: documentInt.getCurrentUser().getId(), newUpdateCount);
2602: newDocument.setDocument(documentXml);
2603:
2604: return documentCreatedDocument;
2605: }
2606:
2607: /**
2608: * Updates the document XML so as if it would look if we retrieved the XML of the document after saving it.
2609: */
2610: private void updateNewDocumentXml(
2611: DocumentDocument.Document documentXml, DocId docId,
2612: Date lastModified, long lastModifier, long newUpdateCount) {
2613: documentXml.setUpdateCount(newUpdateCount);
2614: documentXml.setLastModified(getCalendar(lastModified));
2615: documentXml.setLastModifier(lastModifier);
2616: documentXml.setId(docId.toString());
2617: documentXml.setCreated(getCalendar(lastModified));
2618: }
2619:
2620: private DocumentVariantUpdatedDocument createVariantUpdatedEvent(
2621: DocumentImpl oldDocument, DocumentImpl newDocument,
2622: Date lastModified, String summary, long lastVersionId,
2623: long newVariantUpdateCount, long newDocumentUpdateCount)
2624: throws RepositoryException {
2625: DocumentVariantUpdatedDocument variantUpdatedDocument = DocumentVariantUpdatedDocument.Factory
2626: .newInstance();
2627: DocumentVariantUpdatedDocument.DocumentVariantUpdated variantUpdated = variantUpdatedDocument
2628: .addNewDocumentVariantUpdated();
2629: variantUpdated.addNewOldDocumentVariant().setDocument(
2630: oldDocument.getXml().getDocument());
2631:
2632: DocumentImpl.IntimateAccess newDocumentInt = newDocument
2633: .getIntimateAccess(this );
2634: DocumentDocument.Document newDocumentXml = newDocument.getXml()
2635: .getDocument();
2636: updateUpdatedDocumentXml(newDocumentXml, lastModified,
2637: newDocumentInt.getCurrentUser().getId(),
2638: newDocumentUpdateCount);
2639: // update the XML so as if it would look if we retrieved the XML after the internal document object state
2640: // is already modified
2641: newDocumentXml.setVariantUpdateCount(newVariantUpdateCount);
2642: newDocumentXml
2643: .setVariantLastModified(getCalendar(lastModified));
2644: newDocumentXml.setVariantLastModifier(newDocumentInt
2645: .getCurrentUser().getId());
2646: newDocumentXml.setLastVersionId(lastVersionId);
2647: newDocumentXml.setSummary(summary);
2648: variantUpdated.addNewNewDocumentVariant().setDocument(
2649: newDocumentXml);
2650:
2651: return variantUpdatedDocument;
2652: }
2653:
2654: private DocumentVariantCreatedDocument createNewVariantEvent(
2655: DocumentImpl document, DocId docId, Date lastModified,
2656: String summary, long newVariantUpdateCount,
2657: long newDocumentUpdateCount) throws RepositoryException {
2658: DocumentImpl.IntimateAccess documentInt = document
2659: .getIntimateAccess(this );
2660:
2661: DocumentVariantCreatedDocument variantCreatedDocument = DocumentVariantCreatedDocument.Factory
2662: .newInstance();
2663: DocumentVariantCreatedDocument.DocumentVariantCreated variantCreated = variantCreatedDocument
2664: .addNewDocumentVariantCreated();
2665: DocumentVariantCreatedDocument.DocumentVariantCreated.NewDocumentVariant newVariant = variantCreated
2666: .addNewNewDocumentVariant();
2667:
2668: DocumentDocument.Document documentXml = document.getXml()
2669: .getDocument();
2670: updateNewDocumentXml(documentXml, docId, lastModified,
2671: documentInt.getCurrentUser().getId(),
2672: newDocumentUpdateCount);
2673: // update the XML so as if it would look if we retrieved the XML after the internal document object state
2674: // is already modified
2675: documentXml.setVariantUpdateCount(newVariantUpdateCount);
2676: documentXml.setVariantLastModified(getCalendar(lastModified));
2677: documentXml.setVariantLastModifier(documentInt.getCurrentUser()
2678: .getId());
2679: documentXml.setSummary(summary);
2680: newVariant.setDocument(documentXml);
2681:
2682: return variantCreatedDocument;
2683: }
2684:
2685: public void deleteDocument(DocId docId, AuthenticatedUser user)
2686: throws RepositoryException {
2687: PreparedStatement stmt = null;
2688: Connection conn = null;
2689: List<Runnable> executeAfterCommit = new ArrayList<Runnable>();
2690: Lock avoidSuspendLock = context.getBlobStore()
2691: .getAvoidSuspendLock();
2692: try {
2693: if (!avoidSuspendLock.tryLock(0, TimeUnit.MILLISECONDS))
2694: throw new RepositoryException(
2695: "The blobstore is currently protected for write access, it is impossible to delete documents right now. Try again later.");
2696: } catch (InterruptedException e) {
2697: throw new RuntimeException(e);
2698: }
2699: try {
2700: conn = context.getDataSource().getConnection();
2701: jdbcHelper.startTransaction(conn);
2702:
2703: // take a write lock on the document record
2704: stmt = conn
2705: .prepareStatement("select id from documents where id = ? and ns_id = ? "
2706: + jdbcHelper.getSharedLockClause());
2707: stmt.setLong(1, docId.getSeqId());
2708: stmt.setLong(2, docId.getNsId());
2709: ResultSet rs = stmt.executeQuery();
2710: if (!rs.next()) {
2711: throw new DocumentNotFoundException(docId.toString());
2712: }
2713: stmt.close();
2714:
2715: // for each variant, check the access permissions and the presence of an exclusive lock owned by a different user
2716: AvailableVariant[] availableVariants = getAvailableVariantsInTransaction(
2717: docId, user, conn, true);
2718: for (AvailableVariant availableVariant : availableVariants) {
2719: // check if the user is allowed to delete the document variant
2720: AclResultInfo aclInfo = context.getCommonRepository()
2721: .getAccessManager().getAclInfoOnLive(
2722: systemUser, user.getId(),
2723: user.getActiveRoleIds(), docId,
2724: availableVariant.getBranchId(),
2725: availableVariant.getLanguageId());
2726: if (!aclInfo.isAllowed(AclPermission.DELETE))
2727: throw new AccessException(
2728: "User "
2729: + user.getId()
2730: + " ("
2731: + user.getLogin()
2732: + ") is not allowed to delete document ID "
2733: + docId
2734: + ", since the user has no delete permission for "
2735: + getFormattedVariant(new VariantKey(
2736: docId.toString(),
2737: availableVariant
2738: .getBranchId(),
2739: availableVariant
2740: .getLanguageId())));
2741:
2742: // take a lock on the document variant record
2743: stmt = conn
2744: .prepareStatement("select doc_id from document_variants where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ? "
2745: + jdbcHelper.getSharedLockClause());
2746: stmt.setLong(1, docId.getSeqId());
2747: stmt.setLong(2, docId.getNsId());
2748: stmt.setLong(3, availableVariant.getBranchId());
2749: stmt.setLong(4, availableVariant.getLanguageId());
2750: stmt.execute();
2751: stmt.close();
2752:
2753: // check there's no lock on the document variant owned by another user
2754: LockInfo lockInfo = loadLock(docId, availableVariant
2755: .getBranchId(), availableVariant
2756: .getLanguageId(), conn, executeAfterCommit);
2757: if (lockInfo.hasLock()
2758: && lockInfo.getType() == LockType.PESSIMISTIC
2759: && lockInfo.getUserId() != user.getId()) {
2760: throw new RepositoryException(
2761: "Cannot delete document "
2762: + docId
2763: + " because someone else is holding a pessimistic lock on it: user "
2764: + lockInfo.getUserId()
2765: + " on branch "
2766: + getBranchLabel(availableVariant
2767: .getBranchId())
2768: + ", language "
2769: + getLanguageLabel(availableVariant
2770: .getLanguageId()));
2771: }
2772: }
2773:
2774: // load the old document (if any), needed to generate event later on
2775: // this is our last chance to do this
2776: DocumentImpl deletedDocument = loadDocumentInTransaction(
2777: user, docId, -1, -1, conn, executeAfterCommit);
2778:
2779: // delete all variants
2780: Set<String> blobIds = new HashSet<String>();
2781: for (AvailableVariant availableVariant : availableVariants) {
2782: deleteVariantInTransaction(docId, availableVariant
2783: .getBranchId(), availableVariant
2784: .getLanguageId(), blobIds, user, conn,
2785: executeAfterCommit);
2786: }
2787:
2788: // delete document itself
2789: stmt = conn
2790: .prepareStatement("delete from documents where id = ? and ns_id = ?");
2791: stmt.setLong(1, docId.getSeqId());
2792: stmt.setLong(2, docId.getNsId());
2793: stmt.execute();
2794: stmt.close();
2795:
2796: // make async event
2797: XmlObject eventDescription = createDocumentDeletedEvent(
2798: deletedDocument, user);
2799: eventHelper.createEvent(eventDescription,
2800: "DocumentDeleted", conn);
2801:
2802: // commit document deletion
2803: conn.commit();
2804:
2805: // after succesful commit of database transaction, start deleting blobs
2806: deleteBlobs(blobIds, conn);
2807: } catch (Throwable e) {
2808: jdbcHelper.rollback(conn);
2809: executeAfterCommit.clear();
2810: if (e instanceof AccessException)
2811: throw (AccessException) e;
2812: if (e instanceof DocumentNotFoundException)
2813: throw (DocumentNotFoundException) e;
2814:
2815: throw new RepositoryException("Error deleting document "
2816: + docId + ".", e);
2817: } finally {
2818: jdbcHelper.closeStatement(stmt);
2819: jdbcHelper.closeConnection(conn);
2820: avoidSuspendLock.unlock();
2821: executeRunnables(executeAfterCommit);
2822: }
2823:
2824: // do sync event
2825: context.getCommonRepository().fireRepositoryEvent(
2826: RepositoryEventType.DOCUMENT_DELETED, docId.toString(),
2827: -1);
2828: }
2829:
2830: private void deleteBlobs(Set<String> blobIds, Connection conn)
2831: throws SQLException {
2832: if (blobIds.size() > 0) {
2833: PreparedStatement stmt = null;
2834: try {
2835: stmt = conn
2836: .prepareStatement("select count(*) from parts where blob_id = ?");
2837: for (String blobId : blobIds) {
2838: stmt.setString(1, blobId);
2839: ResultSet rs = stmt.executeQuery();
2840: rs.next();
2841: if (rs.getLong(1) == 0) {
2842: try {
2843: context.getBlobStore().delete(blobId);
2844: } catch (Throwable e) {
2845: logger.error("Error deleting blob with id "
2846: + blobId, e);
2847: }
2848: }
2849: }
2850: } finally {
2851: jdbcHelper.closeStatement(stmt);
2852: }
2853: }
2854: }
2855:
2856: public void deleteVariant(DocId docId, long branchId,
2857: long languageId, AuthenticatedUser user)
2858: throws RepositoryException {
2859: List<Runnable> executeAfterCommit = new ArrayList<Runnable>(2);
2860: PreparedStatement stmt = null;
2861: Connection conn = null;
2862: Lock avoidSuspendLock = context.getBlobStore()
2863: .getAvoidSuspendLock();
2864: try {
2865: if (!avoidSuspendLock.tryLock(0, TimeUnit.MILLISECONDS))
2866: throw new RepositoryException(
2867: "The blobstore is currently protected for write access, it is impossible to delete document variants right now. Try again later.");
2868: } catch (InterruptedException e) {
2869: throw new RuntimeException(e);
2870: }
2871: try {
2872: conn = context.getDataSource().getConnection();
2873: jdbcHelper.startTransaction(conn);
2874:
2875: // take a write lock on the document record
2876: stmt = conn
2877: .prepareStatement("select id from documents where id = ? and ns_id = ? "
2878: + jdbcHelper.getSharedLockClause());
2879: stmt.setLong(1, docId.getSeqId());
2880: stmt.setLong(2, docId.getNsId());
2881: ResultSet rs = stmt.executeQuery();
2882: if (!rs.next()) {
2883: throw new DocumentNotFoundException(docId.toString());
2884: }
2885: stmt.close();
2886:
2887: // check if the user is allowed to delete the document variant
2888: AclResultInfo aclInfo = context.getCommonRepository()
2889: .getAccessManager().getAclInfoOnLive(systemUser,
2890: user.getId(), user.getActiveRoleIds(),
2891: docId, branchId, languageId);
2892: if (!aclInfo.isAllowed(AclPermission.DELETE))
2893: throw new AccessException("User "
2894: + user.getId()
2895: + " ("
2896: + user.getLogin()
2897: + ") is not allowed to delete the variant: "
2898: + getFormattedVariant(new VariantKey(docId
2899: .toString(), branchId, languageId)));
2900:
2901: // check there's still more then one variant
2902: stmt = conn
2903: .prepareStatement("select count(*) from document_variants where doc_id = ? and ns_id = ?");
2904: stmt.setLong(1, docId.getSeqId());
2905: stmt.setLong(2, docId.getNsId());
2906: rs = stmt.executeQuery();
2907: rs.next();
2908: long count = rs.getLong(1);
2909: if (count <= 1)
2910: throw new RepositoryException(
2911: "Deleting the last variant of a document is not possible.");
2912: stmt.close();
2913:
2914: Set<String> blobIds = new HashSet<String>();
2915: deleteVariantInTransaction(docId, branchId, languageId,
2916: blobIds, user, conn, executeAfterCommit);
2917:
2918: removeWeakVariantReferences(docId, branchId, languageId,
2919: conn);
2920:
2921: // commit document variant deletion
2922: conn.commit();
2923:
2924: // after succesful commit of database transaction, start deleting blobs
2925: deleteBlobs(blobIds, conn);
2926: } catch (Throwable e) {
2927: jdbcHelper.rollback(conn);
2928: executeAfterCommit.clear();
2929: if (e instanceof AccessException)
2930: throw (AccessException) e;
2931: if (e instanceof DocumentNotFoundException)
2932: throw (DocumentNotFoundException) e;
2933: if (e instanceof DocumentVariantNotFoundException)
2934: throw (DocumentVariantNotFoundException) e;
2935:
2936: throw new RepositoryException("Error deleting variant: "
2937: + getFormattedVariant(new VariantKey(docId
2938: .toString(), branchId, languageId)), e);
2939: } finally {
2940: jdbcHelper.closeStatement(stmt);
2941: jdbcHelper.closeConnection(conn);
2942: avoidSuspendLock.unlock();
2943: executeRunnables(executeAfterCommit);
2944: }
2945: }
2946:
2947: private void removeWeakVariantReferences(DocId docId,
2948: long branchId, long languageId, Connection conn)
2949: throws SQLException {
2950: // make sure document.reference_id and version.synced_with_... no longer point to this variant
2951: PreparedStatement stmt = null;
2952: try {
2953: // clear document_versions.synced_with fields
2954: stmt = conn
2955: .prepareStatement("update document_versions set synced_with_lang_id = null, synced_with_version_id = null, synced_with_search = null"
2956: + " where doc_id = ? and ns_id = ? and branch_id = ? and synced_with_lang_id = ?");
2957: stmt.setLong(1, docId.getSeqId());
2958: stmt.setLong(2, docId.getNsId());
2959: stmt.setLong(3, branchId);
2960: stmt.setLong(4, languageId);
2961: stmt.execute();
2962: stmt.close();
2963:
2964: } finally {
2965: jdbcHelper.closeStatement(stmt);
2966: }
2967: }
2968:
2969: private void executeRunnables(List<Runnable> runnables) {
2970: while (runnables.size() > 0) {
2971: Runnable runnable = runnables.remove(runnables.size() - 1);
2972: runnable.run();
2973: }
2974: }
2975:
2976: /**
2977: * Deletes a document variant, assumes the necessary pre-condition checks are already done
2978: * (ie user is allowed to delete it, there's no pessimistic lock by another user, it's not
2979: * the last variant of the document)
2980: */
2981: private void deleteVariantInTransaction(final DocId docId,
2982: final long branchId, final long languageId,
2983: Set<String> blobIds, AuthenticatedUser user,
2984: Connection conn, List<Runnable> executeAfterCommit)
2985: throws SQLException, RepositoryException {
2986: PreparedStatement stmt = null;
2987: try {
2988: DocumentImpl deletedDocument = loadDocumentInTransaction(
2989: user, docId, branchId, languageId, conn,
2990: executeAfterCommit);
2991:
2992: // delete variant
2993: stmt = conn
2994: .prepareStatement("delete from document_variants where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
2995: stmt.setLong(1, docId.getSeqId());
2996: stmt.setLong(2, docId.getNsId());
2997: stmt.setLong(3, branchId);
2998: stmt.setLong(4, languageId);
2999: stmt.execute();
3000: stmt.close();
3001:
3002: // delete lock
3003: stmt = conn
3004: .prepareStatement("delete from locks where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3005: stmt.setLong(1, docId.getSeqId());
3006: stmt.setLong(2, docId.getNsId());
3007: stmt.setLong(3, branchId);
3008: stmt.setLong(4, languageId);
3009: stmt.execute();
3010: stmt.close();
3011:
3012: // delete summary
3013: stmt = conn
3014: .prepareStatement("delete from summaries where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3015: stmt.setLong(1, docId.getSeqId());
3016: stmt.setLong(2, docId.getNsId());
3017: stmt.setLong(3, branchId);
3018: stmt.setLong(4, languageId);
3019: stmt.execute();
3020: stmt.close();
3021:
3022: // delete collection membership
3023: stmt = conn
3024: .prepareStatement("delete from document_collections where document_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3025: stmt.setLong(1, docId.getSeqId());
3026: stmt.setLong(2, docId.getNsId());
3027: stmt.setLong(3, branchId);
3028: stmt.setLong(4, languageId);
3029: stmt.execute();
3030: stmt.close();
3031:
3032: // delete custom fields
3033: stmt = conn
3034: .prepareStatement("delete from customfields where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3035: stmt.setLong(1, docId.getSeqId());
3036: stmt.setLong(2, docId.getNsId());
3037: stmt.setLong(3, branchId);
3038: stmt.setLong(4, languageId);
3039: stmt.execute();
3040: stmt.close();
3041:
3042: // delete extracted links
3043: stmt = conn
3044: .prepareStatement("delete from extracted_links where source_doc_id = ? and source_ns_id = ? and source_branch_id = ? and source_lang_id = ?");
3045: stmt.setLong(1, docId.getSeqId());
3046: stmt.setLong(2, docId.getNsId());
3047: stmt.setLong(3, branchId);
3048: stmt.setLong(4, languageId);
3049: stmt.execute();
3050: stmt.close();
3051:
3052: // delete comments
3053: stmt = conn
3054: .prepareStatement("delete from comments where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3055: stmt.setLong(1, docId.getSeqId());
3056: stmt.setLong(2, docId.getNsId());
3057: stmt.setLong(3, branchId);
3058: stmt.setLong(4, languageId);
3059: stmt.execute();
3060: stmt.close();
3061:
3062: // read all blob ids before deleting the parts
3063: stmt = conn
3064: .prepareStatement("select distinct(blob_id) from parts where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3065: stmt.setLong(1, docId.getSeqId());
3066: stmt.setLong(2, docId.getNsId());
3067: stmt.setLong(3, branchId);
3068: stmt.setLong(4, languageId);
3069: ResultSet rs = stmt.executeQuery();
3070: while (rs.next())
3071: blobIds.add(rs.getString(1));
3072: stmt.close();
3073:
3074: // delete parts (of all versions)
3075: stmt = conn
3076: .prepareStatement("delete from parts where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3077: stmt.setLong(1, docId.getSeqId());
3078: stmt.setLong(2, docId.getNsId());
3079: stmt.setLong(3, branchId);
3080: stmt.setLong(4, languageId);
3081: stmt.execute();
3082: stmt.close();
3083:
3084: // delete fields (of all versions)
3085: stmt = conn
3086: .prepareStatement("delete from thefields where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3087: stmt.setLong(1, docId.getSeqId());
3088: stmt.setLong(2, docId.getNsId());
3089: stmt.setLong(3, branchId);
3090: stmt.setLong(4, languageId);
3091: stmt.execute();
3092: stmt.close();
3093:
3094: // delete links (of all versions)
3095: stmt = conn
3096: .prepareStatement("delete from links where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3097: stmt.setLong(1, docId.getSeqId());
3098: stmt.setLong(2, docId.getNsId());
3099: stmt.setLong(3, branchId);
3100: stmt.setLong(4, languageId);
3101: stmt.execute();
3102: stmt.close();
3103:
3104: // delete versions
3105: stmt = conn
3106: .prepareStatement("delete from document_versions where doc_id = ? and ns_id = ? and branch_id = ? and lang_id = ?");
3107: stmt.setLong(1, docId.getSeqId());
3108: stmt.setLong(2, docId.getNsId());
3109: stmt.setLong(3, branchId);
3110: stmt.setLong(4, languageId);
3111: stmt.execute();
3112: stmt.close();
3113:
3114: XmlObject eventDescription = createVariantDeletedEvent(
3115: deletedDocument, user);
3116: eventHelper.createEvent(eventDescription,
3117: "DocumentVariantDeleted", conn);
3118:
3119: executeAfterCommit.add(new Runnable() {
3120: public void run() {
3121: context
3122: .getCommonRepository()
3123: .fireRepositoryEvent(
3124: RepositoryEventType.DOCUMENT_VARIANT_DELETED,
3125: new VariantKey(docId.toString(),
3126: branchId, languageId), -1);
3127: }
3128: });
3129:
3130: } finally {
3131: jdbcHelper.closeStatement(stmt);
3132: }
3133: }
3134:
3135: private DocumentVariantDeletedDocument createVariantDeletedEvent(
3136: DocumentImpl document, AuthenticatedUser user)
3137: throws RepositoryException {
3138: DocumentVariantDeletedDocument variantDeletedDocument = DocumentVariantDeletedDocument.Factory
3139: .newInstance();
3140: DocumentVariantDeletedDocument.DocumentVariantDeleted variantDeleted = variantDeletedDocument
3141: .addNewDocumentVariantDeleted();
3142: variantDeleted.addNewDeletedDocumentVariant().setDocument(
3143: document.getXml().getDocument());
3144: variantDeleted.setDeletedTime(new GregorianCalendar());
3145: variantDeleted.setDeleterId(user.getId());
3146: return variantDeletedDocument;
3147: }
3148:
3149: private DocumentDeletedDocument createDocumentDeletedEvent(
3150: DocumentImpl document, AuthenticatedUser user) {
3151: DocumentDeletedDocument documentDeletedDocument = DocumentDeletedDocument.Factory
3152: .newInstance();
3153: DocumentDeletedDocument.DocumentDeleted documentDeleted = documentDeletedDocument
3154: .addNewDocumentDeleted();
3155: documentDeleted.addNewDeletedDocument().setDocument(
3156: document.getXmlWithoutVariant().getDocument());
3157: documentDeleted.setDeletedTime(new GregorianCalendar());
3158: documentDeleted.setDeleterId(user.getId());
3159: return documentDeletedDocument;
3160: }
3161:
3162: public AvailableVariantImpl[] getAvailableVariants(DocId docId,
3163: AuthenticatedUser user) throws RepositoryException {
3164: Connection conn = null;
3165: try {
3166: conn = context.getDataSource().getConnection();
3167: return getAvailableVariantsInTransaction(docId, user, conn,
3168: false);
3169: } catch (DocumentNotFoundException e) {
3170: // pass this one through
3171: throw e;
3172: } catch (Throwable e) {
3173: throw new RepositoryException(
3174: "Error getting variants of document " + docId + ".",
3175: e);
3176: } finally {
3177: jdbcHelper.closeConnection(conn);
3178: }
3179: }
3180:
3181: public AvailableVariantImpl[] getAvailableVariantsInTransaction(
3182: DocId docId, AuthenticatedUser user, Connection conn,
3183: boolean takeLock) throws RepositoryException, SQLException {
3184: PreparedStatement stmt = null;
3185: try {
3186: stmt = conn
3187: .prepareStatement("select branch_id, lang_id, retired, liveversion_id, lastversion_id from document_variants where doc_id = ? and ns_id = ? "
3188: + (takeLock ? jdbcHelper
3189: .getSharedLockClause() : ""));
3190: stmt.setLong(1, docId.getSeqId());
3191: stmt.setLong(2, docId.getNsId());
3192: ResultSet rs = stmt.executeQuery();
3193:
3194: if (!rs.next())
3195: throw new DocumentNotFoundException(docId.toString());
3196:
3197: List<AvailableVariantImpl> availableVariants = new ArrayList<AvailableVariantImpl>();
3198: do {
3199: long branchId = rs.getLong("branch_id");
3200: long languageId = rs.getLong("lang_id");
3201: boolean retired = rs.getBoolean("retired");
3202: long liveVersionId = rs.getLong("liveversion_id");
3203: long lastVersionId = rs.getLong("lastversion_id");
3204:
3205: AvailableVariantImpl availableVariant = new AvailableVariantImpl(
3206: branchId, languageId, retired, liveVersionId,
3207: lastVersionId, context.getCommonRepository()
3208: .getVariantManager(), user);
3209: availableVariants.add(availableVariant);
3210: } while (rs.next());
3211:
3212: return availableVariants
3213: .toArray(new AvailableVariantImpl[availableVariants
3214: .size()]);
3215: } finally {
3216: jdbcHelper.closeStatement(stmt);
3217: }
3218: }
3219:
3220: public Document createVariant(DocId docId, long startBranchId,
3221: long startLanguageId, long startVersionId,
3222: long newBranchId, long newLanguageId, AuthenticatedUser user)
3223: throws RepositoryException {
3224: try {
3225: Document startDocument = load(docId, startBranchId,
3226: startLanguageId, user);
3227: VersionImpl startVersion;
3228: if (startVersionId == -1) {
3229: startVersion = (VersionImpl) startDocument
3230: .getLastVersion();
3231: } else if (startVersionId == -2) {
3232: startVersion = (VersionImpl) startDocument
3233: .getLiveVersion();
3234: if (startVersion == null)
3235: throw new RepositoryException(
3236: "Requested to create a variant starting from the live version, but the document does not have a live version. Document: "
3237: + getFormattedVariant(new VariantKey(
3238: docId.toString(),
3239: startBranchId,
3240: startLanguageId)));
3241: } else {
3242: startVersion = (VersionImpl) startDocument
3243: .getVersion(startVersionId);
3244: }
3245:
3246: DocumentImpl newDocument = new DocumentImpl(this , context
3247: .getCommonRepository(), user, startDocument
3248: .getDocumentTypeId(), newBranchId, newLanguageId);
3249: DocumentImpl.IntimateAccess newDocumentInt = newDocument
3250: .getIntimateAccess(this );
3251: DocumentVariantImpl newVariant = newDocumentInt
3252: .getVariant();
3253: DocumentVariantImpl.IntimateAccess newVariantInt = newVariant
3254: .getIntimateAccess(this );
3255:
3256: newVariantInt.setCreatedFrom(startBranchId,
3257: startLanguageId, startVersion.getId());
3258:
3259: newDocumentInt.load(docId, startDocument.getLastModified(),
3260: startDocument.getLastModifier(), startDocument
3261: .getCreated(), startDocument.getOwner(),
3262: startDocument.isPrivate(), startDocument
3263: .getUpdateCount(), startDocument
3264: .getReferenceLanguageId());
3265:
3266: newDocument.setRetired(startDocument.isRetired());
3267:
3268: // copy document name
3269: newDocument.setName(startDocument.getName());
3270:
3271: // copy parts -- efficiently, thus reusing existing blob key
3272: PartImpl[] parts = startVersion.getIntimateAccess(this )
3273: .getPartImpls();
3274: for (PartImpl part : parts) {
3275: newVariantInt.addPart(part.getIntimateAccess(this )
3276: .internalDuplicate(newVariantInt));
3277: }
3278:
3279: // copy fields
3280: Field[] fields = startVersion.getFields().getArray();
3281: for (Field field : fields) {
3282: newDocument.setField(field.getTypeId(), field
3283: .getValue());
3284: }
3285:
3286: // copy links
3287: Link[] links = startVersion.getLinks().getArray();
3288: for (Link link : links) {
3289: newDocument.addLink(link.getTitle(), link.getTarget());
3290: }
3291:
3292: newDocument.setNewVersionState(startVersion.getState());
3293:
3294: // copy custom fields
3295: Map<String, String> customFields = startDocument
3296: .getCustomFields();
3297: for (Map.Entry<String, String> customField : customFields
3298: .entrySet()) {
3299: String name = customField.getKey();
3300: String value = customField.getValue();
3301: newDocument.setCustomField(name, value);
3302: }
3303:
3304: // copy collections
3305: DocumentCollection[] collections = startDocument
3306: .getCollections().getArray();
3307: for (DocumentCollection collection : collections) {
3308: newDocument.addToCollection(collection);
3309: }
3310:
3311: newDocument.save(false);
3312:
3313: return newDocument;
3314: } catch (Exception e) {
3315: throw new RepositoryException(
3316: "Error while creating new variant based on existing variant.",
3317: e);
3318: }
3319: }
3320: }
|