Source Code Cross Referenced for LocalDocumentStrategy.java in  » Content-Management-System » daisy » org » outerj » daisy » repository » serverimpl » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » Content Management System » daisy » org.outerj.daisy.repository.serverimpl 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


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:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.