View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.component;
21  
22  import org.apache.myfaces.tobago.event.SortActionEvent;
23  import org.apache.myfaces.tobago.internal.component.AbstractUICommand;
24  import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
25  import org.apache.myfaces.tobago.internal.util.StringUtils;
26  import org.apache.myfaces.tobago.model.SheetState;
27  import org.apache.myfaces.tobago.util.BeanComparator;
28  import org.apache.myfaces.tobago.util.ValueExpressionComparator;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import javax.el.ValueExpression;
33  import javax.faces.component.UIColumn;
34  import javax.faces.component.UICommand;
35  import javax.faces.component.UIComponent;
36  import javax.faces.component.UIInput;
37  import javax.faces.component.UIOutput;
38  import javax.faces.component.UISelectBoolean;
39  import javax.faces.component.UISelectMany;
40  import javax.faces.component.UISelectOne;
41  import javax.faces.context.FacesContext;
42  import javax.faces.model.DataModel;
43  import java.util.ArrayList;
44  import java.util.Arrays;
45  import java.util.Collections;
46  import java.util.Comparator;
47  import java.util.List;
48  
49  public class Sorter {
50  
51    private static final Logger LOG = LoggerFactory.getLogger(Sorter.class);
52  
53    private Comparator comparator;
54  
55    /**
56     * @deprecated Please use {@link #perform(org.apache.myfaces.tobago.internal.component.AbstractUISheet)}
57     */
58    @Deprecated
59    public void perform(final SortActionEvent sortEvent) {
60      final AbstractUISheet data = (AbstractUISheet) sortEvent.getComponent();
61      perform(data);
62    }
63  
64    public void perform(final AbstractUISheet data) {
65  
66      Object value = data.getValue();
67      if (value instanceof DataModel) {
68        value = ((DataModel) value).getWrappedData();
69      }
70      final FacesContext facesContext = FacesContext.getCurrentInstance();
71      final SheetState sheetState = data.getSheetState(facesContext);
72  
73      final String sortedColumnId = sheetState.getSortedColumnId();
74      if (LOG.isDebugEnabled()) {
75        LOG.debug("sorterId = '{}'", sortedColumnId);
76      }
77  
78      if (sortedColumnId == null) {
79        // not to be sorted
80        return;
81      }
82  
83      final UIColumn column = (UIColumn) data.findComponent(sortedColumnId);
84      if (column == null) {
85        LOG.warn("No column to sort found, sorterId = '{}'", sortedColumnId);
86        return;
87      }
88  
89      final Comparator actualComparator;
90  
91      if (value instanceof List || value instanceof Object[]) {
92        final String sortProperty;
93  
94        try {
95          final UIComponent child = getFirstSortableChild(column.getChildren());
96          if (child != null) {
97  
98            final String attributeName = child instanceof AbstractUICommand ? Attributes.LABEL : Attributes.VALUE;
99            if (child.getValueExpression(attributeName) != null) {
100             final String var = data.getVar();
101             if (var == null) {
102                 LOG.error("No sorting performed. Property var of sheet is not set!");
103                 unsetSortableAttribute(column);
104                 return;
105             }
106             String expressionString = child.getValueExpression(attributeName).getExpressionString();
107             if (isSimpleProperty(expressionString)) {
108               if (expressionString.startsWith("#{")
109                   && expressionString.endsWith("}")) {
110                 expressionString =
111                     expressionString.substring(2,
112                         expressionString.length() - 1);
113               }
114               sortProperty = expressionString.substring(var.length() + 1);
115 
116               actualComparator = new BeanComparator(
117                   sortProperty, comparator, !sheetState.isAscending());
118 
119               if (LOG.isDebugEnabled()) {
120                 LOG.debug("Sort property is {}", sortProperty);
121               }
122             } else {
123 
124               final boolean descending = !sheetState.isAscending();
125               final ValueExpression expression = child.getValueExpression("value");
126               actualComparator = new ValueExpressionComparator(facesContext, var, expression, descending, comparator);
127             }
128           } else {
129               LOG.error("No sorting performed. No Expression target found for sorting!");
130               unsetSortableAttribute(column);
131               return;
132           }
133         } else {
134           LOG.error("No sorting performed. Value is not instanceof List or Object[]!");
135           unsetSortableAttribute(column);
136           return;
137         }
138       } catch (final Exception e) {
139         LOG.error("Error while extracting sortMethod :" + e.getMessage(), e);
140         if (column != null) {
141           unsetSortableAttribute(column);
142         }
143         return;
144       }
145 
146       // TODO: locale / comparator parameter?
147       // don't compare numbers with Collator.getInstance() comparator
148 //        Comparator comparator = Collator.getInstance();
149 //          comparator = new RowComparator(ascending, method);
150 
151       // memorize selected rows
152       List<Object> selectedDataRows = null;
153       if (sheetState.getSelectedRows().size() > 0) {
154         selectedDataRows = new ArrayList<Object>(sheetState.getSelectedRows().size());
155         Object dataRow;
156         for (final Integer index : sheetState.getSelectedRows()) {
157           if (value instanceof List) {
158             dataRow = ((List) value).get(index);
159           } else {
160             dataRow = ((Object[]) value)[index];
161           }
162           selectedDataRows.add(dataRow);
163         }
164       }
165 
166       // do sorting
167       if (value instanceof List) {
168         Collections.sort((List) value, actualComparator);
169       } else { // value is instanceof Object[]
170         Arrays.sort((Object[]) value, actualComparator);
171       }
172 
173       // restore selected rows
174       if (selectedDataRows != null) {
175         sheetState.getSelectedRows().clear();
176         for (final Object dataRow : selectedDataRows) {
177           int index = -1;
178           if (value instanceof List) {
179             for (int i = 0; i < ((List) value).size() && index < 0; i++) {
180               if (dataRow == ((List) value).get(i)) {
181                 index = i;
182               }
183             }
184           } else {
185             for (int i = 0; i < ((Object[]) value).length && index < 0; i++) {
186               if (dataRow == ((Object[]) value)[i]) {
187                 index = i;
188               }
189             }
190           }
191           if (index >= 0) {
192             sheetState.getSelectedRows().add(index);
193           }
194         }
195       }
196 
197     } else {  // DataModel?, ResultSet, Result or Object
198       LOG.warn("Sorting not supported for type "
199           + (value != null ? value.getClass().toString() : "null"));
200     }
201   }
202 
203   // XXX needs to be tested
204   // XXX was based on ^#\{(\w+(\.\w)*)\}$ which is wrong, because there is a + missing after the last \w
205   boolean isSimpleProperty(final String expressionString) {
206     if (expressionString.startsWith("#{") && expressionString.endsWith("}")) {
207       final String inner = expressionString.substring(2, expressionString.length() - 1);
208       final String[] parts = StringUtils.split(inner, '.');
209       for (final String part : parts) {
210         if (!StringUtils.isAlpha(part)) {
211           return false;
212         }
213       }
214       return true;
215     }
216     return false;
217   }
218 
219   private void unsetSortableAttribute(final UIColumn uiColumn) {
220     LOG.warn("removing attribute sortable from column " + uiColumn.getId());
221     uiColumn.getAttributes().put(Attributes.SORTABLE, Boolean.FALSE);
222   }
223 
224   private UIComponent getFirstSortableChild(final List<UIComponent> children) {
225     UIComponent result = null;
226 
227     for (UIComponent child : children) {
228       result = child;
229       if (child instanceof UISelectMany
230           || child instanceof UISelectOne
231           || child instanceof UISelectBoolean
232           || (child instanceof AbstractUICommand && child.getChildren().isEmpty())
233           || (child instanceof UIInput && RendererTypes.HIDDEN.equals(child.getRendererType()))) {
234         continue;
235         // look for a better component if any
236       }
237       if (child instanceof UIOutput) {
238         break;
239       }
240       if (child instanceof UICommand
241           || child instanceof javax.faces.component.UIPanel) {
242         child = getFirstSortableChild(child.getChildren());
243         if (child instanceof UIOutput) {
244           break;
245         }
246       }
247     }
248     return result;
249   }
250 
251   public Comparator getComparator() {
252     return comparator;
253   }
254 
255   public void setComparator(final Comparator comparator) {
256     this.comparator = comparator;
257   }
258 }
259