1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package com.xpn.xwiki.store

File XWikiHibernateStore.java

 

Coverage histogram

../../../../img/srcFileCovDistChart7.png
66% of files have more coverage

Code metrics

544
1,307
110
1
3,232
2,479
480
0.37
11.88
110
4.36

Classes

Class Line # Actions
XWikiHibernateStore 118 1,307 0% 480 615
0.686384568.6%
 

Contributing tests

This file is covered by 9 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.store;
21   
22    import java.io.Serializable;
23    import java.lang.reflect.Field;
24    import java.sql.Connection;
25    import java.sql.SQLException;
26    import java.sql.Statement;
27    import java.util.ArrayList;
28    import java.util.Collection;
29    import java.util.Collections;
30    import java.util.Date;
31    import java.util.HashMap;
32    import java.util.HashSet;
33    import java.util.Iterator;
34    import java.util.LinkedHashSet;
35    import java.util.List;
36    import java.util.Locale;
37    import java.util.Map;
38    import java.util.Set;
39    import java.util.StringTokenizer;
40   
41    import javax.inject.Inject;
42    import javax.inject.Named;
43    import javax.inject.Provider;
44    import javax.inject.Singleton;
45   
46    import org.apache.commons.lang3.ArrayUtils;
47    import org.apache.commons.lang3.StringUtils;
48    import org.hibernate.EntityMode;
49    import org.hibernate.FlushMode;
50    import org.hibernate.HibernateException;
51    import org.hibernate.ObjectNotFoundException;
52    import org.hibernate.Query;
53    import org.hibernate.Session;
54    import org.hibernate.SessionFactory;
55    import org.hibernate.cfg.Configuration;
56    import org.hibernate.cfg.Settings;
57    import org.hibernate.connection.ConnectionProvider;
58    import org.hibernate.impl.SessionFactoryImpl;
59    import org.hibernate.mapping.PersistentClass;
60    import org.hibernate.mapping.Property;
61    import org.slf4j.Logger;
62    import org.suigeneris.jrcs.rcs.Version;
63    import org.xwiki.bridge.event.ActionExecutingEvent;
64    import org.xwiki.component.annotation.Component;
65    import org.xwiki.component.manager.ComponentLookupException;
66    import org.xwiki.component.manager.ComponentManager;
67    import org.xwiki.component.phase.InitializationException;
68    import org.xwiki.model.EntityType;
69    import org.xwiki.model.reference.DocumentReference;
70    import org.xwiki.model.reference.DocumentReferenceResolver;
71    import org.xwiki.model.reference.EntityReference;
72    import org.xwiki.model.reference.EntityReferenceSerializer;
73    import org.xwiki.model.reference.SpaceReference;
74    import org.xwiki.model.reference.WikiReference;
75    import org.xwiki.observation.EventListener;
76    import org.xwiki.observation.ObservationManager;
77    import org.xwiki.observation.event.Event;
78    import org.xwiki.query.QueryException;
79    import org.xwiki.query.QueryManager;
80    import org.xwiki.store.UnexpectedException;
81   
82    import com.xpn.xwiki.XWiki;
83    import com.xpn.xwiki.XWikiContext;
84    import com.xpn.xwiki.XWikiException;
85    import com.xpn.xwiki.doc.XWikiAttachment;
86    import com.xpn.xwiki.doc.XWikiDocument;
87    import com.xpn.xwiki.doc.XWikiDocument.XWikiAttachmentToRemove;
88    import com.xpn.xwiki.doc.XWikiLink;
89    import com.xpn.xwiki.doc.XWikiLock;
90    import com.xpn.xwiki.doc.XWikiSpace;
91    import com.xpn.xwiki.internal.render.OldRendering;
92    import com.xpn.xwiki.monitor.api.MonitorPlugin;
93    import com.xpn.xwiki.objects.BaseCollection;
94    import com.xpn.xwiki.objects.BaseElement;
95    import com.xpn.xwiki.objects.BaseObject;
96    import com.xpn.xwiki.objects.BaseProperty;
97    import com.xpn.xwiki.objects.BaseStringProperty;
98    import com.xpn.xwiki.objects.LargeStringProperty;
99    import com.xpn.xwiki.objects.ListProperty;
100    import com.xpn.xwiki.objects.PropertyInterface;
101    import com.xpn.xwiki.objects.StringProperty;
102    import com.xpn.xwiki.objects.classes.BaseClass;
103    import com.xpn.xwiki.objects.classes.PropertyClass;
104    import com.xpn.xwiki.objects.classes.StringClass;
105    import com.xpn.xwiki.objects.classes.TextAreaClass;
106    import com.xpn.xwiki.stats.impl.XWikiStats;
107    import com.xpn.xwiki.store.migration.MigrationRequiredException;
108    import com.xpn.xwiki.util.Util;
109   
110    /**
111    * The XWiki Hibernate database driver.
112    *
113    * @version $Id: 2353703c3f56b223d385a1ec32abc81a88f7e4d5 $
114    */
115    @Component
116    @Named(XWikiHibernateBaseStore.HINT)
117    @Singleton
 
118    public class XWikiHibernateStore extends XWikiHibernateBaseStore implements XWikiStoreInterface
119    {
120    @Inject
121    private Logger logger;
122   
123    /**
124    * QueryManager for this store.
125    */
126    @Inject
127    private QueryManager queryManager;
128   
129    /** Needed so we can register an event to trap logout and delete held locks. */
130    @Inject
131    private ObservationManager observationManager;
132   
133    /**
134    * Used to resolve a string into a proper Document Reference using the current document's reference to fill the
135    * blanks, except for the page name for which the default page name is used instead and for the wiki name for which
136    * the current wiki is used instead of the current document reference's wiki.
137    */
138    @Inject
139    @Named("currentmixed")
140    private DocumentReferenceResolver<String> currentMixedDocumentReferenceResolver;
141   
142    @Inject
143    private DocumentReferenceResolver<String> defaultDocumentReferenceResolver;
144   
145    /**
146    * Used to convert a proper Document Reference to string (standard form).
147    */
148    @Inject
149    private EntityReferenceSerializer<String> defaultEntityReferenceSerializer;
150   
151    /**
152    * Used to convert a Document Reference to string (compact form without the wiki part).
153    */
154    @Inject
155    @Named("compactwiki")
156    private EntityReferenceSerializer<String> compactWikiEntityReferenceSerializer;
157   
158    /**
159    * Used to convert a proper Document Reference to a string but without the wiki name.
160    */
161    @Inject
162    @Named("local")
163    private EntityReferenceSerializer<String> localEntityReferenceSerializer;
164   
165    @Inject
166    private Provider<OldRendering> oldRenderingProvider;
167   
168    @Inject
169    private ComponentManager componentManager;
170   
171    @Inject
172    @Named(HINT)
173    private XWikiAttachmentStoreInterface attachmentContentStore;
174   
175    @Inject
176    @Named(HINT)
177    private AttachmentVersioningStore attachmentArchiveStore;
178   
179    private Map<String, String[]> validTypesMap = new HashMap<>();
180   
181    /**
182    * This allows to initialize our storage engine. The hibernate config file path is taken from xwiki.cfg or directly
183    * in the WEB-INF directory.
184    *
185    * @param xwiki
186    * @param context
187    * @deprecated 1.6M1. Use ComponentManager.lookup(XWikiStoreInterface.class) instead.
188    */
 
189  41 toggle @Deprecated
190    public XWikiHibernateStore(XWiki xwiki, XWikiContext context)
191    {
192  41 super(xwiki, context);
193  41 initValidColumTypes();
194    }
195   
196    /**
197    * Initialize the storage engine with a specific path. This is used for tests.
198    *
199    * @param hibpath
200    * @deprecated 1.6M1. Use ComponentManager.lookup(XWikiStoreInterface.class) instead.
201    */
 
202  0 toggle @Deprecated
203    public XWikiHibernateStore(String hibpath)
204    {
205  0 super(hibpath);
206  0 initValidColumTypes();
207    }
208   
209    /**
210    * @see #XWikiHibernateStore(XWiki, XWikiContext)
211    * @deprecated 1.6M1. Use ComponentManager.lookup(XWikiStoreInterface.class) instead.
212    */
 
213  0 toggle @Deprecated
214    public XWikiHibernateStore(XWikiContext context)
215    {
216  0 this(context.getWiki(), context);
217    }
218   
219    /**
220    * Empty constructor needed for component manager.
221    */
 
222  117 toggle public XWikiHibernateStore()
223    {
224  117 initValidColumTypes();
225    }
226   
 
227  117 toggle @Override
228    public void initialize() throws InitializationException
229    {
230  117 super.initialize();
231  117 this.registerLogoutListener();
232    }
233   
234    /**
235    * This initializes the valid custom types Used for Custom Mapping
236    */
 
237  158 toggle private void initValidColumTypes()
238    {
239  158 String[] string_types = { "string", "text", "clob" };
240  158 String[] number_types =
241    { "integer", "long", "float", "double", "big_decimal", "big_integer", "yes_no", "true_false" };
242  158 String[] date_types = { "date", "time", "timestamp" };
243  158 String[] boolean_types = { "boolean", "yes_no", "true_false", "integer" };
244  158 this.validTypesMap = new HashMap<>();
245  158 this.validTypesMap.put("com.xpn.xwiki.objects.classes.StringClass", string_types);
246  158 this.validTypesMap.put("com.xpn.xwiki.objects.classes.TextAreaClass", string_types);
247  158 this.validTypesMap.put("com.xpn.xwiki.objects.classes.PasswordClass", string_types);
248  158 this.validTypesMap.put("com.xpn.xwiki.objects.classes.NumberClass", number_types);
249  158 this.validTypesMap.put("com.xpn.xwiki.objects.classes.DateClass", date_types);
250  158 this.validTypesMap.put("com.xpn.xwiki.objects.classes.BooleanClass", boolean_types);
251    }
252   
 
253  15 toggle @Override
254    public boolean isWikiNameAvailable(String wikiName, XWikiContext inputxcontext) throws XWikiException
255    {
256  15 XWikiContext context = getExecutionXContext(inputxcontext, false);
257   
258  15 boolean available;
259   
260  15 boolean bTransaction = true;
261  15 String database = context.getWikiId();
262   
263  15 try {
264  15 bTransaction = beginTransaction(context);
265  15 Session session = getSession(context);
266   
267    // Capture Logs since we voluntarily generate storage errors to check if the wiki already exists and
268    // we don't want to pollute application logs with "normal errors"...
269  15 if (!this.logger.isDebugEnabled()) {
270  15 this.loggerManager.pushLogListener(null);
271    }
272   
273  15 context.setWikiId(wikiName);
274  15 try {
275  15 setDatabase(session, context);
276  0 available = false;
277    } catch (XWikiException e) {
278    // Failed to switch to database. Assume it means database does not exists.
279  15 available = !(e.getCause() instanceof MigrationRequiredException);
280    }
281    } catch (Exception e) {
282  0 Object[] args = { wikiName };
283  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
284    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_CHECK_EXISTS_DATABASE,
285    "Exception while listing databases to search for {0}", e, args);
286    } finally {
287  15 context.setWikiId(database);
288  15 try {
289  15 if (bTransaction) {
290  15 endTransaction(context, false);
291    }
292    } catch (Exception e) {
293    }
294   
295    // Restore proper logging
296  15 if (!this.logger.isDebugEnabled()) {
297  15 this.loggerManager.popLogListener();
298    }
299    }
300   
301  15 return available;
302    }
303   
 
