Index: core/xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java =================================================================== --- core/xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java (revision 23543) +++ core/xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java (working copy) @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.TreeMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -620,8 +621,102 @@ saveXWikiDoc(doc, context, true); } - public XWikiDocument loadXWikiDoc(XWikiDocument doc, XWikiContext context) throws XWikiException + public XWikiDocument loadXWikiDoc(XWikiDocument doc, XWikiContext context) + throws XWikiException { + if("1".equals(context.getWiki().Param("xwiki.store.hibernate.useExperimentalDocumentLoader"))){ + return newLoadXWikiDoc(doc, context); + } + return defaultLoadXWikiDoc(doc, context); + } + + public XWikiDocument newLoadXWikiDoc(XWikiDocument doc, XWikiContext context) + throws XWikiException + { + boolean bTransaction = true; + MonitorPlugin monitor = Util.getMonitorPlugin(context); + try { + // Start monitoring timer + if (monitor != null) { + monitor.startTimer("hibernate"); + } + doc.setStore(this); + checkHibernate(context); + + bTransaction = beginTransaction(bTransaction, context); + Session session = getSession(context); + session.setFlushMode(FlushMode.MANUAL); + + try { + session.load(doc, new Long(doc.getId())); + doc.setDatabase(context.getDatabase()); + doc.setNew(false); + doc.setMostRecent(true); + // Fix for XWIKI-1651 + doc.setDate(new Date(doc.getDate().getTime())); + doc.setCreationDate(new Date(doc.getCreationDate().getTime())); + doc.setContentUpdateDate(new Date(doc.getContentUpdateDate().getTime())); + + // If the syntaxId is not set, default to XWiki Syntax 1.0 + if (StringUtils.isBlank(doc.getSyntaxId())) { + doc.setSyntaxId(XWikiDocument.XWIKI10_SYNTAXID); + } + + } catch (ObjectNotFoundException e) { // No document + doc.setNew(true); + return doc; + } + + // Loading the attachment list + if (doc.hasElement(XWikiDocument.HAS_ATTACHMENTS)) { + loadAttachmentList(doc, context, false); + } + + //get the BaseClass if there is no BaseClass, we store a new BaseClass + //with the name of the document. + BaseClass bclass = new BaseClass(); + bclass.setName(doc.getFullName()); + if (doc.getxWikiClassXML() != null) { + bclass.fromXML(doc.getxWikiClassXML()); + } else if (useClassesTable(false, context)) { + bclass = loadXWikiClass(bclass, context, false); + } + doc.setxWikiClass(bclass); + // Store this class in the context in case of recursive usage of classes + context.addBaseClass(bclass); + + // load the objects and properties. + if (doc.hasElement(XWikiDocument.HAS_OBJECTS)) { + loadXWikiObjects(doc, context, false); + } + + // We need to ensure that the loaded document becomes the original document + doc.setOriginalDocument((XWikiDocument) doc.clone()); + + if(bTransaction){ + endTransaction(context, false, false); + } + } catch (Exception e) { + Object[] args = {doc.getFullName()}; + throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, + XWikiException.ERROR_XWIKI_STORE_HIBERNATE_READING_DOC, + "Exception while reading document {0}", e, args); + }finally{ + if(bTransaction){ + try{ + endTransaction(context, false, false); + }catch(Exception e){} + } + } + // End monitoring timer + if (monitor != null) { + monitor.endTimer("hibernate"); + } + return doc; + } + + public XWikiDocument defaultLoadXWikiDoc(XWikiDocument doc, XWikiContext context) throws XWikiException + { // To change body of implemented methods use Options | File Templates. boolean bTransaction = true; MonitorPlugin monitor = Util.getMonitorPlugin(context); @@ -980,6 +1075,207 @@ } } + public void loadXWikiObjects(XWikiDocument doc, XWikiContext context, boolean bTransaction) + throws XWikiException + { + Map> xWikiObjects = new TreeMap>(); + //It takes a ridiculously long time to resize a Vector, so we will store the size + //until the end when we will set the sizes of all Vectors and copy in all objects. + Map numberOfObjectsPerClass = new HashMap(); + //must be declared out here for error trapping. + BaseObject thisObject = new BaseObject(); + + try{ + if (bTransaction) { + checkHibernate(context); + bTransaction = beginTransaction(false, context); + } + Session session = getSession(context); + + //get all objects associated with this document + Query q = session.createQuery("select obj from BaseObject obj where obj.name = :name order by obj.id"); + q.setText("name", doc.getFullName()); + List objects = q.list(); + if(objects.size() == 0){return;} + + //index through the list of objects and... + List objectIds = new ArrayList(objects.size()); + for(int i=0; i()); + numberOfObjectsPerClass.put(obj.getClassName(), obj.getNumber()+1); + //If the stored number of objects for this class is too small, store a larger number + }else if(numberOfObjectsPerClass.get(obj.getClassName()) <= obj.getNumber()){ + numberOfObjectsPerClass.put(obj.getClassName(), obj.getNumber()+1); + } + } + //Set the sizes of the vectors. + for(String className : xWikiObjects.keySet()){ + xWikiObjects.get(className).setSize(numberOfObjectsPerClass.get(className)); + } + //put the object pointers into the vectors + //we can still manipulate the objects outside though. + for(BaseObject object : objects){ + xWikiObjects.get(object.getClassName()).set(object.getNumber(), object); + } + + //custom mapping stuff + if(context.getWiki().hasCustomMappings()){ + //make a map of BaseClasses to use for this load opperation + Map classes = new HashMap(); + for(String key : xWikiObjects.keySet()){ + //get the first object of this class so we can determine the class + thisObject = xWikiObjects.get(key).get(0); + //if objects were saved with slots empty, it is not our problem. + if(thisObject == null){ + int a = 1; + while(thisObject == null && a < xWikiObjects.get(key).size()){ + thisObject = xWikiObjects.get(key).get(a); + a++; + } + } + //get the class + if (!thisObject.getName().equals(thisObject.getClassName())) { + classes.put(thisObject.getClassName(), thisObject.getxWikiClass(context)); + } else { + //from this document if this is where it resides + classes.put(thisObject.getClassName(), doc.getxWikiClass()); + } + //here is where the magic happens + if(classes.get(key).hasCustomMapping()){ + BaseClass bclass = classes.get(key); + for(BaseObject obj : xWikiObjects.get(key)){ + //load the value map from the database + Object map = session.load(bclass.getName(), obj.getId()); + //make the object. + bclass.fromValueMap((Map) map, obj); + } + } + } + } + + + //now get a single list of all properties associated with any one of the above id numbers + q = session.createQuery("select prop from BaseProperty prop where prop.id in (:ids) order by prop.id"); + q.setParameterList("ids", objectIds); + List props = q.list(); + +//remove when StringListProperty is mapped to a seperate column------- + q = session.createQuery("select prop.id, prop.classType, prop.value, prop.name from LargeStringProperty prop where prop.id in (:ids)"); + q.setParameterList("ids", objectIds); + List propClassAndValueArrays = q.list(); + HashMap propClassAndValueById = new HashMap(); + for(Object[] propClassAndValueArray : propClassAndValueArrays){ + String[] thisPropClassAndValue = {(String)propClassAndValueArray[1], (String)propClassAndValueArray[2]}; + int id = ((Integer)propClassAndValueArray[0]+(String)propClassAndValueArray[3]).hashCode(); + propClassAndValueById.put(id, thisPropClassAndValue); + } +//-------------------------------------------------------------------- + + //this is one of the slowest ways of sorting unordered data, however + //the data was ordered by the database so it need only be interleaved. + int objectNum = 0; + thisObject = objects.get(objectNum); + int propNum = 0; + BaseProperty thisProp = props.get(propNum); + while(true){ + //because the data is ordered by id, once we find a property whose id + //doesn't match the id of the current object, we assume we are done adding + //properties to that object and that our property will match the next object. + if(thisProp.getId() == thisObject.getId()){ + +//TODO: Re-map schema so each class is mapped to an individual column. +//This is a hack to make it accept 2 objects mapped to the same db column + if(thisProp.getClassType().equals("com.xpn.xwiki.objects.StringListProperty")){ + int id = (thisProp.getId()+thisProp.getName()).hashCode(); + if(propClassAndValueById.containsKey(id) + && !thisProp.getClassType().equals(propClassAndValueById.get(id)[0])) + { + BaseProperty prop2 = (BaseProperty) Class.forName(propClassAndValueById.get(id)[0]).newInstance(); + prop2.setName(thisProp.getName()); + prop2.setPrettyName(thisProp.getPrettyName()); + prop2.setValue(propClassAndValueById.get(id)[1]); + prop2.setId(thisProp.getId()); + prop2.setWiki(thisProp.getWiki()); + thisProp = prop2; + } + } +//Remove this when StringListProperty is mapped to it's own column. + + + if(thisProp.getValue() == null + && thisProp instanceof BaseStringProperty){ + //Null value strings should be interpreted as "" + thisProp.setValue((Object) ""); + } + + thisProp.setObject(thisObject); + thisObject.safeput(thisProp.getName(), (PropertyInterface) thisProp); + propNum++; + //if we run out of properties, we are done. + if(propNum == props.size()){break;} + thisProp = props.get(propNum); + + }else{ + //We are out of properties with matching id, lets move on to the next object. + objectNum++; + if(objectNum == objects.size()){ + //This should never happen, but if the data it out of order we loop through the + //objects once again (until every property has found a home.) + objectNum = 0; + if(log.isWarnEnabled()){ + log.warn("Object numbers rolled over, this means properties are out of order "+ + "this is very bad for performance and could indicate database corruption. Document: "+ + doc.getFullName()); + } + } + thisObject = objects.get(objectNum); + } + } + + if(bTransaction){ + endTransaction(context, false, false); + } + } catch (Exception e) { + Object[] args = {"(failed to get name)", "(failed to get name)", "(failed to get number)"}; + try{args[0] = thisObject.getName(); }catch(Exception f){} + try{args[1] = thisObject.getClass(); }catch(Exception f){} + try{args[2] = Integer.valueOf(thisObject.getNumber()); }catch(Exception f){} + + throw new XWikiException(XWikiException.MODULE_XWIKI_STORE, + XWikiException.ERROR_XWIKI_STORE_HIBERNATE_LOADING_OBJECT, + "Exception while loading object '{0}' of class '{1}' and number '{2}'", e, args); + }finally{ + if(bTransaction){ + try{ + endTransaction(context, false, false); + }catch(Exception e){} + } + } + doc.setxWikiObjects(xWikiObjects); + return; + } + public void loadXWikiObject(BaseObject object, XWikiContext context, boolean bTransaction) throws XWikiException { loadXWikiCollection(object, null, context, bTransaction, false);