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

File DBTreeListClass.java

 

Coverage histogram

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

Code metrics

100
213
20
1
540
379
82
0.38
10.65
20
4.1

Classes

Class Line # Actions
DBTreeListClass 47 213 0% 82 126
0.621621662.2%
 

Contributing tests

This file is covered by 13 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.objects.classes;
21   
22    import java.util.ArrayList;
23    import java.util.HashMap;
24    import java.util.List;
25    import java.util.Map;
26   
27    import javax.script.ScriptContext;
28   
29    import org.apache.commons.lang3.StringUtils;
30    import org.apache.ecs.xhtml.option;
31    import org.apache.ecs.xhtml.select;
32    import org.slf4j.Logger;
33    import org.slf4j.LoggerFactory;
34    import org.xwiki.script.ScriptContextManager;
35   
36    import com.xpn.xwiki.XWikiContext;
37    import com.xpn.xwiki.internal.xml.XMLAttributeValueFilter;
38    import com.xpn.xwiki.objects.BaseCollection;
39    import com.xpn.xwiki.objects.BaseProperty;
40    import com.xpn.xwiki.objects.ListProperty;
41    import com.xpn.xwiki.objects.meta.PropertyMetaClass;
42    import com.xpn.xwiki.web.Utils;
43   
44    /**
45    * @version $Id: 24e3b1f08b900689e4b35849032c831ebff523d8 $
46    */
 