304  2 toggle @Override
305    public void createWiki(String wikiName, XWikiContext inputxcontext) throws XWikiException
306    {
307  2 XWikiContext context = getExecutionXContext(inputxcontext, false);
308   
309  2 boolean bTransaction = true;
310  2 String database = context.getWikiId();
311  2 Statement stmt = null;
312  2 try {
313  2 bTransaction = beginTransaction(context);
314  2 Session session = getSession(context);
315  2 Connection connection = session.connection();
316  2 stmt = connection.createStatement();
317   
318  2 String schema = getSchemaFromWikiName(wikiName, context);
319  2 String escapedSchema = escapeSchema(schema, context);
320   
321  2 DatabaseProduct databaseProduct = getDatabaseProductName();
322  2 if (DatabaseProduct.ORACLE == databaseProduct) {
323  0 stmt.execute("create user " + escapedSchema + " identified by " + escapedSchema);
324  0 stmt.execute("grant resource to " + escapedSchema);
325  2 } else if (DatabaseProduct.DERBY == databaseProduct || DatabaseProduct.DB2 == databaseProduct
326    || DatabaseProduct.H2 == databaseProduct) {
327  0 stmt.execute("CREATE SCHEMA " + escapedSchema);
328  2 } else if (DatabaseProduct.HSQLDB == databaseProduct) {
329  2 stmt.execute("CREATE SCHEMA " + escapedSchema + " AUTHORIZATION DBA");
330  0 } else if (DatabaseProduct.MYSQL == databaseProduct) {
331    // TODO: find a proper java lib to convert from java encoding to mysql charset name and collation
332  0 if (context.getWiki().getEncoding().equals("UTF-8")) {
333  0 stmt.execute("create database " + escapedSchema + " CHARACTER SET utf8 COLLATE utf8_bin");
334    } else {
335  0 stmt.execute("create database " + escapedSchema);
336    }
337  0 } else if (DatabaseProduct.POSTGRESQL == databaseProduct) {
338  0 if (isInSchemaMode()) {
339  0 stmt.execute("CREATE SCHEMA " + escapedSchema);
340    } else {
341  0 this.logger.error("Creation of a new database is currently only supported in the schema mode, "
342    + "see https://jira.xwiki.org/browse/XWIKI-8753");
343    }
344    } else {
345  0 stmt.execute("create database " + escapedSchema);
346    }
347   
348  2 endTransaction(context, true);
349    } catch (Exception e) {
350  0 Object[] args = { wikiName };
351  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
352    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_CREATE_DATABASE, "Exception while create wiki database {0}",
353    e, args);
354    } finally {
355  2 context.setWikiId(database);
356  2 try {
357  2 if (stmt != null) {
358  2 stmt.close();
359    }
360    } catch (Exception e) {
361    }
362  2 try {
363  2 if (bTransaction) {
364  2 endTransaction(context, false);
365    }
366    } catch (Exception e) {
367    }
368    }
369    }
370   
 
371  0 toggle @Override
372    public void deleteWiki(String wikiName, XWikiContext inputxcontext) throws XWikiException
373    {
374  0 XWikiContext context = getExecutionXContext(inputxcontext, false);
375   
376  0 boolean bTransaction = true;
377  0 String database = context.getWikiId();
378  0 Statement stmt = null;
379  0 try {
380  0 bTransaction = beginTransaction(context);
381  0 Session session = getSession(context);
382  0 Connection connection = session.connection();
383  0 stmt = connection.createStatement();
384   
385  0 String schema = getSchemaFromWikiName(wikiName, context);
386  0 String escapedSchema = escapeSchema(schema, context);
387   
388  0 executeDeleteWikiStatement(stmt, getDatabaseProductName(), escapedSchema);
389   
390  0 endTransaction(context, true);
391    } catch (Exception e) {
392  0 Object[] args = { wikiName };
393  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
394    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_DELETE_DATABASE, "Exception while delete wiki database {0}",
395    e, args);
396    } finally {
397  0 context.setWikiId(database);
398  0 try {
399  0 if (stmt != null) {
400  0 stmt.close();
401    }
402    } catch (Exception e) {
403    }
404  0 try {
405  0 if (bTransaction) {
406  0 endTransaction(context, false);
407    }
408    } catch (Exception e) {
409    }
410    }
411    }
412   
413    /**
414    * Execute the SQL statement on the database to remove a wiki.
415    *
416    * @param statement the statement object on which to execute the wiki deletion
417    * @param databaseProduct the database type
418    * @param escapedSchemaName the subwiki schema name being deleted
419    * @throws SQLException in case of an error while deleting the sub wiki
420    */
 
421  2 toggle protected void executeDeleteWikiStatement(Statement statement, DatabaseProduct databaseProduct,
422    String escapedSchemaName) throws SQLException
423    {
424  2 if (DatabaseProduct.ORACLE == databaseProduct) {
425  0 statement.execute("DROP USER " + escapedSchemaName + " CASCADE");
426  2 } else if (DatabaseProduct.DERBY == databaseProduct || DatabaseProduct.MYSQL == databaseProduct
427    || DatabaseProduct.H2 == databaseProduct) {
428  0 statement.execute("DROP SCHEMA " + escapedSchemaName);
429  2 } else if (DatabaseProduct.HSQLDB == databaseProduct) {
430  0 statement.execute("DROP SCHEMA " + escapedSchemaName + " CASCADE");
431  2 } else if (DatabaseProduct.DB2 == databaseProduct) {
432  0 statement.execute("DROP SCHEMA " + escapedSchemaName + " RESTRICT");
433  2 } else if (DatabaseProduct.POSTGRESQL == databaseProduct) {
434  2 if (isInSchemaMode()) {
435  1 statement.execute("DROP SCHEMA " + escapedSchemaName + " CASCADE");
436    } else {
437  1 this.logger.warn("Subwiki deletion not yet supported in Database mode for PostgreSQL");
438    }
439    }
440    }
441   
442    /**
443    * Verifies if a wiki document exists
444    */
 
