001 package org.apache.myfaces.tobago.component; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one or more 005 * contributor license agreements. See the NOTICE file distributed with 006 * this work for additional information regarding copyright ownership. 007 * The ASF licenses this file to You under the Apache License, Version 2.0 008 * (the "License"); you may not use this file except in compliance with 009 * the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020 import org.apache.commons.lang.StringUtils; 021 import org.apache.myfaces.tobago.compat.FacesUtils; 022 import org.apache.myfaces.tobago.event.SortActionEvent; 023 import org.apache.myfaces.tobago.internal.component.AbstractUICommand; 024 import org.apache.myfaces.tobago.internal.component.AbstractUISheet; 025 import org.apache.myfaces.tobago.model.SheetState; 026 import org.apache.myfaces.tobago.util.BeanComparator; 027 import org.slf4j.Logger; 028 import org.slf4j.LoggerFactory; 029 030 import javax.faces.component.UIColumn; 031 import javax.faces.component.UICommand; 032 import javax.faces.component.UIComponent; 033 import javax.faces.component.UIInput; 034 import javax.faces.component.UIOutput; 035 import javax.faces.component.UISelectBoolean; 036 import javax.faces.component.UISelectMany; 037 import javax.faces.component.UISelectOne; 038 import javax.faces.context.FacesContext; 039 import javax.faces.model.DataModel; 040 import java.util.ArrayList; 041 import java.util.Arrays; 042 import java.util.Collections; 043 import java.util.Comparator; 044 import java.util.List; 045 046 public class Sorter { 047 048 private static final Logger LOG = LoggerFactory.getLogger(Sorter.class); 049 050 private Comparator comparator; 051 052 public void perform(SortActionEvent sortEvent) { 053 if (LOG.isDebugEnabled()) { 054 LOG.debug("sorterId = {}", sortEvent.getComponent().getId()); 055 } 056 UIColumn column = sortEvent.getColumn(); 057 AbstractUISheet data = (AbstractUISheet) sortEvent.getComponent(); 058 059 Object value = data.getValue(); 060 if (value instanceof DataModel) { 061 value = ((DataModel) value).getWrappedData(); 062 } 063 FacesContext facesContext = FacesContext.getCurrentInstance(); 064 SheetState sheetState = data.getSheetState(facesContext); 065 066 Comparator actualComparator = null; 067 068 if (value instanceof List || value instanceof Object[]) { 069 String sortProperty; 070 071 try { 072 073 UIComponent child = getFirstSortableChild(column.getChildren()); 074 if (child != null) { 075 076 String attributeName = child instanceof AbstractUICommand ? Attributes.LABEL:Attributes.VALUE; 077 if (FacesUtils.hasValueBindingOrValueExpression(child, attributeName)) { 078 String var = data.getVar(); 079 String expressionString = FacesUtils.getExpressionString(child, attributeName); 080 if (isSimpleProperty(expressionString)) { 081 if (expressionString.startsWith("#{") 082 && expressionString.endsWith("}")) { 083 expressionString = 084 expressionString.substring(2, 085 expressionString.length() - 1); 086 } 087 sortProperty = expressionString.substring(var.length() + 1); 088 089 actualComparator = new BeanComparator( 090 sortProperty, comparator, !sheetState.isAscending()); 091 092 if (LOG.isDebugEnabled()) { 093 LOG.debug("Sort property is {}", sortProperty); 094 } 095 } else { 096 097 boolean descending = !sheetState.isAscending(); 098 actualComparator = 099 FacesUtils.getBindingOrExpressionComparator(facesContext, child, var, descending, comparator); 100 } 101 } 102 103 } else { 104 LOG.error("No sorting performed. Value is not instanceof List or Object[]!"); 105 unsetSortableAttribute(column); 106 return; 107 } 108 } catch (Exception e) { 109 LOG.error("Error while extracting sortMethod :" + e.getMessage(), e); 110 if (column != null) { 111 unsetSortableAttribute(column); 112 } 113 return; 114 } 115 116 // TODO: locale / comparator parameter? 117 // don't compare numbers with Collator.getInstance() comparator 118 // Comparator comparator = Collator.getInstance(); 119 // comparator = new RowComparator(ascending, method); 120 121 // memorize selected rows 122 List<Object> selectedDataRows = null; 123 if (sheetState.getSelectedRows().size() > 0) { 124 selectedDataRows = new ArrayList<Object>(sheetState.getSelectedRows().size()); 125 Object dataRow; 126 for (Integer index : sheetState.getSelectedRows()) { 127 if (value instanceof List) { 128 dataRow = ((List) value).get(index); 129 } else { 130 dataRow = ((Object[]) value)[index]; 131 } 132 selectedDataRows.add(dataRow); 133 } 134 } 135 136 // do sorting 137 if (value instanceof List) { 138 Collections.sort((List) value, actualComparator); 139 } else { // value is instanceof Object[] 140 Arrays.sort((Object[]) value, actualComparator); 141 } 142 143 // restore selected rows 144 if (selectedDataRows != null) { 145 sheetState.getSelectedRows().clear(); 146 for (Object dataRow : selectedDataRows) { 147 int index = -1; 148 if (value instanceof List) { 149 for (int i = 0; i < ((List) value).size() && index < 0; i++) { 150 if (dataRow == ((List) value).get(i)) { 151 index = i; 152 } 153 } 154 } else { 155 for (int i = 0; i < ((Object[]) value).length && index < 0; i++) { 156 if (dataRow == ((Object[]) value)[i]) { 157 index = i; 158 } 159 } 160 } 161 if (index >= 0) { 162 sheetState.getSelectedRows().add(index); 163 } 164 } 165 } 166 167 } else { // DataModel?, ResultSet, Result or Object 168 LOG.warn("Sorting not supported for type " 169 + (value != null ? value.getClass().toString() : "null")); 170 } 171 } 172 173 // XXX needs to be tested 174 // XXX was based on ^#\{(\w+(\.\w)*)\}$ which is wrong, because there is a + missing after the last \w 175 boolean isSimpleProperty(String expressionString) { 176 if (expressionString.startsWith("#{") && expressionString.endsWith("}")) { 177 String inner = expressionString.substring(2, expressionString.length() - 1); 178 String[] parts = StringUtils.split(inner, "."); 179 for (String part : parts) { 180 if (!StringUtils.isAlpha(part)) { 181 return false; 182 } 183 } 184 return true; 185 } 186 return false; 187 } 188 189 private void unsetSortableAttribute(UIColumn uiColumn) { 190 LOG.warn("removing attribute sortable from column " + uiColumn.getId()); 191 uiColumn.getAttributes().put(Attributes.SORTABLE, Boolean.FALSE); 192 } 193 194 private UIComponent getFirstSortableChild(List children) { 195 UIComponent child = null; 196 197 for (Object aChildren : children) { 198 child = (UIComponent) aChildren; 199 if (child instanceof UISelectMany 200 || child instanceof UISelectOne 201 || child instanceof UISelectBoolean 202 || child instanceof AbstractUICommand 203 || (child instanceof UIInput && RendererTypes.HIDDEN.equals(child.getRendererType()))) { 204 continue; 205 // look for a better component if any 206 } 207 if (child instanceof UIOutput) { 208 break; 209 } 210 if (child instanceof UICommand 211 || child instanceof javax.faces.component.UIPanel) { 212 child = getFirstSortableChild(child.getChildren()); 213 if (child instanceof UIOutput) { 214 break; 215 } 216 } 217 } 218 return child; 219 } 220 221 public Comparator getComparator() { 222 return comparator; 223 } 224 225 public void setComparator(Comparator comparator) { 226 this.comparator = comparator; 227 } 228 } 229