47    public class DBTreeListClass extends DBListClass
48    {
49    private static final String XCLASSNAME = "dbtreelist";
50   
51    private static final Logger LOGGER = LoggerFactory.getLogger(DBTreeListClass.class);
52   
53    /** In-memory cache of the ordered tree values, to be used in case it is supposed to be cached. */
54    private List<ListItem> cachedDBTreeList;
55   
 
56  18 toggle public DBTreeListClass(PropertyMetaClass wclass)
57    {
58  18 super(XCLASSNAME, "DB Tree List", wclass);
59    }
60   
 
61  18 toggle public DBTreeListClass()
62    {
63  18 this(null);
64    }
65   
 
66  55 toggle public String getParentField()
67    {
68  55 return getStringValue("parentField");
69    }
70   
 
71  20 toggle public void setParentField(String parentField)
72    {
73  20 setStringValue("parentField", parentField);
74    }
75   
76    /**
77    * Get the ordered list of tree nodes that is currently cached, if any.
78    *
79    * @param context the current request context
80    * @return the cached list, or {@code null} if not already cached
81    */
 
82  2 toggle protected List<ListItem> getCachedDBTreeList(XWikiContext context)
83    {
84  2 if (isCache()) {
85    // If the property is supposed to be cached long term ({@link #isCache()}), then the list is cached in
86    // memory in the current object
87  0 return this.cachedDBTreeList;
88    } else {
89    // Otherwise, to avoid re-computing the tree in case it is requested several times during the same request,
90    // it is cached in the request context.
91  2 return (List<ListItem>) context.get(context.getWikiId() + ":" + getFieldFullName() + "-tree");
92    }
93    }
94   
95    /**
96    * Store the ordered list of tree nodes in a cache.
97    *
98    * @param cachedDBTreeList the list to cache
99    * @param context the current request context
100    */
 
101  1 toggle protected void setCachedDBTreeList(List<ListItem> cachedDBTreeList, XWikiContext context)
102    {
103  1 if (isCache()) {
104    // If the property is supposed to be cached long term ({@link #isCache()}), then the list is cached in
105    // memory in the current object
106  0 this.cachedDBTreeList = cachedDBTreeList;
107    } else {
108    // Otherwise, to avoid re-computing the tree in case it is requested several times during the same request,
109    // it is cached in the request context.
110  1 context.put(context.getWikiId() + ":" + getFieldFullName() + "-tree", cachedDBTreeList);
111    }
112    }
113   
 
114  2 toggle public Map<String, List<ListItem>> getTreeMap(XWikiContext context)
115    {
116  2 List<ListItem> list = getDBList(context);
117  2 Map<String, List<ListItem>> map = new HashMap<String, List<ListItem>>();
118  2 if ((list == null) || (list.size() == 0)) {
119  0 return map;
120    }
121    // The root of the tree is considered to be the empty string.
122    // Make sure that entries with invalid parents end up in the tree.
123    // TODO: Detect cycles, as these also don't appear in the tree.
124  2 List<String> validParents = this.getList(context);
125  2 for (ListItem item : list) {
126  6 if (validParents.contains(item.getParent())) {
127  0 addToList(map, item.getParent(), item);
128    } else {
129  6 addToList(map, "", item);
130    }
131    }
132  2 return map;
133    }
134   
135    /**
136    * Gets an ordered list of items in the tree. This is necessary to make sure children are coming right after their
137    * parents.
138    *
139    * @param treemap the unordered list of tree nodes
140    * @param map the mapping between a node name and its corresponding tree node
141    * @param context the current request context
142    * @return ordered list of {@code ListItem} tree nodes
143    */
 
144  2 toggle protected List<ListItem> getTreeList(Map<String, List<ListItem>> treemap, Map<String, ListItem> map,
145    XWikiContext context)
146    {
147  2 List<ListItem> list = getCachedDBTreeList(context);
148  2 if (list == null) {
149  1 list = new ArrayList<ListItem>();
150  1 addToTreeList(list, treemap, map, "", context);
151  1 setCachedDBTreeList(list, context);
152    }
153  2 return list;
154    }
155   
 
156  4 toggle protected void addToTreeList(List<ListItem> treelist, Map<String, List<ListItem>> treemap,
157    Map<String, ListItem> map, String parent, XWikiContext context)
158    {
159  4 List<ListItem> list = treemap.get(parent);
160  4 if (list != null) {
161  1 for (ListItem item : list) {
162  3 ListItem item2 =
163    new ListItem(item.getId(), getDisplayValue(item.getId(), "", map, context), item.getParent());
164  3 treelist.add(item2);
165  3 addToTreeList(treelist, treemap, map, item.getId(), context);
166    }
167    }
168    }
169   
 
170  6 toggle protected void addToList(Map<String, List<ListItem>> map, String key, ListItem item)
171    {
172  6 List<ListItem> list = map.get(key);
173  6 if (list == null) {
174  2 list = new ArrayList<ListItem>();
175  2 map.put(key, list);
176    }
177  6 list.add(item);
178    }
179   
 
180  2 toggle @Override
181    public void displayView(StringBuffer buffer, String name, String prefix, BaseCollection object,
182    XWikiContext context)
183    {
184  2 List<String> selectlist;
185  2 BaseProperty prop = (BaseProperty) object.safeget(name);
186  2 if (prop == null) {
187  0 selectlist = new ArrayList<String>();
188  2 } else if (prop instanceof ListProperty) {
189  2 selectlist = ((ListProperty) prop).getList();
190    } else {
191  0 selectlist = new ArrayList<String>();
192  0 selectlist.add(String.valueOf(prop.getValue()));
193    }
194  2 String result = displayFlatView(selectlist, context);
195  2 if (result.equals("")) {
196  2 super.displayView(buffer, name, prefix, object, context);
197    } else {
198  0 buffer.append(result);
199    }
200    }
201   
 
202  0 toggle @Override
203    public void displayEdit(StringBuffer buffer, String name, String prefix, BaseCollection object,
204    XWikiContext context)
205    {
206  0 BaseProperty prop = (BaseProperty) object.safeget(name);
207  0 List<String> selectlist = toList(prop);
208   
209  0 if (isPicker()) {
210  0 String result = displayTree(name, prefix, selectlist, "edit", context);
211  0 if (result.equals("")) {
212  0 displayTreeSelectEdit(buffer, name, prefix, object, context);
213    } else {
214  0 displayHidden(buffer, name, prefix, object, context);
215  0 buffer.append(result);
216    }
217    } else {
218  0 displayTreeSelectEdit(buffer, name, prefix, object, context);
219    }
220    }
221   
 
222  2 toggle private String displayFlatView(List<String> selectlist, XWikiContext context)
223    {
224  2 Map<String, ListItem> map = getMap(context);
225  2 Map<String, List<ListItem>> treemap = getTreeMap(context);
226  2 List<ListItem> fullTreeList = getTreeList(treemap, map, context);
227  2 List<List<ListItem>> resList = new ArrayList<List<ListItem>>(selectlist.size());
228   
229  2 for (String item : selectlist) {
230  3 List<ListItem> itemPath = getItemPath(item, fullTreeList, new ArrayList<ListItem>());
231  3 mergeItems(itemPath, resList);
232    }
233   
234  2 return renderItemsList(resList);
235    }
236   
 
237  2 toggle protected String renderItemsList(List<List<ListItem>> resList)
238    {
239  2 StringBuffer buff = new StringBuffer();
240   
241  2 for (int i = 0; i < resList.size(); i++) {
242  0 List<ListItem> items = resList.get(i);
243  0 for (int j = 0; j < items.size(); j++) {
244  0 ListItem item = items.get(j);
245  0 buff.append(item.getValue());
246  0 if (j < items.size() - 1) {
247  0 buff.append(" &gt; ");
248    }
249    }
250  0 if (i < resList.size() - 1) {
251  0 buff.append("<br />");
252    }
253    }
254  2 return buff.toString();
255    }
256   
 
257  3 toggle private void mergeItems(List<ListItem> itemPath, List<List<ListItem>> resList)
258    {
259  3 if (itemPath == null || itemPath.size() == 0) {
260  3 return;
261    }
262   
263  0 for (int i = 0; i < resList.size(); i++) {
264  0 List<ListItem> items = resList.get(i);
265  0 if (items.size() < itemPath.size()) {
266  0 ListItem item1 = items.get(items.size() - 1);
267  0 ListItem item2 = itemPath.get(items.size() - 1);
268  0 if (item1.equals(item2)) {
269  0 resList.set(i, itemPath);
270  0 return;
271    }
272    } else {
273  0 ListItem item1 = items.get(itemPath.size() - 1);
274  0 ListItem item2 = itemPath.get(itemPath.size() - 1);
275  0 if (item1.equals(item2)) {
276  0 return;
277    }
278    }
279    }
280  0 resList.add(itemPath);
281    }
282   
 
283  3 toggle private List<ListItem> getItemPath(String item, List<ListItem> treeList, ArrayList<ListItem> resList)
284    {
285  3 for (ListItem tmpItem : treeList) {
286  9 if (item.equals(tmpItem.getId())) {
287  0 if (tmpItem.getParent().length() > 0) {
288  0 getItemPath(tmpItem.getParent(), treeList, resList);
289    }
290  0 resList.add(tmpItem);
291  0 return resList;
292    }
293    }
294  3 return null;
295    }
296   
 
297  0 toggle private String displayTree(String name, String prefix, List<String> selectlist, String mode, XWikiContext context)
298    {
299  0 ScriptContextManager scriptManager = Utils.getComponent(ScriptContextManager.class);
300  0 ScriptContext scontext = scriptManager.getCurrentScriptContext();
301   
302  0 Map<String, ListItem> map = getMap(context);
303  0 Map<String, List<ListItem>> treemap = getTreeMap(context);
304   
305  0 scontext.setAttribute("selectlist", selectlist, ScriptContext.ENGINE_SCOPE);
306  0 scontext.setAttribute("fieldname", prefix + name, ScriptContext.ENGINE_SCOPE);
307  0 scontext.setAttribute("tree", map, ScriptContext.ENGINE_SCOPE);
308  0 scontext.setAttribute("treelist", getTreeList(treemap, map, context), ScriptContext.ENGINE_SCOPE);
309  0 scontext.setAttribute("treemap", treemap, ScriptContext.ENGINE_SCOPE);
310  0 scontext.setAttribute("mode", mode, ScriptContext.ENGINE_SCOPE);
311   
312  0 return context.getWiki().parseTemplate("treeview.vm", context);
313    }
314   
 
315  0 toggle protected void addToSelect(select select, List<String> selectlist, Map<String, ListItem> map,
316    Map<String, List<ListItem>> treemap, String parent, String level, XWikiContext context)
317    {
318  0 List<ListItem> list = treemap.get(parent);
319  0 if (list != null) {
320  0 for (ListItem item : list) {
321  0 String display = level + getDisplayValue(item.getId(), "", map, context);
322  0 option option = new option(display, item.getId());
323  0 option.addElement(display);
324  0 if (selectlist.contains(item.getId())) {
325  0 option.setSelected(true);
326    }
327  0 select.addElement(option);
328  0 addToSelect(select, selectlist, map, treemap, item.getId(), level + "\u00A0", context);
329    }
330    }
331    }
332   
 
333  0 toggle protected void displayTreeSelectEdit(StringBuffer buffer, String name, String prefix, BaseCollection object,
334    XWikiContext context)
335    {
336  0 select select = new select(prefix + name, 1);
337  0 select.setAttributeFilter(new XMLAttributeValueFilter());
338  0 select.setMultiple(isMultiSelect());
339  0 select.setSize(getSize());
340  0 select.setName(prefix + name);
341  0 select.setID(prefix + name);
342  0 select.setDisabled(isDisabled());
343   
344  0 Map<String, ListItem> map = getMap(context);
345  0 Map<String, List<ListItem>> treemap = getTreeMap(context);
346  0 List<String> selectlist;
347   
348  0 BaseProperty prop = (BaseProperty) object.safeget(name);
349  0 if (prop == null) {
350  0 selectlist = new ArrayList<String>();
351  0 } else if (prop instanceof ListProperty) {
352  0 selectlist = ((ListProperty) prop).getList();
353    } else {
354  0 selectlist = new ArrayList<String>();
355  0 selectlist.add(String.valueOf(prop.getValue()));
356    }
357   
358    // Add options from Set
359  0 addToSelect(select, selectlist, map, treemap, "", "", context);
360  0 buffer.append(select.toString());
361    }
362   
363    /**
364    * <p>
365    * Computes the query corresponding to the current XProperty. The query is either manually specified by the XClass
366    * creator in the <tt>sql</tt> field, or, if the query field is blank, constructed using the <tt>classname</tt>,
367    * <tt>idField</tt>, <tt>valueField</tt> and <tt>parentField</tt> properties. The query is constructed according to
368    * the following rules:
369    * </p>
370    * <ul>
371    * <li>If no classname, id and value fields are selected, return a query that return no rows, as the parent is not
372    * enough to make a query.</li>
373    * <li>If no parent field is provided, use the document "parent" medatada.</li>
374    * <li>If only the classname is provided, select all document names which have an object of that type, preserving
375    * the hierarchy defined by the parent field.</li>
376    * <li>If only one of id and value is provided, use it for both columns.</li>
377    * <li>If no classname is provided, assume the fields are document properties.</li>
378    * <li>If the document is not used at all, don't put it in the query.</li>
379    * <li>If the object is not used at all, don't put it in the query.</li>
380    * </ul>
381    * <p>
382    * The generated query always selects 3 columns, the first one is used as the stored value, the second one as the
383    * displayed value, and the third one defines the "parent" of the current value.
384    * </p>
385    *
386    * @param context The current {@link XWikiContext context}.
387    * @return The HQL query corresponding to this property.
388    */
 
389  57 toggle @Override
390    public String getQuery(XWikiContext context)
391    {
392    // First, get the hql query entered by the user.
393  57 String sql = getSql();
394    // If the query field is blank, construct a query using the classname, idField,
395    // valueField and parentField properties.
396  57 if (StringUtils.isBlank(sql)) {
397  55 if (context.getWiki().getHibernateStore() != null) {
398    // Extract the 3 properties in non-null variables.
399  55 String classname = StringUtils.defaultString(getClassname());
400  55 String idField = StringUtils.defaultString(getIdField());
401  55 String valueField = StringUtils.defaultString(getValueField());
402  55 String parentField = StringUtils.defaultString(getParentField());
403   
404    // Check if the properties are specified or not.
405  55 boolean hasClassname = !StringUtils.isBlank(classname);
406  55 boolean hasIdField = !StringUtils.isBlank(idField);
407  55 boolean hasValueField = !StringUtils.isBlank(valueField);
408  55 boolean hasParentField = !StringUtils.isBlank(parentField);
409   
410  55 if (!(hasIdField || hasValueField)) {
411    // If only the classname is specified, return a query that selects all the
412    // document names which have an object of that type, and the hierarchy is
413    // defined by the document "parent" property (unless a parent property is
414    // specified).
415  3 if (hasClassname) {
416  2 sql = "select distinct doc.fullName, doc.fullName, "
417  2 + (hasParentField ? parentField : "doc.parent")
418    + " from XWikiDocument as doc, BaseObject as obj"
419    + " where doc.fullName=obj.name and obj.className='" + classname + "'";
420    } else {
421    // If none of the first 3 properties is specified, return a query that
422    // always returns no rows (only with the parent field no query can be made)
423  1 sql = DEFAULT_QUERY;
424    }
425  3 return sql;
426    }
427   
428    // If only one of the id and value fields is specified, use it for both columns.
429  52 if (!hasIdField && hasValueField) {
430  1 idField = valueField;
431  51 } else if (hasIdField && !hasValueField) {
432  26 valueField = idField;
433    }
434   
435    // If no parent field was specified, use the document "parent" metadata
436  52 if (!hasParentField) {
437  24 parentField = "doc.parent";
438    }
439   
440    // Check if the document and object are needed or not.
441    // The object is needed if there is a classname, or if at least one of the selected
442    // columns is an object property.
443  52 boolean usesObj = hasClassname || idField.startsWith("obj.") || valueField.startsWith("obj.")
444    || parentField.startsWith("obj.");
445    // The document is needed if one of the selected columns is a document property, or
446    // if there is no classname specified and at least one of the selected columns is
447    // not an object property.
448  52 boolean usesDoc =
449    idField.startsWith("doc.") || valueField.startsWith("doc.") || parentField.startsWith("doc.");
450  52 if ((!idField.startsWith("obj.") || !valueField.startsWith("obj.") || !parentField.startsWith("obj."))
451    && !hasClassname) {
452  27 usesDoc = true;
453    }
454   
455    // Build the query in this variable.
456  52 StringBuffer select = new StringBuffer("select distinct ");
457    // These will hold the components of the from and where parts of the query.
458  52 ArrayList<String> fromStatements = new ArrayList<String>();
459  52 ArrayList<String> whereStatements = new ArrayList<String>();
460   
461    // Add the document to the query only if it is needed.
462  52 if (usesDoc) {
463  41 fromStatements.add("XWikiDocument as doc");
464  41 if (usesObj) {
465  26 whereStatements.add("doc.fullName=obj.name");
466    }
467    }
468    // Add the object to the query only if it is needed.
469  52 if (usesObj) {
470  37 fromStatements.add("BaseObject as obj");
471  37 if (hasClassname) {
472  23 whereStatements.add("obj.className='" + classname + "'");
473    }
474    }
475   
476    // Add the first column to the query.
477  52 if (idField.startsWith("doc.") || idField.startsWith("obj.")) {
478  32 select.append(idField);
479  20 } else if (!hasClassname) {
480  9 select.append("doc." + idField);
481    } else {
482  11 select.append("idprop.value");
483  11 fromStatements.add("StringProperty as idprop");
484  11 whereStatements.add("obj.id=idprop.id.id and idprop.id.name='" + idField + "'");
485    }
486   
487    // Add the second column to the query.
488  52 if (valueField.startsWith("doc.") || valueField.startsWith("obj.")) {
489  31 select.append(", ").append(valueField);
490  21 } else if (!hasClassname) {
491  9 select.append(", doc." + valueField);
492    } else {
493  12 if (valueField.equals(idField)) {
494  7 select.append(", idprop.value");
495    } else {
496  5 select.append(", valueprop.value");
497  5 fromStatements.add("StringProperty as valueprop");
498  5 whereStatements.add("obj.id=valueprop.id.id and valueprop.id.name='" + valueField + "'");
499    }
500    }
501   
502    // Add the third column to the query.
503  52 if (parentField.startsWith("doc.") || parentField.startsWith("obj.")) {
504  40 select.append(", ").append(parentField);
505  12 } else if (!hasClassname) {
506  4 select.append(", doc." + parentField);
507    } else {
508  8 if (parentField.equals(idField)) {
509  3 select.append(", idprop.value");
510  5 } else if (parentField.equals(valueField)) {
511  1 select.append(", valueprop.value");
512    } else {
513  4 select.append(", parentprop.value");
514  4 fromStatements.add("StringProperty as parentprop");
515  4 whereStatements.add("obj.id=parentprop.id.id and parentprop.id.name='" + parentField + "'");
516    }
517    }
518    // Let's create the complete query
519  52 select.append(" from ");
520  52 select.append(StringUtils.join(fromStatements.iterator(), ", "));
521  52 if (whereStatements.size() > 0) {
522  35 select.append(" where ");
523  35 select.append(StringUtils.join(whereStatements.iterator(), " and "));
524    }
525  52 sql = select.toString();
526    } else {
527    // TODO: query plugin impl.
528    // We need to generate the right query for the query plugin
529    }
530    }
531    // Parse the query, so that it can contain velocity scripts, for example to use the
532    // current document name, or the current username.
533  54 try {
534  54 sql = context.getWiki().parseContent(sql, context);
535    } catch (Exception e) {
536  0 LOGGER.error("Failed to parse SQL script [" + sql + "]. Continuing with non-rendered script.", e);
537    }
538  54 return sql;
539    }
540    }