445  10273 toggle @Override
446    public boolean exists(XWikiDocument doc, XWikiContext inputxcontext) throws XWikiException
447    {
448  10274 XWikiContext context = getExecutionXContext(inputxcontext, true);
449   
450  10273 try {
451  10275 boolean bTransaction = true;
452  10274 MonitorPlugin monitor = Util.getMonitorPlugin(context);
453  10274 try {
454   
455  10274 doc.setStore(this);
456  10275 checkHibernate(context);
457   
458    // Start monitoring timer
459  10274 if (monitor != null) {
460  9006 monitor.startTimer(HINT);
461    }
462   
463  10273 bTransaction = bTransaction && beginTransaction(null, context);
464  10272 Session session = getSession(context);
465  10273 String fullName = doc.getFullName();
466   
467  10275 String sql = "select doc.fullName from XWikiDocument as doc where doc.fullName=:fullName";
468  10271 if (!doc.getLocale().equals(Locale.ROOT)) {
469  2991 sql += " and doc.language=:language";
470    }
471  10270 if (monitor != null) {
472  9005 monitor.setTimerDesc(HINT, sql);
473    }
474  10275 Query query = session.createQuery(sql);
475  10275 query.setString("fullName", fullName);
476  10275 if (!doc.getLocale().equals(Locale.ROOT)) {
477  2991 query.setString("language", doc.getLocale().toString());
478    }
479  10271 Iterator<String> it = query.list().iterator();
480  10275 while (it.hasNext()) {
481  6804 if (fullName.equals(it.next())) {
482  6805 return true;
483    }
484    }
485  3469 return false;
486    } catch (Exception e) {
487  0 Object[] args = { doc.getDocumentReferenceWithLocale() };
488  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
489    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_CHECK_EXISTS_DOC, "Exception while reading document {0}",
490    e, args);
491    } finally {
492    // End monitoring timer
493  10275 if (monitor != null) {
494  9005 monitor.endTimer(HINT);
495    }
496   
497  10273 try {
498  10272 if (bTransaction) {
499  10259 endTransaction(context, false);
500    }
501    } catch (Exception e) {
502    }
503    }
504    } finally {
505  10273 restoreExecutionXContext();
506    }
507    }
508   
 
509  7999 toggle @Override
510    public void saveXWikiDoc(XWikiDocument doc, XWikiContext inputxcontext, boolean bTransaction) throws XWikiException
511    {
512  7999 XWikiContext context = getExecutionXContext(inputxcontext, true);
513   
514  7999 try {
515  7999 MonitorPlugin monitor = Util.getMonitorPlugin(context);
516  7999 try {
517    // Start monitoring timer
518  7999 if (monitor != null) {
519  2065 monitor.startTimer(HINT);
520    }
521  7999 doc.setStore(this);
522    // Make sure the database name is stored
523  7999 doc.setDatabase(context.getWikiId());
524   
525    // If the comment is larger than the max size supported by the Storage, then abbreviate it
526  7999 String comment = doc.getComment();
527  7999 if (comment != null && comment.length() > 1023) {
528  0 doc.setComment(StringUtils.abbreviate(comment, 1023));
529    }
530   
531  7999 if (bTransaction) {
532  7999 checkHibernate(context);
533  7999 SessionFactory sfactory = injectCustomMappingsInSessionFactory(doc, context);
534  7999 bTransaction = beginTransaction(sfactory, context);
535    }
536  7999 Session session = getSession(context);
537  7999 session.setFlushMode(FlushMode.COMMIT);
538   
539    // These informations will allow to not look for attachments and objects on loading
540  7999 doc.setElement(XWikiDocument.HAS_ATTACHMENTS, !doc.getAttachmentList().isEmpty());
541  7999 doc.setElement(XWikiDocument.HAS_OBJECTS, !doc.getXObjects().isEmpty());
542   
543    // Let's update the class XML since this is the new way to store it
544    // TODO If all the properties are removed, the old xml stays?
545  7999 BaseClass bclass = doc.getXClass();
546  7999 if (bclass != null) {
547  7999 if (bclass.getFieldList().isEmpty()) {
548  6116 doc.setXClassXML("");
549    } else {
550    // Don't format the XML to reduce the size of the stored data as much as possible
551  1883 doc.setXClassXML(bclass.toXMLString(false));
552    }
553  7999 bclass.setDirty(false);
554    }
555   
556  7999 if (doc.hasElement(XWikiDocument.HAS_ATTACHMENTS)) {
557  340 saveAttachmentList(doc, context);
558    }
559    // Remove attachments planned for removal
560  7999 if (!doc.getAttachmentsToRemove().isEmpty()) {
561  10 for (XWikiAttachmentToRemove attachmentToRemove : doc.getAttachmentsToRemove()) {
562  10 XWikiAttachment attachment = attachmentToRemove.getAttachment();
563  10 XWikiAttachmentStoreInterface store = getXWikiAttachmentStoreInterface(attachment);
564  10 store.deleteXWikiAttachment(attachment, false, context, false);
565    }
566  10 doc.clearAttachmentsToRemove();
567    }
568   
569    // Handle the latest text file
570  7999 if (doc.isContentDirty() || doc.isMetaDataDirty()) {
571  7781 Date ndate = new Date();
572  7781 doc.setDate(ndate);
573  7781 if (doc.isContentDirty()) {
574  4028 doc.setContentUpdateDate(ndate);
575  4028 doc.setContentAuthorReference(doc.getAuthorReference());
576    }
577  7781 doc.incrementVersion();
578  7781 if (context.getWiki().hasVersioning(context)) {
579  7781 context.getWiki().getVersioningStore().updateXWikiDocArchive(doc, false, context);
580    }
581   
582  7781 doc.setContentDirty(false);
583  7781 doc.setMetaDataDirty(false);
584    } else {
585  218 if (doc.getDocumentArchive() != null) {
586    // A custom document archive has been provided, we assume it's right
587    // (we also assume it's custom but that's another matter...)
588    // Let's make sure we save the archive if we have one
589    // This is especially needed if we load a document from XML
590  36 if (context.getWiki().hasVersioning(context)) {
591  36 context.getWiki().getVersioningStore().saveXWikiDocArchive(doc.getDocumentArchive(), false,
592    context);
593   
594    // If the version does not exist it means it's a new version so add it to the history
595  36 if (!containsVersion(doc, doc.getRCSVersion(), context)) {
596  0 context.getWiki().getVersioningStore().updateXWikiDocArchive(doc, false, context);
597    }
598    }
599    } else {
600    // Make sure the getArchive call has been made once
601    // with a valid context
602  182 try {
603  182 if (context.getWiki().hasVersioning(context)) {
604  182 doc.getDocumentArchive(context);
605   
606    // If the version does not exist it means it's a new version so register it in the
607    // history
608  182 if (!containsVersion(doc, doc.getRCSVersion(), context)) {
609  143 context.getWiki().getVersioningStore().updateXWikiDocArchive(doc, false, context);
610    }
611    }
612    } catch (XWikiException e) {
613    // this is a non critical error
614    }
615    }
616    }
617   
618    // Verify if the document already exists
619  7999 Query query =
620    session.createQuery("select xwikidoc.id from XWikiDocument as xwikidoc where xwikidoc.id = :id");
621  7999 query.setLong("id", doc.getId());
622   
623    // Note: we don't use session.saveOrUpdate(doc) because it used to be slower in Hibernate than calling
624    // session.save() and session.update() separately.
625  7999 if (query.uniqueResult() == null) {
626  5788 if (doc.isContentDirty() || doc.isMetaDataDirty()) {
627    // Reset the creationDate to reflect the date of the first save, not the date of the object
628    // creation
629  0 doc.setCreationDate(new Date());
630    }
631  5788 session.save(doc);
632    } else {
633  2211 session.update(doc);
634    }
635   
636    // Remove objects planned for removal
637  7999 if (doc.getXObjectsToRemove().size() > 0) {
638  40 for (BaseObject removedObject : doc.getXObjectsToRemove()) {
639  44 deleteXWikiCollection(removedObject, context, false, false);
640    }
641  40 doc.setXObjectsToRemove(new ArrayList<BaseObject>());
642    }
643   
644  7999 if (bclass != null) {
645  7999 bclass.setDocumentReference(doc.getDocumentReference());
646    // Store this XWikiClass in the context so that we can use it in case of recursive usage of classes
647  7999 context.addBaseClass(bclass);
648    }
649   
650  7999 if (doc.hasElement(XWikiDocument.HAS_OBJECTS)) {
651    // TODO: Delete all objects for which we don't have a name in the Map
652  4786 for (List<BaseObject> objects : doc.getXObjects().values()) {
653  7390 for (BaseObject obj : objects) {
654  10855 if (obj != null) {
655  10519 obj.setDocumentReference(doc.getDocumentReference());
656    /* If the object doesn't have a GUID, create it before saving */
657  10519 if (StringUtils.isEmpty(obj.getGuid())) {
658  0 obj.setGuid(null);
659    }
660  10519 saveXWikiCollection(obj, context, false);
661    }
662    }
663    }
664    }
665   
666  7999 if (context.getWiki().hasBacklinks(context)) {
667  7999 try {
668  7999 saveLinks(doc, context, true);
669    } catch (Exception e) {
670  0 this.logger.error("Failed to save links for document [{}]",
671    doc.getDocumentReferenceWithLocale(), e);
672    }
673    }
674   
675    // Update space table
676  7999 updateXWikiSpaceTable(doc, session);
677   
678  7999 if (bTransaction) {
679  7999 endTransaction(context, true);
680    }
681   
682  7999 doc.setNew(false);
683   
684    // We need to ensure that the saved document becomes the original document
685  7999 doc.setOriginalDocument(doc.clone());
686    } catch (Exception e) {
687  0 Object[] args = { this.defaultEntityReferenceSerializer.serialize(doc.getDocumentReference()) };
688  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
689    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_DOC, "Exception while saving document {0}", e,
690    args);
691    } finally {
692  7999 try {
693  7999 if (bTransaction) {
694  7999 endTransaction(context, false);
695    }
696    } catch (Exception e) {
697    }
698   
699    // End monitoring timer
700  7999 if (monitor != null) {
701  2065 monitor.endTimer(HINT);
702    }
703    }
704    } finally {
705  7999 restoreExecutionXContext();
706    }
707    }
708   
 
