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

File DBListClass.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart5.png
74% of files have more coverage

Code metrics

110
252
24
1
597
441
92
0.37
10.5
24
3.83

Classes

Class Line # Actions
DBListClass 46 252 0% 92 199
0.4844559748.4%
 

Contributing tests

This file is covered by 14 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.Collections;
24    import java.util.HashMap;
25    import java.util.List;
26    import java.util.Map;
27    import java.util.StringTokenizer;
28   
29    import org.apache.commons.lang3.StringUtils;
30    import org.apache.ecs.xhtml.input;
31    import org.slf4j.Logger;
32    import org.slf4j.LoggerFactory;
33    import org.xwiki.model.EntityType;
34    import org.xwiki.query.Query;
35    import org.xwiki.query.QueryManager;
36   
37    import com.xpn.xwiki.XWiki;
38    import com.xpn.xwiki.XWikiContext;
39    import com.xpn.xwiki.internal.xml.XMLAttributeValueFilter;
40    import com.xpn.xwiki.objects.BaseCollection;
41    import com.xpn.xwiki.objects.BaseProperty;
42    import com.xpn.xwiki.objects.ListProperty;
43    import com.xpn.xwiki.objects.meta.PropertyMetaClass;
44    import com.xpn.xwiki.web.Utils;
45   
 
