1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.store

File XWikiHibernateStore.java

 

Coverage histogram

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

Code metrics

542
1,268
107
1
3,130
2,416
475
0.37
11.85
107
4.44

Classes

Class Line # Actions
XWikiHibernateStore 116 1,268 0% 475 655
0.658320365.8%
 

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