709  7999 toggle private void updateXWikiSpaceTable(XWikiDocument document, Session session)
710    {
711  7999 if (document.getLocale().equals(Locale.ROOT)) {
712  6436 if (!document.isNew()) {
713    // If the hidden state of an existing document did not changed there is nothing to do
714  1838 if (document.isHidden() != document.getOriginalDocument().isHidden()) {
715  2 if (document.isHidden()) {
716    // If the document became hidden it's possible the space did too
717  0 maybeMakeSpaceHidden(document.getDocumentReference().getLastSpaceReference(),
718    document.getFullName(), session);
719    } else {
720    // If the document became visible then all its parents should be visible as well
721  2 makeSpaceVisible(document.getDocumentReference().getLastSpaceReference(), session);
722    }
723    }
724    } else {
725    // It's possible the space of a new document does not yet exist
726  4598 maybeCreateSpace(document.getDocumentReference().getLastSpaceReference(), document.isHidden(),
727    document.getFullName(), session);
728    }
729    }
730    }
731   
 
732  627 toggle private void insertXWikiSpace(XWikiSpace space, String newDocument, Session session)
733    {
734    // Insert the space
735  627 session.save(space);
736   
737    // Update parent space
738  627 if (space.getSpaceReference().getParent() instanceof SpaceReference) {
739  282 maybeCreateSpace((SpaceReference) space.getSpaceReference().getParent(), space.isHidden(), newDocument,
740    session);
741    }
742    }
743   
 
744  20 toggle private void makeSpaceVisible(SpaceReference spaceReference, Session session)
745    {
746  20 XWikiSpace space = loadXWikiSpace(spaceReference, session);
747   
748  20 makeSpaceVisible(space, session);
749    }
750   
 
751  67 toggle private void makeSpaceVisible(XWikiSpace space, Session session)
752    {
753  67 if (space.isHidden()) {
754  54 space.setHidden(false);
755   
756  54 session.update(space);
757   
758    // Update parent
759  54 if (space.getSpaceReference().getParent() instanceof SpaceReference) {
760  18 makeSpaceVisible((SpaceReference) space.getSpaceReference().getParent(), session);
761    }
762    }
763    }
764   
 
765  310 toggle private void maybeMakeSpaceHidden(SpaceReference spaceReference, String modifiedDocument, Session session)
766    {
767  310 XWikiSpace space = loadXWikiSpace(spaceReference, session);
768   
769    // The space is supposed to exist
770  310 if (space == null) {
771  0 this.logger.warn(
772    "Space [{}] does not exist. Usually means the spaces table is not in sync with the documents table.",
773    spaceReference);
774   
775  0 return;
776    }
777   
778    // If the space is already hidden return
779  310 if (space.isHidden()) {
780  151 return;
781    }
782   
783  159 if (calculateHiddenStatus(spaceReference, modifiedDocument, session)) {
784    // Make the space hidden
785  10 space.setHidden(true);
786  10 session.update(space);
787   
788    // Update space parent
789  10 if (spaceReference.getParent() instanceof SpaceReference) {
790  3 maybeMakeSpaceHidden((SpaceReference) spaceReference.getParent(), modifiedDocument, session);
791    }
792    }
793    }
794   
 
795  4880 toggle private void maybeCreateSpace(SpaceReference spaceReference, boolean hidden, String newDocument, Session session)
796    {
797  4880 XWikiSpace space = loadXWikiSpace(spaceReference, session);
798   
799  4880 if (space != null) {
800  4253 if (space.isHidden() && !hidden) {
801  47 makeSpaceVisible(space, session);
802    }
803    } else {
804  627 insertXWikiSpace(new XWikiSpace(spaceReference, hidden), newDocument, session);
805    }
806    }
807   
 
808  556 toggle private long countAllDocuments(SpaceReference spaceReference, Session session, String extraWhere,
809    Object... extraParameters)
810    {
811  556 StringBuilder builder =
812    new StringBuilder("select count(*) from XWikiDocument as xwikidoc where (space = ? OR space LIKE ?)");
813   
814  556 if (StringUtils.isNotEmpty(extraWhere)) {
815  556 builder.append(" AND ");
816  556 builder.append('(');
817  556 builder.append(extraWhere);
818  556 builder.append(')');
819    }
820   
821  556 Query query = session.createQuery(builder.toString());
822   
823  556 String localSpaceReference = this.localEntityReferenceSerializer.serialize(spaceReference);
824   
825  556 int index = 0;
826   
827  556 query.setString(index++, localSpaceReference);
828  556 query.setString(index++, localSpaceReference + ".%");
829   
830  556 if (extraParameters != null) {
831  556 for (Object parameter : extraParameters) {
832  556 query.setParameter(index++, parameter);
833    }
834    }
835   
836  556 return (Long) query.uniqueResult();
837    }
838   
839    /**
840    * Find hidden status of a space from its children.
841    */
 
842  159 toggle private boolean calculateHiddenStatus(SpaceReference spaceReference, String documentToIngore, Session session)
843    {
844    // If there is at least one visible document then the space is visible
845  159 StringBuilder builder = new StringBuilder("(hidden = false OR hidden IS NULL)");
846   
847  159 Object[] parameters;
848  159 if (documentToIngore != null) {
849  159 builder.append(" AND fullName <> ?");
850  159 parameters = new Object[] { documentToIngore };
851    } else {
852  0 parameters = null;
853    }
854   
855  159 return !(countAllDocuments(spaceReference, session, builder.toString(), parameters) > 0);
856    }
857   
 
858  218 toggle private boolean containsVersion(XWikiDocument doc, Version targetversion, XWikiContext context)
859    throws XWikiException
860    {
861  218 for (Version version : doc.getRevisions(context)) {
862  114 if (version.equals(targetversion)) {
863  75 return true;
864    }
865    }
866   
867  143 return false;
868    }
869   
 
870  0 toggle @Override
871    public void saveXWikiDoc(XWikiDocument doc, XWikiContext context) throws XWikiException
872    {
873  0 saveXWikiDoc(doc, context, true);
874    }
875   
 
