use of com.xpn.xwiki.doc.XWikiDocument.XWikiAttachmentToRemove in project xwiki-platform by xwiki.
the class XWikiHibernateStore method saveXWikiDoc.
@Override
public void saveXWikiDoc(XWikiDocument doc, XWikiContext inputxcontext, boolean bTransaction) throws XWikiException {
XWikiContext context = getExecutionXContext(inputxcontext, true);
try {
MonitorPlugin monitor = Util.getMonitorPlugin(context);
try {
// Start monitoring timer
if (monitor != null) {
monitor.startTimer(HINT);
}
doc.setStore(this);
// Make sure the database name is stored
doc.setDatabase(context.getWikiId());
// If the comment is larger than the max size supported by the Storage, then abbreviate it
String comment = doc.getComment();
if (comment != null && comment.length() > 1023) {
doc.setComment(StringUtils.abbreviate(comment, 1023));
}
if (bTransaction) {
checkHibernate(context);
SessionFactory sfactory = injectCustomMappingsInSessionFactory(doc, context);
bTransaction = beginTransaction(sfactory, context);
}
Session session = getSession(context);
session.setFlushMode(FlushMode.COMMIT);
// These informations will allow to not look for attachments and objects on loading
doc.setElement(XWikiDocument.HAS_ATTACHMENTS, !doc.getAttachmentList().isEmpty());
doc.setElement(XWikiDocument.HAS_OBJECTS, !doc.getXObjects().isEmpty());
// Let's update the class XML since this is the new way to store it
// TODO If all the properties are removed, the old xml stays?
BaseClass bclass = doc.getXClass();
if (bclass != null) {
if (bclass.getFieldList().isEmpty()) {
doc.setXClassXML("");
} else {
// Don't format the XML to reduce the size of the stored data as much as possible
doc.setXClassXML(bclass.toXMLString(false));
}
bclass.setDirty(false);
}
if (doc.hasElement(XWikiDocument.HAS_ATTACHMENTS)) {
saveAttachmentList(doc, context);
}
// Remove attachments planned for removal
if (!doc.getAttachmentsToRemove().isEmpty()) {
for (XWikiAttachmentToRemove attachmentToRemove : doc.getAttachmentsToRemove()) {
XWikiAttachment attachment = attachmentToRemove.getAttachment();
XWikiAttachmentStoreInterface store = getXWikiAttachmentStoreInterface(attachment);
store.deleteXWikiAttachment(attachment, false, context, false);
}
doc.clearAttachmentsToRemove();
}
// Handle the latest text file
if (doc.isContentDirty() || doc.isMetaDataDirty()) {
Date ndate = new Date();
doc.setDate(ndate);
if (doc.isContentDirty()) {
doc.setContentUpdateDate(ndate);
doc.setContentAuthorReference(doc.getAuthorReference());
}
doc.incrementVersion();
if (context.getWiki().hasVersioning(context)) {
context.getWiki().getVersioningStore().updateXWikiDocArchive(doc, false, context);
}
doc.setContentDirty(false);
doc.setMetaDataDirty(false);
} else {
if (doc.getDocumentArchive() != null) {
// This is especially needed if we load a document from XML
if (context.getWiki().hasVersioning(context)) {
context.getWiki().getVersioningStore().saveXWikiDocArchive(doc.getDocumentArchive(), false, context);
// If the version does not exist it means it's a new version so add it to the history
if (!containsVersion(doc, doc.getRCSVersion(), context)) {
context.getWiki().getVersioningStore().updateXWikiDocArchive(doc, false, context);
}
}
} else {
// with a valid context
try {
if (context.getWiki().hasVersioning(context)) {
doc.getDocumentArchive(context);
// history
if (!containsVersion(doc, doc.getRCSVersion(), context)) {
context.getWiki().getVersioningStore().updateXWikiDocArchive(doc, false, context);
}
}
} catch (XWikiException e) {
// this is a non critical error
}
}
}
// Verify if the document already exists
Query query = session.createQuery("select xwikidoc.id from XWikiDocument as xwikidoc where xwikidoc.id = :id");
query.setLong("id", doc.getId());
if (query.uniqueResult() == null) {
if (doc.isContentDirty() || doc.isMetaDataDirty()) {
// Reset the creationDate to reflect the date of the first save, not the date of the object
// creation
doc.setCreationDate(new Date());
}
session.save(doc);
} else {
session.update(doc);
// TODO: this is slower!! How can it be improved?
// session.saveOrUpdate(doc);
}
// Remove objects planned for removal
if (doc.getXObjectsToRemove().size() > 0) {
for (BaseObject removedObject : doc.getXObjectsToRemove()) {
deleteXWikiCollection(removedObject, context, false, false);
}
doc.setXObjectsToRemove(new ArrayList<BaseObject>());
}
if (bclass != null) {
bclass.setDocumentReference(doc.getDocumentReference());
// Store this XWikiClass in the context so that we can use it in case of recursive usage of classes
context.addBaseClass(bclass);
}
if (doc.hasElement(XWikiDocument.HAS_OBJECTS)) {
// TODO: Delete all objects for which we don't have a name in the Map
for (List<BaseObject> objects : doc.getXObjects().values()) {
for (BaseObject obj : objects) {
if (obj != null) {
obj.setDocumentReference(doc.getDocumentReference());
/* If the object doesn't have a GUID, create it before saving */
if (StringUtils.isEmpty(obj.getGuid())) {
obj.setGuid(null);
}
saveXWikiCollection(obj, context, false);
}
}
}
}
if (context.getWiki().hasBacklinks(context)) {
try {
saveLinks(doc, context, true);
} catch (Exception e) {
this.logger.error("Failed to save links for document [{}]", doc.getDocumentReferenceWithLocale(), e);
}
}
// Update space table
updateXWikiSpaceTable(doc, session);
if (bTransaction) {
endTransaction(context, true);
}
doc.setNew(false);
// We need to ensure that the saved document becomes the original document
doc.setOriginalDocument(doc.clone());
} catch (Exception e) {
Object[] args = { this.defaultEntityReferenceSerializer.serialize(doc.getDocumentReference()) };
throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_DOC, "Exception while saving document {0}", e, args);
} finally {
try {
if (bTransaction) {
endTransaction(context, false);
}
} catch (Exception e) {
}
// End monitoring timer
if (monitor != null) {
monitor.endTimer(HINT);
}
}
} finally {
restoreExecutionXContext();
}
}
use of com.xpn.xwiki.doc.XWikiDocument.XWikiAttachmentToRemove in project xwiki-platform by xwiki.
the class XWiki method rollback.
public XWikiDocument rollback(final XWikiDocument tdoc, String rev, XWikiContext context) throws XWikiException {
LOGGER.debug("Rolling back [{}] to version [{}]", tdoc, rev);
// Let's clone rolledbackDoc since we might modify it
XWikiDocument rolledbackDoc = getDocument(tdoc, rev, context).clone();
if ("1".equals(getConfiguration().getProperty("xwiki.store.rollbackattachmentwithdocuments", "1"))) {
// Attachment handling strategy:
// - Two lists: Old Attachments, Current Attachments
// Goals:
// 1. Attachments that are only in OA must be restored from the trash
// 2. Attachments that are only in CA must be sent to the trash
// 3. Attachments that are in both lists should be reverted to the right version
// 4. Gotcha: deleted and re-uploaded attachments should be both trashed and restored.
// Plan:
// - Construct two lists: to restore, to revert
// - Iterate over OA.
// -- If the attachment is not in CA, add it to the restore list
// -- If it is in CA, but the date of the first version of the current attachment is after the date of the
// restored document version, add it the restore & move the current attachment to the recycle bin
// -- Otherwise, add it to the revert list
// - Iterate over CA
// -- If the attachment is not in OA, delete it
List<XWikiAttachment> oldAttachments = rolledbackDoc.getAttachmentList();
List<XWikiAttachment> currentAttachments = tdoc.getAttachmentList();
List<XWikiAttachment> toRestore = new ArrayList<>();
List<XWikiAttachment> toRevert = new ArrayList<>();
// First step, determine what to do with each attachment
LOGGER.debug("Checking attachments");
for (XWikiAttachment oldAttachment : oldAttachments) {
String filename = oldAttachment.getFilename();
XWikiAttachment equivalentAttachment = tdoc.getAttachment(filename);
if (equivalentAttachment == null) {
// Deleted attachment
LOGGER.debug("Deleted attachment: [{}]", filename);
toRestore.add(oldAttachment);
continue;
}
XWikiAttachment equivalentAttachmentRevision = equivalentAttachment.getAttachmentRevision(oldAttachment.getVersion(), context);
// because the nanoseconds component of the passed date is unknown.
if (equivalentAttachmentRevision == null || equivalentAttachmentRevision.getDate().getTime() != oldAttachment.getDate().getTime()) {
// Recreated attachment
LOGGER.debug("Recreated attachment: [{}]", filename);
// If the attachment trash is not available, don't lose the existing attachment
if (getAttachmentRecycleBinStore() != null) {
getAttachmentRecycleBinStore().saveToRecycleBin(equivalentAttachment, context.getUser(), new Date(), context, true);
toRestore.add(oldAttachment);
}
continue;
}
if (!StringUtils.equals(oldAttachment.getVersion(), equivalentAttachment.getVersion())) {
// Updated attachment
LOGGER.debug("Updated attachment: [{}]", filename);
toRevert.add(equivalentAttachment);
}
}
for (XWikiAttachment attachment : currentAttachments) {
if (rolledbackDoc.getAttachment(attachment.getFilename()) == null) {
LOGGER.debug("New attachment: " + attachment.getFilename());
// XWikiDocument#save() is actually the only way to delete an attachment cleanly
rolledbackDoc.getAttachmentsToRemove().add(new XWikiAttachmentToRemove(attachment, true));
}
}
// Revert updated attachments to the old version
for (XWikiAttachment attachmentToRevert : toRevert) {
String oldAttachmentVersion = rolledbackDoc.getAttachment(attachmentToRevert.getFilename()).getVersion();
XWikiAttachment oldAttachmentRevision = attachmentToRevert.getAttachmentRevision(oldAttachmentVersion, context);
if (oldAttachmentRevision == null) {
// Previous version is lost, just leave the current version in place
rolledbackDoc.setAttachment(attachmentToRevert);
continue;
}
// We can't just leave the old version in place, since it will break the revision history, given the
// current implementation, so we set the attachment version to the most recent version, mark the content
// as dirty, and the storage will automatically bump up the version number.
// This is a hack, to be fixed once the storage doesn't take care of updating the history and version,
// and once the current attachment version can point to an existing version from the history.
oldAttachmentRevision.setVersion(attachmentToRevert.getVersion());
oldAttachmentRevision.setMetaDataDirty(true);
oldAttachmentRevision.getAttachment_content().setContentDirty(true);
rolledbackDoc.setAttachment(oldAttachmentRevision);
}
// Restore deleted attachments from the trash
if (getAttachmentRecycleBinStore() != null) {
for (XWikiAttachment attachmentToRestore : toRestore) {
// There might be multiple versions of the attachment in the trash, search for the right one
List<DeletedAttachment> deletedVariants = getAttachmentRecycleBinStore().getAllDeletedAttachments(attachmentToRestore, context, true);
DeletedAttachment correctVariant = null;
for (DeletedAttachment variant : deletedVariants) {
// Reverse chronological order
if (variant.getDate().before(rolledbackDoc.getDate())) {
break;
}
correctVariant = variant;
}
if (correctVariant == null) {
// Not found in the trash, nothing left to do
continue;
}
XWikiAttachment restoredAttachment = correctVariant.restoreAttachment();
XWikiAttachment restoredAttachmentRevision = restoredAttachment.getAttachmentRevision(attachmentToRestore.getVersion(), context);
if (restoredAttachmentRevision != null) {
restoredAttachmentRevision.setAttachment_archive(restoredAttachment.getAttachment_archive());
restoredAttachmentRevision.getAttachment_archive().setAttachment(restoredAttachmentRevision);
restoredAttachmentRevision.setVersion(restoredAttachment.getVersion());
restoredAttachmentRevision.setMetaDataDirty(true);
restoredAttachmentRevision.getAttachment_content().setContentDirty(true);
rolledbackDoc.setAttachment(restoredAttachmentRevision);
} else {
// This particular version is lost, update to the one available
rolledbackDoc.setAttachment(restoredAttachment);
}
}
} else {
// No trash, can't restore. Remove the attachment references, so that the document is not broken
for (XWikiAttachment attachmentToRestore : toRestore) {
rolledbackDoc.getAttachmentList().remove(attachmentToRestore);
}
}
}
// Special treatment for deleted objects
rolledbackDoc.addXObjectsToRemoveFromVersion(tdoc);
// now we save the final document..
rolledbackDoc.setOriginalDocument(tdoc);
rolledbackDoc.setAuthorReference(context.getUserReference());
rolledbackDoc.setRCSVersion(tdoc.getRCSVersion());
rolledbackDoc.setVersion(tdoc.getVersion());
rolledbackDoc.setContentDirty(true);
ObservationManager om = getObservationManager();
if (om != null) {
// Notify listeners about the document that is going to be rolled back.
// Note that for the moment the event being send is a bridge event, as we are still passing around
// an XWikiDocument as source and an XWikiContext as data.
om.notify(new DocumentRollingBackEvent(rolledbackDoc.getDocumentReference(), rev), rolledbackDoc, context);
}
saveDocument(rolledbackDoc, localizePlainOrKey("core.comment.rollback", rev), context);
// Since the the store resets the original document, we need to temporarily put it back to send notifications.
XWikiDocument newOriginalDocument = rolledbackDoc.getOriginalDocument();
rolledbackDoc.setOriginalDocument(tdoc);
try {
if (om != null) {
// Notify listeners about the document that was rolled back.
// Note that for the moment the event being send is a bridge event, as we are still passing around an
// XWikiDocument as source and an XWikiContext as data.
om.notify(new DocumentRolledBackEvent(rolledbackDoc.getDocumentReference(), rev), rolledbackDoc, context);
}
} finally {
rolledbackDoc.setOriginalDocument(newOriginalDocument);
}
return rolledbackDoc;
}
use of com.xpn.xwiki.doc.XWikiDocument.XWikiAttachmentToRemove in project xwiki-platform by xwiki.
the class XWiki method saveDocument.
/**
* @param document the document to save
* @param comment the comment to associated to the new version of the saved document
* @param isMinorEdit true if the new version is a minor version
* @param context see {@link XWikiContext}
*/
public void saveDocument(XWikiDocument document, String comment, boolean isMinorEdit, XWikiContext context) throws XWikiException {
String currentWiki = context.getWikiId();
try {
// Switch to document wiki
context.setWikiId(document.getDocumentReference().getWikiReference().getName());
// Setting comment & minor edit before saving
document.setComment(StringUtils.defaultString(comment));
document.setMinorEdit(isMinorEdit);
// We need to save the original document since saveXWikiDoc() will reset it and we
// need that original document for the notification below.
XWikiDocument originalDocument = document.getOriginalDocument();
// (which is not a good practice)
if (originalDocument == null) {
originalDocument = getDocument(new DocumentReference(document.getDocumentReference(), document.getLocale()), context);
document.setOriginalDocument(originalDocument);
}
ObservationManager om = getObservationManager();
if (om != null) {
CancelableEvent documentEvent;
if (originalDocument.isNew()) {
documentEvent = new DocumentCreatingEvent(document.getDocumentReference());
} else {
documentEvent = new DocumentUpdatingEvent(document.getDocumentReference());
}
om.notify(documentEvent, document, context);
// If the action has been canceled by the user then don't perform any save and throw an exception
if (documentEvent.isCanceled()) {
throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_DOC, String.format("An Event Listener has cancelled the document save for [%s]. Reason: [%s]", document.getDocumentReference(), documentEvent.getReason()));
}
}
// Put attachments to remove in recycle bin
if (hasAttachmentRecycleBin(context)) {
for (XWikiAttachmentToRemove attachment : document.getAttachmentsToRemove()) {
if (attachment.isToRecycleBin()) {
getAttachmentRecycleBinStore().saveToRecycleBin(attachment.getAttachment(), context.getUser(), new Date(), context, true);
}
}
}
// Actually save the document.
getStore().saveXWikiDoc(document, context);
// Since the store#saveXWikiDoc resets originalDocument, we need to temporarily put it
// back to send notifications.
XWikiDocument newOriginal = document.getOriginalDocument();
try {
document.setOriginalDocument(originalDocument);
if (om != null) {
if (originalDocument.isNew()) {
om.notify(new DocumentCreatedEvent(document.getDocumentReference()), document, context);
} else {
om.notify(new DocumentUpdatedEvent(document.getDocumentReference()), document, context);
}
}
} catch (Exception ex) {
LOGGER.error("Failed to send document save notification for document [" + getDefaultEntityReferenceSerializer().serialize(document.getDocumentReference()) + "]", ex);
} finally {
document.setOriginalDocument(newOriginal);
}
} finally {
context.setWikiId(currentWiki);
}
}
Aggregations