46    public class DBListClass extends ListClass
47    {
48    private static final String XCLASSNAME = "dblist";
49   
50    /**
51    * Logging helper object.
52    */
53    private static final Logger LOGGER = LoggerFactory.getLogger(DBListClass.class);
54   
55    protected static final String DEFAULT_QUERY = "select doc.name from XWikiDocument doc where 1 = 0";
56   
57    private List<ListItem> cachedDBList;
58   
 
59  18 toggle public DBListClass(String name, String prettyname, PropertyMetaClass wclass)
60    {
61  18 super(name, prettyname, wclass);
62    }
63   
 
64  689 toggle public DBListClass(PropertyMetaClass wclass)
65    {
66  689 super(XCLASSNAME, "DB List", wclass);
67    }
68   
 
69  689 toggle public DBListClass()
70    {
71  689 this(null);
72    }
73   
 
74  67 toggle public List<ListItem> makeList(List<Object> list)
75    {
76  67 List<ListItem> result = new ArrayList<ListItem>();
77  67 for (Object item : list) {
78    // Oracle databases treat NULL and empty strings similarly. Thus the list passed
79    // as parameter can have some elements being NULL (for XWiki string properties which
80    // were empty strings). This means we need to check for NULL and ignore NULL entries
81    // from the list.
82  262 if (item != null) {
83  262 if (item instanceof String) {
84  21 result.add(new ListItem((String) item));
85    } else {
86  241 Object[] res = (Object[]) item;
87  241 if (res.length == 1) {
88  0 result.add(new ListItem(toStringButEmptyIfNull(res[0])));
89  241 } else if (res.length == 2) {
90  241 result.add(new ListItem(toStringButEmptyIfNull(res[0]), toStringButEmptyIfNull(res[1])));
91    } else {
92  0 result.add(new ListItem(toStringButEmptyIfNull(res[0]), toStringButEmptyIfNull(res[1]),
93    toStringButEmptyIfNull(res[2])));
94    }
95    }
96    }
97    }
98  67 return result;
99    }
100   
 
101  482 toggle private String toStringButEmptyIfNull(Object object)
102    {
103  482 if (object == null) {
104  0 return "";
105    } else {
106  482 return object.toString();
107    }
108    }
109   
 
110  125 toggle public List<ListItem> getDBList(XWikiContext context)
111    {
112  125 List<ListItem> list = getCachedDBList(context);
113  125 if (list == null) {
114  67 String hqlQuery = getQuery(context);
115   
116  67 if (hqlQuery == null) {
117  0 list = new ArrayList<ListItem>();
118    } else {
119  67 try {
120    // We need the query manager
121  67 QueryManager queryManager = Utils.getComponent(QueryManager.class);
122    // We create the query
123  67 Query query = queryManager.createQuery(hqlQuery, Query.HQL);
124    // The DBlist may come from an other wiki
125  67 String wikiName = getReference().extractReference(EntityType.WIKI).getName();
126  67 query.setWiki(wikiName);
127    // We execute the query to create the list of values.
128  67 list = makeList(query.execute());
129    } catch (Exception e) {
130  0 LOGGER.error("Failed to get the list", e);
131  0 list = new ArrayList<ListItem>();
132    }
133    }
134  67 setCachedDBList(list, context);
135    }
136  125 return list;
137    }
138   
 
139  38 toggle @Override
140    public List<String> getList(XWikiContext context)
141    {
142  38 List<ListItem> dblist = getDBList(context);
143   
144  38 String sort = getSort();
145   
146  38 if ("id".equals(sort)) {
147  0 Collections.sort(dblist, ListItem.ID_COMPARATOR);
148  38 } else if ("value".equals(sort)) {
149  28 Collections.sort(dblist, ListItem.VALUE_COMPARATOR);
150    }
151   
152  38 List<String> result = new ArrayList<String>(dblist.size());
153  38 for (ListItem value : dblist) {
154  191 result.add(value.getId());
155    }
156  38 return result;
157    }
158   
 
159  85 toggle @Override
160    public Map<String, ListItem> getMap(XWikiContext context)
161    {
162  85 List<ListItem> list = getDBList(context);
163  85 Map<String, ListItem> result = new HashMap<String, ListItem>();
164  85 if ((list == null) || (list.size() == 0)) {
165  41 return result;
166    }
167  341 for (int i = 0; i < list.size(); i++) {
168  297 Object res = list.get(i);
169  297 if (res instanceof String) {
170  0 result.put((String) res, new ListItem((String) res));
171    } else {
172  297 ListItem item = (ListItem) res;
173  297 result.put(item.getId(), item);
174    }
175    }
176  44 return result;
177    }
178   
179    /**
180    * <p>
181    * Computes the query corresponding to the current XProperty. The query is either manually specified by the XClass
182    * creator in the <tt>sql</tt> field, or, if the query field is blank, constructed using the <tt>classname</tt>,
183    * <tt>idField</tt> and <tt>valueField</tt> properties. The query is constructed according to the following rules:
184    * </p>
185    * <ul>
186    * <li>If no classname, id or value fields are selected, return a query that return no rows.</li>
187    * <li>If only the classname is provided, select all document names which have an object of that type.</li>
188    * <li>If only one of id and value is provided, select just one column.</li>
189    * <li>If id = value, select just one column.</li>
190    * <li>If no classname is provided, assume the fields are document properties.</li>
191    * <li>If the document is not used at all, don't put it in the query.</li>
192    * <li>If the object is not used at all, don't put it in the query.</li>
193    * </ul>
194    * <p>
195    * If there are two columns selected, use the first one as the stored value and the second one as the displayed
196    * value.
197    * </p>
198    *
199    * @param context The current {@link XWikiContext context}.
200    * @return The HQL query corresponding to this property.
201    */
 
202  66 toggle public String getQuery(XWikiContext context)
203    {
204    // First, get the hql query entered by the user.
205  66 String sql = getSql();
206    // If the query field is blank, construct a query using the classname, idField and
207    // valueField properties.
208  66 if (StringUtils.isBlank(sql)) {
209  54 if (context.getWiki().getHibernateStore() != null) {
210    // Extract the 3 properties in non-null variables.
211  54 String classname = StringUtils.defaultString(getClassname());
212  54 String idField = StringUtils.defaultString(getIdField());
213  54 String valueField = StringUtils.defaultString(getValueField());
214   
215    // Check if the properties are specified or not.
216  54 boolean hasClassname = !StringUtils.isBlank(classname);
217  54 boolean hasIdField = !StringUtils.isBlank(idField);
218  54 boolean hasValueField = !StringUtils.isBlank(valueField);
219   
220  54 if (!(hasIdField || hasValueField)) {
221    // If only the classname is specified, return a query that selects all the
222    // document names which have an object of that type.
223  21 if (hasClassname) {
224  0 sql =
225    "select distinct doc.fullName from XWikiDocument as doc, BaseObject as obj"
226    + " where doc.fullName=obj.name and obj.className='" + classname + "'";
227    } else {
228    // If none of the 3 properties is specified, return a query that always
229    // returns no rows.
230  21 sql = DEFAULT_QUERY;
231    }
232  21 return sql;
233    }
234   
235    // If the value field is specified, but the id isn't, swap them.
236  33 if (!hasIdField && hasValueField) {
237  0 idField = valueField;
238  0 valueField = "";
239  0 hasValueField = false;
240  33 } else if (idField.equals(valueField)) {
241    // If the value field is the same as the id field, ignore it.
242  0 hasValueField = false;
243    }
244   
245    // Check if the document and object are needed or not.
246    // The object is needed if there is a classname, or if at least one of the selected
247    // columns is an object property.
248  33 boolean usesObj = hasClassname || idField.startsWith("obj.") || valueField.startsWith("obj.");
249    // The document is needed if one of the selected columns is a document property, or
250    // if there is no classname specified and at least one of the selected columns is
251    // not an object property.
252  33 boolean usesDoc = idField.startsWith("doc.") || valueField.startsWith("doc.");
253  33 if ((!idField.startsWith("obj.") || (hasValueField && !valueField.startsWith("obj."))) && !hasClassname) {
254  0 usesDoc = true;
255    }
256   
257    // Build the query in this variable.
258  33 StringBuffer select = new StringBuffer("select distinct ");
259    // These will hold the components of the from and where parts of the query.
260  33 List<String> fromStatements = new ArrayList<String>();
261  33 List<String> whereStatements = new ArrayList<String>();
262   
263    // Add the document to the query only if it is needed.
264  33 if (usesDoc) {
265  0 fromStatements.add("XWikiDocument as doc");
266  0 if (usesObj) {
267  0 whereStatements.add("doc.fullName=obj.name");
268    }
269    }
270    // Add the object to the query only if it is needed.
271  33 if (usesObj) {
272  33 fromStatements.add("BaseObject as obj");
273  33 if (hasClassname) {
274  33 whereStatements.add("obj.className='" + classname + "'");
275    }
276    }
277   
278    // Add the first column to the query.
279  33 if (idField.startsWith("doc.") || idField.startsWith("obj.")) {
280  0 select.append(idField);
281  33 } else if (!hasClassname) {
282  0 select.append("doc." + idField);
283    } else {
284  33 select.append("idprop.value");
285  33 fromStatements.add("StringProperty as idprop");
286  33 whereStatements.add("obj.id=idprop.id.id and idprop.id.name='" + idField + "'");
287    }
288   
289    // If specified, add the second column to the query.
290  33 if (hasValueField) {
291  33 if (valueField.startsWith("doc.") || valueField.startsWith("obj.")) {
292  0 select.append(", ").append(valueField);
293  33 } else if (!hasClassname) {
294  0 select.append(", doc." + valueField);
295    } else {
296  33 select.append(", valueprop.value");
297  33 fromStatements.add("StringProperty as valueprop");
298  33 whereStatements.add("obj.id=valueprop.id.id and valueprop.id.name='" + valueField + "'");
299    }
300    }
301    // Let's create the complete query
302  33 select.append(" from ");
303  33 select.append(StringUtils.join(fromStatements.iterator(), ", "));
304  33 if (whereStatements.size() > 0) {
305  33 select.append(" where ");
306  33 select.append(StringUtils.join(whereStatements.iterator(), " and "));
307    }
308  33 sql = select.toString();
309    } else {
310    // TODO: query plugin impl.
311    // We need to generate the right query for the query plugin
312    }
313    }
314    // Parse the query, so that it can contain velocity scripts, for example to use the
315    // current document name, or the current username.
316  45 try {
317  45 sql = context.getWiki().parseContent(sql, context);
318    } catch (Exception e) {
319  0 LOGGER.error("Failed to parse SQL script [{}]. Continuing with non-rendered script.", sql, e);
320    }
321   
322  45 return sql;
323    }
324   
 
325  124 toggle public String getSql()
326    {
327  124 return getLargeStringValue("sql");
328    }
329   
 
330  83 toggle public void setSql(String sql)
331    {
332  83 setLargeStringValue("sql", sql);
333    }
334   
 
335  109 toggle public String getClassname()
336    {
337  109 return getStringValue("classname");
338    }
339   
 
340  6 toggle public void setClassname(String classname)
341    {
342  6 setStringValue("classname", classname);
343    }
344   
 
345  109 toggle public String getIdField()
346    {
347  109 return getStringValue("idField");
348    }
349   
 
350  29 toggle public void setIdField(String idField)
351    {
352  29 setStringValue("idField", idField);
353    }
354   
 
355  109 toggle public String getValueField()
356    {
357  109 return getStringValue("valueField");
358    }
359   
 
360  21 toggle public void setValueField(String valueField)
361    {
362  21 setStringValue("valueField", valueField);
363    }
364   
 
365  125 toggle public List<ListItem> getCachedDBList(XWikiContext context)
366    {
367  125 if (isCache()) {
368  0 return this.cachedDBList;
369    } else {
370  125 return (List<ListItem>) context.get(context.getWikiId() + ":" + getFieldFullName());
371    }
372    }
373   
 
374  67 toggle public void setCachedDBList(List<ListItem> cachedDBList, XWikiContext context)
375    {
376  67 if (isCache()) {
377  0 this.cachedDBList = cachedDBList;
378    } else {
379  67 context.put(context.getWikiId() + ":" + getFieldFullName(), cachedDBList);
380    }
381    }
382   
 
383  0 toggle @Override
384    public void flushCache()
385    {
386  0 this.cachedDBList = null;
387  0 super.flushCache();
388    }
389   
390    // return first or second column from user query
 
391  0 toggle public String returnCol(String hqlQuery, boolean first)
392    {
393  0 String firstCol = "-", secondCol = "-";
394  0 if (StringUtils.isEmpty(hqlQuery)) {
395  0 return firstCol;
396    }
397   
398  0 int fromIndx = hqlQuery.toLowerCase().indexOf("from");
399   
400  0 if (fromIndx > 0) {
401  0 String beforeFrom = hqlQuery.substring(0, fromIndx).replaceAll("\\s+", " ");
402  0 int commaIndex = beforeFrom.indexOf(",");
403   
404    // There are two columns selected
405  0 if (commaIndex > 0) {
406  0 StringTokenizer st = new StringTokenizer(beforeFrom, " ,()", true);
407  0 ArrayList<String> words = new ArrayList<String>();
408   
409  0 while (st.hasMoreTokens()) {
410  0 words.add(st.nextToken());
411    }
412   
413  0 int comma = words.indexOf(",") - 1;
414  0 while (words.get(comma).toString().compareTo(" ") == 0) {
415  0 comma--;
416    }
417  0 firstCol = words.get(comma).toString().trim();
418   
419  0 comma = words.indexOf(",") + 1;
420  0 while (words.get(comma).toString().compareTo(" ") == 0) {
421  0 comma++;
422    }
423   
424  0 if (words.get(comma).toString().compareTo("(") == 0) {
425  0 int i = comma + 1;
426  0 while (words.get(i).toString().compareTo(")") != 0) {
427  0 secondCol += words.get(i).toString();
428  0 i++;
429    }
430  0 secondCol += ")";
431    } else {
432  0 secondCol = words.get(comma).toString().trim();
433    }
434    }
435    // Only one column selected
436    else {
437  0 firstCol = StringUtils.substringAfterLast(beforeFrom.trim(), " ");
438    }
439    }
440  0 if (first == true) {
441  0 return firstCol;
442    } else {
443  0 return secondCol;
444    }
445    }
446   
447    // the result of the second query, to retrieve the value
 
448  0 toggle public String getValue(String val, String sql, XWikiContext context)
449    {
450  0 String lowerCaseSQL = sql.toLowerCase();
451   
452    // Make sure the query does not contain ORDER BY, as it will fail in certain databases.
453    // TODO: dangerous: "order by" could be inside a string in the query
454  0 int orderByPos = lowerCaseSQL.lastIndexOf("order by ");
455  0 if (orderByPos >= 0) {
456  0 sql = sql.substring(0, orderByPos);
457    }
458  0 String firstCol = returnCol(sql, true);
459  0 String secondCol = returnCol(sql, false);
460   
461  0 String newsql = sql.substring(0, sql.indexOf(firstCol));
462  0 newsql += secondCol + " ";
463    // TODO: dangerous: "from" could be inside a string in the query
464  0 newsql += sql.substring(lowerCaseSQL.indexOf("from "));
465  0 newsql += "and " + firstCol + "='" + val + "'";
466   
467  0 Object[] list = null;
468  0 XWiki xwiki = context.getWiki();
469  0 String res = "";
470  0 try {
471  0 list = xwiki.search(newsql, context).toArray();
472  0 if (list.length > 0) {
473  0 res = list[0].toString();
474    }
475    } catch (Exception e) {
476  0 e.printStackTrace();
477    }
478  0 return res;
479    }
480   
481    // override the method from parent ListClass
 
482  28 toggle @Override
483    public void displayEdit(StringBuffer buffer, String name, String prefix, BaseCollection object, XWikiContext context)
484    {
485    // input display
486  28 if (getDisplayType().equals(DISPLAYTYPE_INPUT)) {
487  0 input input = new input();
488  0 input.setAttributeFilter(new XMLAttributeValueFilter());
489  0 input.setType("text");
490  0 input.setSize(getSize());
491  0 boolean changeInputName = false;
492  0 boolean setInpVal = true;
493   
494  0 BaseProperty prop = (BaseProperty) object.safeget(name);
495  0 String value = "";
496  0 String databaseValue = "";
497  0 if (prop != null) {
498  0 value = this.toFormString(prop);
499  0 databaseValue = prop.toText();
500    }
501   
502  0 if (isPicker()) {
503  0 input.setClass("suggested");
504  0 String path = "";
505  0 XWiki xwiki = context.getWiki();
506  0 path = xwiki.getURL("Main.WebHome", "view", context);
507  0 String classname = this.getObject().getName();
508  0 String fieldname = this.getName();
509  0 String hibquery = this.getSql();
510  0 String secondCol = "-", firstCol = "-";
511   
512  0 if (hibquery != null && !hibquery.equals("")) {
513  0 firstCol = returnCol(hibquery, true);
514  0 secondCol = returnCol(hibquery, false);
515   
516  0 if (secondCol.compareTo("-") != 0) {
517  0 changeInputName = true;
518  0 input hidden = new input();
519  0 hidden.setAttributeFilter(new XMLAttributeValueFilter());
520  0 hidden.setID(prefix + name);
521  0 hidden.setName(prefix + name);
522  0 hidden.setType("hidden");
523  0 hidden.setDisabled(isDisabled());
524  0 if (StringUtils.isNotEmpty(value)) {
525  0 hidden.setValue(value);
526    }
527  0 buffer.append(hidden.toString());
528   
529  0 input.setValue(getValue(databaseValue, hibquery, context));
530  0 setInpVal = false;
531    }
532    }
533   
534  0 String script =
535    "\"" + path + "?xpage=suggest&classname=" + classname + "&fieldname=" + fieldname + "&firCol="
536    + firstCol + "&secCol=" + secondCol + "&\"";
537  0 String varname = "\"input\"";
538  0 String seps = "\"" + this.getSeparators() + "\"";
539  0 if (isMultiSelect()) {
540  0 input.setOnFocus("new ajaxSuggest(this, {script:" + script + ", varname:" + varname + ", seps:"
541    + seps + "} )");
542    } else {
543  0 input.setOnFocus("new ajaxSuggest(this, {script:" + script + ", varname:" + varname + "} )");
544    }
545    }
546   
547  0 if (changeInputName == true) {
548  0 input.setName(prefix + name + "_suggest");
549  0 input.setID(prefix + name + "_suggest");
550    } else {
551  0 input.setName(prefix + name);
552  0 input.setID(prefix + name);
553    }
554  0 if (setInpVal == true) {
555  0 input.setValue(value);
556    }
557   
558  0 input.setDisabled(isDisabled());
559  0 buffer.append(input.toString());
560  28 } else if (getDisplayType().equals(DISPLAYTYPE_RADIO) || getDisplayType().equals(DISPLAYTYPE_CHECKBOX)) {
561  0 displayRadioEdit(buffer, name, prefix, object, context);
562    } else {
563  28 displaySelectEdit(buffer, name, prefix, object, context);
564    }
565   
566  28 if (!getDisplayType().equals("input")) {
567  28 org.apache.ecs.xhtml.input hidden = new input(input.hidden, prefix + name, "");
568  28 hidden.setAttributeFilter(new XMLAttributeValueFilter());
569  28 buffer.append(hidden);
570    }
571    }
572   
 
573  47 toggle @Override
574    public void displayView(StringBuffer buffer, String name, String prefix, BaseCollection object, XWikiContext context)
575    {
576  47 List<String> selectlist;
577  47 String separator = getSeparator();
578  47 BaseProperty prop = (BaseProperty) object.safeget(name);
579  47 Map<String, ListItem> map = getMap(context);
580   
581    // Skip unset values.
582  47 if (prop == null) {
583  0 return;
584    }
585   
586  47 if (prop instanceof ListProperty) {
587  39 selectlist = ((ListProperty) prop).getList();
588  39 List<String> newlist = new ArrayList<String>();
589  39 for (String entry : selectlist) {
590  42 newlist.add(getDisplayValue(entry, name, map, context));
591    }
592  39 buffer.append(StringUtils.join(newlist, separator));
593    } else {
594  8 buffer.append(getDisplayValue(prop.getValue(), name, map, context));
595    }
596    }
597    }