876  40613 toggle @Override
877    public XWikiDocument loadXWikiDoc(XWikiDocument doc, XWikiContext inputxcontext) throws XWikiException
878    {
879  40614 XWikiContext context = getExecutionXContext(inputxcontext, true);
880   
881  40619 try {
882    // To change body of implemented methods use Options | File Templates.
883  40617 boolean bTransaction = true;
884  40618 MonitorPlugin monitor = Util.getMonitorPlugin(context);
885  40618 try {
886    // Start monitoring timer
887  40616 if (monitor != null) {
888  20387 monitor.startTimer(HINT);
889    }
890  40613 doc.setStore(this);
891  40615 checkHibernate(context);
892   
893  40612 SessionFactory sfactory = injectCustomMappingsInSessionFactory(doc, context);
894  40613 bTransaction = bTransaction && beginTransaction(sfactory, context);
895  40594 Session session = getSession(context);
896  40608 session.setFlushMode(FlushMode.MANUAL);
897   
898  40614 try {
899  40612 session.load(doc, Long.valueOf(doc.getId()));
900  24768 doc.setNew(false);
901  24768 doc.setMostRecent(true);
902    // Fix for XWIKI-1651
903  24766 doc.setDate(new Date(doc.getDate().getTime()));
904  24768 doc.setCreationDate(new Date(doc.getCreationDate().getTime()));
905  24772 doc.setContentUpdateDate(new Date(doc.getContentUpdateDate().getTime()));
906    } catch (ObjectNotFoundException e) { // No document
907  15795 doc.setNew(true);
908   
909    // Make sure to always return a document with an original version, even for one that does not exist.
910    // Allow writing more generic code.
911  15810 doc.setOriginalDocument(new XWikiDocument(doc.getDocumentReference(), doc.getLocale()));
912   
913  15837 return doc;
914    }
915   
916    // Loading the attachment list
917  24767 if (doc.hasElement(XWikiDocument.HAS_ATTACHMENTS)) {
918  1719 loadAttachmentList(doc, context, false);
919    }
920   
921    // TODO: handle the case where there are no xWikiClass and xWikiObject in the Database
922  24773 BaseClass bclass = new BaseClass();
923  24770 String cxml = doc.getXClassXML();
924  24772 if (cxml != null) {
925  24767 bclass.fromXML(cxml);
926  24773 doc.setXClass(bclass);
927  24769 bclass.setDirty(false);
928    }
929   
930    // Store this XWikiClass in the context so that we can use it in case of recursive usage
931    // of classes
932  24774 context.addBaseClass(bclass);
933   
934  24773 if (doc.hasElement(XWikiDocument.HAS_OBJECTS)) {
935  15849 Query query = session
936    .createQuery("from BaseObject as bobject where bobject.name = :name order by bobject.number");
937  15850 query.setText("name", doc.getFullName());
938  15849 @SuppressWarnings("unchecked")
939    Iterator<BaseObject> it = query.list().iterator();
940   
941  15850 EntityReference localGroupEntityReference = new EntityReference("XWikiGroups", EntityType.DOCUMENT,
942    new EntityReference("XWiki", EntityType.SPACE));
943  15848 DocumentReference groupsDocumentReference = new DocumentReference(context.getWikiId(),
944    localGroupEntityReference.getParent().getName(), localGroupEntityReference.getName());
945   
946  15848 boolean hasGroups = false;
947  46504 while (it.hasNext()) {
948  30656 BaseObject object = it.next();
949  30658 DocumentReference classReference = object.getXClassReference();
950   
951  30656 if (classReference == null) {
952  0 continue;
953    }
954   
955    // It seems to search before is case insensitive. And this would break the loading if we get an
956    // object which doesn't really belong to this document
957  30658 if (!object.getDocumentReference().equals(doc.getDocumentReference())) {
958  0 continue;
959    }
960   
961  30657 BaseObject newobject;
962  30656 if (classReference.equals(doc.getDocumentReference())) {
963  232 newobject = bclass.newCustomClassInstance(context);
964    } else {
965  30424 newobject = BaseClass.newCustomClassInstance(classReference, context);
966    }
967  30655 if (newobject != null) {
968  30655 newobject.setId(object.getId());
969  30651 newobject.setXClassReference(object.getRelativeXClassReference());
970  30654 newobject.setDocumentReference(object.getDocumentReference());
971  30656 newobject.setNumber(object.getNumber());
972  30656 newobject.setGuid(object.getGuid());
973  30651 object = newobject;
974    }
975   
976  30654 if (classReference.equals(groupsDocumentReference)) {
977    // Groups objects are handled differently.
978  569 hasGroups = true;
979    } else {
980  30086 loadXWikiCollectionInternal(object, doc, context, false, true);
981    }
982  30656 doc.setXObject(object.getNumber(), object);
983    }
984   
985    // AFAICT this was added as an emergency patch because loading of objects has proven
986    // too slow and the objects which cause the most overhead are the XWikiGroups objects
987    // as each group object (each group member) would otherwise cost 2 database queries.
988    // This will do every group member in a single query.
989  15848 if (hasGroups) {
990  240 Query query2 =
991    session.createQuery("select bobject.number, prop.value from StringProperty as prop,"
992    + "BaseObject as bobject where bobject.name = :name and bobject.className='XWiki.XWikiGroups' "
993    + "and bobject.id=prop.id.id and prop.id.name='member' order by bobject.number");
994  240 query2.setText("name", doc.getFullName());
995  240 @SuppressWarnings("unchecked")
996    Iterator<Object[]> it2 = query2.list().iterator();
997  809 while (it2.hasNext()) {
998  569 Object[] result = it2.next();
999  569 Integer number = (Integer) result[0];
1000  569 String member = (String) result[1];
1001  569 BaseObject obj = BaseClass.newCustomClassInstance(groupsDocumentReference, context);
1002  569 obj.setDocumentReference(doc.getDocumentReference());
1003  569 obj.setXClassReference(localGroupEntityReference);
1004  569 obj.setNumber(number.intValue());
1005  569 obj.setStringValue("member", member);
1006  569 doc.setXObject(obj.getNumber(), obj);
1007    }
1008    }
1009    }
1010   
1011  24773 doc.setContentDirty(false);
1012  24773 doc.setMetaDataDirty(false);
1013   
1014    // We need to ensure that the loaded document becomes the original document
1015  24772 doc.setOriginalDocument(doc.clone());
1016   
1017  24773 if (bTransaction) {
1018  24173 endTransaction(context, false);
1019    }
1020    } catch (Exception e) {
1021  0 Object[] args = { doc.getDocumentReference() };
1022  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1023    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_READING_DOC, "Exception while reading document [{0}]", e,
1024    args);
1025    } finally {
1026  40604 try {
1027  40611 if (bTransaction) {
1028  39863 endTransaction(context, false);
1029    }
1030    } catch (Exception e) {
1031    }
1032   
1033    // End monitoring timer
1034  40604 if (monitor != null) {
1035  20378 monitor.endTimer(HINT);
1036    }
1037    }
1038   
1039  24774 this.logger.debug("Loaded XWikiDocument: [{}]", doc.getDocumentReference());
1040   
1041  24776 return doc;
1042    } finally {
1043  40600 restoreExecutionXContext();
1044    }
1045    }
1046   
 
1047  426 toggle @Override
1048    public void deleteXWikiDoc(XWikiDocument doc, XWikiContext inputxcontext) throws XWikiException
1049    {
1050  426 XWikiContext context = getExecutionXContext(inputxcontext, true);
1051   
1052  426 try {
1053  426 boolean bTransaction = true;
1054  426 MonitorPlugin monitor = Util.getMonitorPlugin(context);
1055  426 try {
1056    // Start monitoring timer
1057  426 if (monitor != null) {
1058  193 monitor.startTimer(HINT);
1059    }
1060  426 checkHibernate(context);
1061  426 SessionFactory sfactory = injectCustomMappingsInSessionFactory(doc, context);
1062  426 bTransaction = bTransaction && beginTransaction(sfactory, context);
1063  426 Session session = getSession(context);
1064  426 session.setFlushMode(FlushMode.COMMIT);
1065   
1066  426 if (doc.getStore() == null) {
1067  0 Object[] args = { doc.getDocumentReference() };
1068  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1069    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_CANNOT_DELETE_UNLOADED_DOC,
1070    "Impossible to delete document {0} if it is not loaded", null, args);
1071    }
1072   
1073    // Let's delete any attachment this document might have
1074  426 for (XWikiAttachment attachment : doc.getAttachmentList()) {
1075  48 XWikiAttachmentStoreInterface store = getXWikiAttachmentStoreInterface(attachment);
1076  48 store.deleteXWikiAttachment(attachment, false, context, false);
1077    }
1078   
1079    // deleting XWikiLinks
1080  426 if (context.getWiki().hasBacklinks(context)) {
1081  426 deleteLinks(doc.getId(), context, true);
1082    }
1083   
1084    // Find the list of classes for which we have an object
1085    // Remove properties planned for removal
1086  426 if (!doc.getXObjectsToRemove().isEmpty()) {
1087  0 for (BaseObject bobj : doc.getXObjectsToRemove()) {
1088  0 if (bobj != null) {
1089  0 deleteXWikiCollection(bobj, context, false, false);
1090    }
1091    }
1092  0 doc.setXObjectsToRemove(new ArrayList<BaseObject>());
1093    }
1094  426 for (List<BaseObject> objects : doc.getXObjects().values()) {
1095  315 for (BaseObject obj : objects) {
1096  445 if (obj != null) {
1097  433 deleteXWikiCollection(obj, context, false, false);
1098    }
1099    }
1100    }
1101  426 context.getWiki().getVersioningStore().deleteArchive(doc, false, context);
1102   
1103  426 session.delete(doc);
1104   
1105    // We need to ensure that the deleted document becomes the original document
1106  426 doc.setOriginalDocument(doc.clone());
1107   
1108    // Update space table if needed
1109  426 maybeDeleteXWikiSpace(doc, session);
1110   
1111  426 if (bTransaction) {
1112  426 endTransaction(context, true);
1113    }
1114    } catch (Exception e) {
1115  0 Object[] args = { doc.getDocumentReference() };
1116  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1117    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_DELETING_DOC, "Exception while deleting document {0}", e,
1118    args);
1119    } finally {
1120  426 try {
1121  426 if (bTransaction) {
1122  426 endTransaction(context, false);
1123    }
1124    } catch (Exception e) {
1125    }
1126   
1127    // End monitoring timer
1128  426 if (monitor != null) {
1129  193 monitor.endTimer(HINT);
1130    }
1131    }
1132    } finally {
1133  426 restoreExecutionXContext();
1134    }
1135    }
1136   
 
1137  426 toggle private void maybeDeleteXWikiSpace(XWikiDocument deletedDocument, Session session)
1138    {
1139  426 if (deletedDocument.getLocale().equals(Locale.ROOT)) {
1140  347 DocumentReference documentReference = deletedDocument.getDocumentReference();
1141  347 maybeDeleteXWikiSpace(documentReference.getLastSpaceReference(),
1142    this.localEntityReferenceSerializer.serialize(documentReference), session);
1143    }
1144    }
1145   
 
1146  397 toggle private void maybeDeleteXWikiSpace(SpaceReference spaceReference, String deletedDocument, Session session)
1147    {
1148  397 if (countAllDocuments(spaceReference, session, "fullName <> ? AND (language IS NULL OR language = '')",
1149    deletedDocument) == 0) {
1150    // The document was the last document in the space
1151  90 XWikiSpace space = new XWikiSpace(spaceReference, this);
1152   
1153  90 session.delete(space);
1154   
1155    // Update parent
1156  90 if (spaceReference.getParent() instanceof SpaceReference) {
1157  50 maybeDeleteXWikiSpace((SpaceReference) spaceReference.getParent(), deletedDocument, session);
1158    }
1159    } else {
1160    // Update space hidden property if needed
1161  307 maybeMakeSpaceHidden(spaceReference, deletedDocument, session);
1162    }
1163    }
1164   
 
1165  5210 toggle private XWikiSpace loadXWikiSpace(SpaceReference spaceReference, Session session)
1166    {
1167  5210 XWikiSpace space = new XWikiSpace(spaceReference, this);
1168   
1169  5210 try {
1170  5210 session.load(space, Long.valueOf(space.getId()));
1171    } catch (ObjectNotFoundException e) {
1172    // No space
1173  627 return null;
1174    }
1175   
1176  4583 return space;
1177    }
1178   
 
1179  10520 toggle private void checkObjectClassIsLocal(BaseCollection object, XWikiContext context) throws XWikiException
1180    {
1181  10520 DocumentReference xclass = object.getXClassReference();
1182  10520 WikiReference wikiReference = xclass.getWikiReference();
1183  10520 String db = context.getWikiId();
1184  10520 if (!wikiReference.getName().equals(db)) {
1185  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1186    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_OBJECT,
1187    "XObject [{0}] is an instance of an external XClass and cannot be persisted in this wiki [{1}].", null,
1188    new Object[] { this.localEntityReferenceSerializer.serialize(object.getReference()), db });
1189    }
1190    }
1191   
1192    /**
1193    * @deprecated This is internal to XWikiHibernateStore and may be removed in the future.
1194    */
 
1195  10520 toggle @Deprecated
1196    public void saveXWikiCollection(BaseCollection object, XWikiContext inputxcontext, boolean bTransaction)
1197    throws XWikiException
1198    {
1199  10520 XWikiContext context = getExecutionXContext(inputxcontext, true);
1200   
1201  10520 try {
1202  10520 if (object == null) {
1203  0 return;
1204    }
1205    // We need a slightly different behavior here
1206  10520 boolean stats = (object instanceof XWikiStats);
1207  10520 if (!stats) {
1208  10520 checkObjectClassIsLocal(object, context);
1209    }
1210   
1211  10520 if (bTransaction) {
1212  0 checkHibernate(context);
1213  0 bTransaction = beginTransaction(context);
1214    }
1215  10520 Session session = getSession(context);
1216   
1217    // Verify if the property already exists
1218  10520 Query query;
1219  10520 if (stats) {
1220  0 query = session
1221    .createQuery("select obj.id from " + object.getClass().getName() + " as obj where obj.id = :id");
1222    } else {
1223  10520 query = session.createQuery("select obj.id from BaseObject as obj where obj.id = :id");
1224    }
1225  10520 query.setLong("id", object.getId());
1226  10520 if (query.uniqueResult() == null) {
1227  6402 if (stats) {
1228  0 session.save(object);
1229    } else {
1230  6402 session.save("com.xpn.xwiki.objects.BaseObject", object);
1231    }
1232    } else {
1233  4118 if (stats) {
1234  0 session.update(object);
1235    } else {
1236  4118 session.update("com.xpn.xwiki.objects.BaseObject", object);
1237    }
1238    }
1239    /*
1240    * if (stats) session.saveOrUpdate(object); else
1241    * session.saveOrUpdate((String)"com.xpn.xwiki.objects.BaseObject", (Object)object);
1242    */
1243  10520 BaseClass bclass = object.getXClass(context);
1244  10520 List<String> handledProps = new ArrayList<>();
1245  10520 if ((bclass != null) && (bclass.hasCustomMapping()) && context.getWiki().hasCustomMappings()) {
1246    // save object using the custom mapping
1247  143 Map<String, Object> objmap = object.getCustomMappingMap();
1248  143 handledProps = bclass.getCustomMappingPropertyList(context);
1249  143 Session dynamicSession = session.getSession(EntityMode.MAP);
1250  143 query = session.createQuery("select obj.id from " + bclass.getName() + " as obj where obj.id = :id");
1251  143 query.setLong("id", object.getId());
1252  143 if (query.uniqueResult() == null) {
1253  41 dynamicSession.save(bclass.getName(), objmap);
1254    } else {
1255  102 dynamicSession.update(bclass.getName(), objmap);
1256    }
1257   
1258    // dynamicSession.saveOrUpdate((String) bclass.getName(), objmap);
1259    }
1260   
1261  10520 if (object.getXClassReference() != null) {
1262    // Remove properties to remove
1263  10520 if (!object.getFieldsToRemove().isEmpty()) {
1264  42 for (int i = 0; i < object.getFieldsToRemove().size(); i++) {
1265  21 BaseProperty prop = (BaseProperty) object.getFieldsToRemove().get(i);
1266  21 if (!handledProps.contains(prop.getName())) {
1267  21 session.delete(prop);
1268    }
1269    }
1270  21 object.setFieldsToRemove(new ArrayList<BaseProperty>());
1271    }
1272   
1273    // Add missing properties to the object
1274  10520 BaseClass xclass = object.getXClass(context);
1275  10520 if (xclass != null) {
1276  10519 for (String key : xclass.getPropertyList()) {
1277  64532 if (object.safeget(key) == null) {
1278  4806 PropertyClass classProperty = (PropertyClass) xclass.getField(key);
1279  4806 object.safeput(key, classProperty.newProperty());
1280    }
1281    }
1282    }
1283   
1284    // Save properties
1285  10520 Iterator<String> it = object.getPropertyList().iterator();
1286  78145 while (it.hasNext()) {
1287  67625 String key = it.next();
1288  67625 BaseProperty prop = (BaseProperty) object.getField(key);
1289  67625 if (!prop.getName().equals(key)) {
1290  0 Object[] args = { key, object.getName() };
1291  0 throw new XWikiException(XWikiException.MODULE_XWIKI_CLASSES,
1292    XWikiException.ERROR_XWIKI_CLASSES_FIELD_INVALID,
1293    "Field {0} in object {1} has an invalid name", null, args);
1294    }
1295   
1296  67625 String pname = prop.getName();
1297  67625 if (pname != null && !pname.trim().equals("") && !handledProps.contains(pname)) {
1298  63049 saveXWikiPropertyInternal(prop, context, false);
1299    }
1300    }
1301    }
1302   
1303  10520 if (bTransaction) {
1304  0 endTransaction(context, true);
1305    }
1306    } catch (XWikiException xe) {
1307  0 throw xe;
1308    } catch (Exception e) {
1309  0 Object[] args = { object.getName() };
1310  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1311    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_OBJECT, "Exception while saving object {0}", e, args);
1312   
1313    } finally {
1314  10520 try {
1315  10520 if (bTransaction) {
1316  0 endTransaction(context, true);
1317    }
1318    } catch (Exception e) {
1319    }
1320   
1321  10520 restoreExecutionXContext();
1322    }
1323    }
1324   
1325    /**
1326    * @deprecated This is internal to XWikiHibernateStore and may be removed in the future.
1327    */
 
1328  0 toggle @Deprecated
1329    public void loadXWikiCollection(BaseCollection object, XWikiContext context, boolean bTransaction)
1330    throws XWikiException
1331    {
1332  0 loadXWikiCollectionInternal(object, context, bTransaction, false);
1333    }
1334   
 
1335  0 toggle private void loadXWikiCollectionInternal(BaseCollection object, XWikiContext context, boolean bTransaction,
1336    boolean alreadyLoaded) throws XWikiException
1337    {
1338  0 loadXWikiCollectionInternal(object, null, context, bTransaction, alreadyLoaded);
1339    }
1340   
 
1341  30080 toggle private void loadXWikiCollectionInternal(BaseCollection object1, XWikiDocument doc, XWikiContext inputxcontext,
1342    boolean bTransaction, boolean alreadyLoaded) throws XWikiException
1343    {
1344  30087 XWikiContext context = getExecutionXContext(inputxcontext, true);
1345   
1346  30083 BaseCollection object = object1;
1347  30088 try {
1348  30084 if (bTransaction) {
1349  0 checkHibernate(context);
1350  0 bTransaction = beginTransaction(false, context);
1351    }
1352  30084 Session session = getSession(context);
1353   
1354  30083 if (!alreadyLoaded) {
1355  0 try {
1356  0 session.load(object, object1.getId());
1357    } catch (ObjectNotFoundException e) {
1358    // There is no object data saved
1359  0 object = null;
1360  0 return;
1361    }
1362    }
1363   
1364  30083 DocumentReference classReference = object.getXClassReference();
1365   
1366    // If the class reference is null in the loaded object then skip loading properties
1367  30087 if (classReference != null) {
1368   
1369  30086 BaseClass bclass = null;
1370  30087 if (!classReference.equals(object.getDocumentReference())) {
1371    // Let's check if the class has a custom mapping
1372  29855 bclass = object.getXClass(context);
1373    } else {
1374    // We need to get it from the document otherwise
1375    // we will go in an endless loop
1376  232 if (doc != null) {
1377  232 bclass = doc.getXClass();
1378    }
1379    }
1380   
1381  30085 List<String> handledProps = new ArrayList<String>();
1382  30086 try {
1383  30083 if ((bclass != null) && (bclass.hasCustomMapping()) && context.getWiki().hasCustomMappings()) {
1384  423 Session dynamicSession = session.getSession(EntityMode.MAP);
1385  423 String className = this.localEntityReferenceSerializer.serialize(bclass.getDocumentReference());
1386  423 @SuppressWarnings("unchecked")
1387    Map<String, ?> map = (Map<String, ?>) dynamicSession.load(className, object.getId());
1388    // Let's make sure to look for null fields in the dynamic mapping
1389  423 bclass.fromValueMap(map, object);
1390  423 for (String prop : bclass.getCustomMappingPropertyList(context)) {
1391  13959 if (map.get(prop) != null) {
1392  9077 handledProps.add(prop);
1393    }
1394    }
1395    }
1396    } catch (HibernateException e) {
1397  0 this.logger.error("Failed loading custom mapping for doc [{}], class [{}], nb [{}]",
1398    object.getDocumentReference(), object.getXClassReference(), object.getNumber(), e);
1399    }
1400   
1401    // Load strings, integers, dates all at once
1402   
1403  30084 Query query = session
1404    .createQuery("select prop.name, prop.classType from BaseProperty as prop where prop.id.id = :id");
1405  30088 query.setLong("id", object.getId());
1406  30084 for (Object[] result : (List<Object[]>) query.list()) {
1407  206006 String name = (String) result[0];
1408    // No need to load fields already loaded from
1409    // custom mapping
1410  206034 if (handledProps.contains(name)) {
1411  707 continue;
1412    }
1413  205351 String classType = (String) result[1];
1414  205382 BaseProperty property = null;
1415   
1416  205364 try {
1417  205371 property = (BaseProperty) Class.forName(classType).newInstance();
1418  205361 property.setObject(object);
1419  205352 property.setName(name);
1420  205374 loadXWikiProperty(property, context, false);
1421    } catch (Exception e) {
1422    // WORKAROUND IN CASE OF MIXMATCH BETWEEN STRING AND LARGESTRING
1423  0 try {
1424  0 if (property instanceof StringProperty) {
1425  0 LargeStringProperty property2 = new LargeStringProperty();
1426  0 property2.setObject(object);
1427  0 property2.setName(name);
1428  0 loadXWikiProperty(property2, context, false);
1429  0 property.setValue(property2.getValue());
1430   
1431  0 if (bclass != null) {
1432  0 if (bclass.get(name) instanceof TextAreaClass) {
1433  0 property = property2;
1434    }
1435    }
1436   
1437  0 } else if (property instanceof LargeStringProperty) {
1438  0 StringProperty property2 = new StringProperty();
1439  0 property2.setObject(object);
1440  0 property2.setName(name);
1441  0 loadXWikiProperty(property2, context, false);
1442  0 property.setValue(property2.getValue());
1443   
1444  0 if (bclass != null) {
1445  0 if (bclass.get(name) instanceof StringClass) {
1446  0 property = property2;
1447    }
1448    }
1449    } else {
1450  0 throw e;
1451    }
1452    } catch (Throwable e2) {
1453  0 Object[] args =
1454    { object.getName(), object.getClass(), Integer.valueOf(object.getNumber() + ""), name };
1455  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1456    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_LOADING_OBJECT,
1457    "Exception while loading object '{0}' of class '{1}', number '{2}' and property '{3}'",
1458    e, args);
1459    }
1460    }
1461   
1462  205133 object.addField(name, property);
1463    }
1464    }
1465   
1466  30085 if (bTransaction) {
1467  0 endTransaction(context, false, false);
1468    }
1469    } catch (Exception e) {
1470  0 Object[] args = { object.getName(), object.getClass(), Integer.valueOf(object.getNumber() + "") };
1471  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1472    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_LOADING_OBJECT,
1473    "Exception while loading object '{0}' of class '{1}' and number '{2}'", e, args);
1474   
1475    } finally {
1476  30087 try {
1477  30085 if (bTransaction) {
1478  0 endTransaction(context, false, false);
1479    }
1480    } catch (Exception e) {
1481    }
1482   
1483  30088 restoreExecutionXContext();
1484    }
1485   
1486    }
1487   
1488    /**
1489    * @deprecated This is internal to XWikiHibernateStore and may be removed in the future.
1490    */
 
1491  477 toggle @Deprecated
1492    public void deleteXWikiCollection(BaseCollection object, XWikiContext inputxcontext, boolean bTransaction,
1493    boolean evict) throws XWikiException
1494    {
1495  477 if (object == null) {
1496  0 return;
1497    }
1498   
1499  477 XWikiContext context = getExecutionXContext(inputxcontext, true);
1500   
1501  477 try {
1502  477 if (bTransaction) {
1503  0 checkHibernate(context);
1504  0 bTransaction = beginTransaction(context);
1505    }
1506  477 Session session = getSession(context);
1507   
1508    // Let's check if the class has a custom mapping
1509  477 BaseClass bclass = object.getXClass(context);
1510  477 List<String> handledProps = new ArrayList<String>();
1511  477 if ((bclass != null) && (bclass.hasCustomMapping()) && context.getWiki().hasCustomMappings()) {
1512  1 handledProps = bclass.getCustomMappingPropertyList(context);
1513  1 Session dynamicSession = session.getSession(EntityMode.MAP);
1514  1 Object map = dynamicSession.get(bclass.getName(), object.getId());
1515  1 if (map != null) {
1516  1 if (evict) {
1517  0 dynamicSession.evict(map);
1518    }
1519  1 dynamicSession.delete(map);
1520    }
1521    }
1522   
1523  477 if (object.getXClassReference() != null) {
1524  477 for (BaseElement property : (Collection<BaseElement>) object.getFieldList()) {
1525  3102 if (!handledProps.contains(property.getName())) {
1526  3078 if (evict) {
1527  0 session.evict(property);
1528    }
1529  3078 if (session.get(property.getClass(), property) != null) {
1530  3074 session.delete(property);
1531    }
1532    }
1533    }
1534    }
1535   
1536    // In case of custom class we need to force it as BaseObject to delete the xwikiobject row
1537  477 if (!"".equals(bclass.getCustomClass())) {
1538  0 BaseObject cobject = new BaseObject();
1539  0 cobject.setDocumentReference(object.getDocumentReference());
1540  0 cobject.setClassName(object.getClassName());
1541  0 cobject.setNumber(object.getNumber());
1542  0 if (object instanceof BaseObject) {
1543  0 cobject.setGuid(((BaseObject) object).getGuid());
1544    }
1545  0 cobject.setId(object.getId());
1546  0 if (evict) {
1547  0 session.evict(cobject);
1548    }
1549  0 session.delete(cobject);
1550    } else {
1551  477 if (evict) {
1552  0 session.evict(object);
1553    }
1554  477 session.delete(object);
1555    }
1556   
1557  477 if (bTransaction) {
1558  0 endTransaction(context, true);
1559    }
1560    } catch (Exception e) {
1561  0 Object[] args = { object.getName() };
1562  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1563    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_DELETING_OBJECT, "Exception while deleting object {0}", e,
1564    args);
1565    } finally {
1566  477 try {
1567  477 if (bTransaction) {
1568  0 endTransaction(context, false);
1569    }
1570    } catch (Exception e) {
1571    }
1572   
1573  477 restoreExecutionXContext();
1574    }
1575    }
1576   
 
1577  205219 toggle private void loadXWikiProperty(PropertyInterface property, XWikiContext context, boolean bTransaction)
1578    throws XWikiException
1579    {
1580  205312 try {
1581  205325 if (bTransaction) {
1582  0 checkHibernate(context);
1583  0 bTransaction = beginTransaction(false, context);
1584    }
1585  205323 Session session = getSession(context);
1586   
1587  205379 try {
1588  205374 session.load(property, (Serializable) property);
1589    // In Oracle, empty string are converted to NULL. Since an undefined property is not found at all, it is
1590    // safe to assume that a retrieved NULL value should actually be an empty string.
1591  205336 if (property instanceof BaseStringProperty) {
1592  161516 BaseStringProperty stringProperty = (BaseStringProperty) property;
1593  161541 if (stringProperty.getValue() == null) {
1594  0 stringProperty.setValue("");
1595    }
1596    }
1597  205357 ((BaseProperty) property).setValueDirty(false);
1598    } catch (ObjectNotFoundException e) {
1599    // Let's accept that there is no data in property tables but log it
1600  0 this.logger.error("No data for property [{}] of object id [{}]", property.getName(), property.getId());
1601    }
1602   
1603    // TODO: understand why collections are lazy loaded
1604    // Let's force reading lists if there is a list
1605    // This seems to be an issue since Hibernate 3.0
1606    // Without this test ViewEditTest.testUpdateAdvanceObjectProp fails
1607  205304 if (property instanceof ListProperty) {
1608  6172 ((ListProperty) property).getList();
1609    }
1610   
1611  205321 if (bTransaction) {
1612  0 endTransaction(context, false, false);
1613    }
1614    } catch (Exception e) {
1615  0 BaseCollection obj = property.getObject();
1616  0 Object[] args = { (obj != null) ? obj.getName() : "unknown", property.getName() };
1617  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1618    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_LOADING_OBJECT,
1619    "Exception while loading property {1} of object {0}", e, args);
1620   
1621    } finally {
1622  205332 try {
1623  205228 if (bTransaction) {
1624  0 endTransaction(context, false, false);
1625    }
1626    } catch (Exception e) {
1627    }
1628    }
1629    }
1630   
 
1631  63049 toggle private void saveXWikiPropertyInternal(final PropertyInterface property, final XWikiContext context,
1632    final boolean runInOwnTransaction) throws XWikiException
1633    {
1634    // Clone runInOwnTransaction so the value passed is not altered.
1635  63049 boolean bTransaction = runInOwnTransaction;
1636  63049 try {
1637  63049 if (bTransaction) {
1638  0 this.checkHibernate(context);
1639  0 bTransaction = this.beginTransaction(context);
1640    }
1641   
1642  63049 final Session session = this.getSession(context);
1643   
1644  63049 Query query = session.createQuery(
1645    "select prop.classType from BaseProperty as prop " + "where prop.id.id = :id and prop.id.name= :name");
1646  63049 query.setLong("id", property.getId());
1647  63049 query.setString("name", property.getName());
1648   
1649  63049 String oldClassType = (String) query.uniqueResult();
1650  63049 String newClassType = ((BaseProperty) property).getClassType();
1651  63049 if (oldClassType == null) {
1652  34074 session.save(property);
1653  28975 } else if (oldClassType.equals(newClassType)) {
1654  28958 session.update(property);
1655    } else {
1656    // The property type has changed. We cannot simply update its value because the new value and the old
1657    // value are stored in different tables (we're using joined-subclass to map different property types).
1658    // We must delete the old property value before saving the new one and for this we must load the old
1659    // property from the table that corresponds to the old property type (we cannot delete and save the new
1660    // property or delete a clone of the new property; loading the old property from the BaseProperty table
1661    // doesn't work either).
1662  17 query = session.createQuery(
1663    "select prop from " + oldClassType + " as prop where prop.id.id = :id and prop.id.name= :name");
1664  17 query.setLong("id", property.getId());
1665  17 query.setString("name", property.getName());
1666  17 session.delete(query.uniqueResult());
1667  17 session.save(property);
1668    }
1669   
1670  63049 ((BaseProperty) property).setValueDirty(false);
1671   
1672  63049 if (bTransaction) {
1673  0 endTransaction(context, true);
1674    }
1675    } catch (Exception e) {
1676    // Something went wrong, collect some information.
1677  0 final BaseCollection obj = property.getObject();
1678  0 final Object[] args = { (obj != null) ? obj.getName() : "unknown", property.getName() };
1679   
1680    // Try to roll back the transaction if this is in it's own transaction.
1681  0 try {
1682  0 if (bTransaction) {
1683  0 this.endTransaction(context, false);
1684    }
1685    } catch (Exception ee) {
1686    // Not a lot we can do here if there was an exception committing and an exception rolling back.
1687    }
1688   
1689    // Throw the exception.
1690  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1691    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_LOADING_OBJECT,
1692    "Exception while saving property {1} of object {0}", e, args);
1693    }
1694    }
1695   
 
1696  1719 toggle private void loadAttachmentList(XWikiDocument doc, XWikiContext context, boolean bTransaction) throws XWikiException
1697    {
1698  1719 try {
1699  1719 if (bTransaction) {
1700  0 checkHibernate(context);
1701  0 bTransaction = beginTransaction(null, context);
1702    }
1703  1719 Session session = getSession(context);
1704   
1705  1719 Query query = session.createQuery("from XWikiAttachment as attach where attach.docId=:docid");
1706  1719 query.setLong("docid", doc.getId());
1707  1719 @SuppressWarnings("unchecked")
1708    List<XWikiAttachment> list = query.list();
1709  1719 for (XWikiAttachment attachment : list) {
1710  3461 doc.setAttachment(attachment);
1711    }
1712    } catch (Exception e) {
1713  0 this.logger.error("Failed to load attachments of document [{}]", doc.getDocumentReference(), e);
1714   
1715  0 Object[] args = { doc.getDocumentReference() };
1716  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1717    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SEARCHING_ATTACHMENT,
1718    "Exception while searching attachments for documents {0}", e, args);
1719    } finally {
1720  1719 try {
1721  1719 if (bTransaction) {
1722  0 endTransaction(context, false);
1723    }
1724    } catch (Exception e) {
1725    }
1726    }
1727    }
1728   
 
1729  340 toggle private void saveAttachmentList(XWikiDocument doc, XWikiContext context) throws XWikiException
1730    {
1731  340 try {
1732  340 getSession(context);
1733   
1734  340 List<XWikiAttachment> list = doc.getAttachmentList();
1735  340 for (XWikiAttachment attachment : list) {
1736  850 saveAttachment(attachment, context);
1737    }
1738   
1739    } catch (Exception e) {
1740  0 Object[] args = { doc.getDocumentReference() };
1741  0 throw new XWikiException(XWikiException.MODULE_XWIKI_STORE,
1742    XWikiException.ERROR_XWIKI_STORE_HIBERNATE_SAVING_ATTACHMENT_LIST,
1743    "Exception while saving attachments attachment list of document {0}", e, args);
1744    }
1745    }
1746