001/* 002 * (C) Copyright 2008 JBoss and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Original file from org.jboss.seam.excel.ui.UICell.java in jboss-seam-excel 016 * Anahide Tchertchian 017 */ 018package org.nuxeo.ecm.platform.ui.web.component.seam; 019 020import java.io.IOException; 021import java.io.StringWriter; 022import java.text.DateFormat; 023import java.text.ParseException; 024import java.util.Locale; 025 026import javax.el.ELException; 027import javax.el.ValueExpression; 028import javax.faces.FacesException; 029import javax.faces.component.UIComponent; 030import javax.faces.context.FacesContext; 031import javax.faces.context.ResponseWriter; 032 033import org.apache.commons.lang3.StringUtils; 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.jboss.seam.core.Interpolator; 037import org.jboss.seam.excel.ExcelWorkbookException; 038import org.jboss.seam.ui.util.JSF; 039import org.nuxeo.ecm.platform.ui.web.util.NXHtmlResponseWriter; 040 041import com.sun.faces.config.WebConfiguration; 042 043/** 044 * Override of Seam cell component to control HTML encoding of accents in excel, and to improve data type guessing when 045 * using dates or numbers. 046 * 047 * @since 5.5 048 */ 049public class UICellExcel extends org.jboss.seam.excel.ui.UICell { 050 051 private static final Log log = LogFactory.getLog(UICellExcel.class); 052 053 public static final String DEFAULT_CONTENT_TYPE = "text/html"; 054 055 public static final String DEFAULT_CHARACTER_ENCODING = "utf-8"; 056 057 // add field again as it's private in parent class 058 protected Object value; 059 060 /** 061 * Force type attribute, added here to ensure value expression resolution 062 */ 063 protected String forceType; 064 065 /** 066 * Style attribute, added here to ensure value expression resolution 067 */ 068 protected String style; 069 070 @Override 071 public Object getValue() { 072 Object theValue = valueOf("value", value); 073 if (theValue == null) { 074 try { 075 theValue = cmp2String(FacesContext.getCurrentInstance(), this); 076 String forceType = getForceType(); 077 if (forceType != null && !forceType.isEmpty()) { 078 theValue = convertStringToTargetType((String) theValue, forceType); 079 } 080 } catch (IOException e) { 081 String message = Interpolator.instance().interpolate("Could not render cell #0", getId()); 082 throw new ExcelWorkbookException(message, e); 083 } 084 } else { 085 theValue = theValue.toString(); 086 } 087 return theValue; 088 } 089 090 @Override 091 public void setValue(Object value) { 092 this.value = value; 093 } 094 095 /** 096 * Converts string value as returned by widget to the target type for an accurate cell format in the XLS/CSV export. 097 * <ul> 098 * <li>If force type is set to "number", convert value to a double (null if empty).</li> 099 * <li>If force type is set to "bool", convert value to a boolean (null if empty).</li> 100 * <li>If force type is set to "date", convert value to a date using most frequent date parsers using the short, 101 * medium, long and full formats and current locale, trying first with time information and after with only date 102 * information. Returns null if date is empty or could not be parsed.</li> 103 * </ul> 104 * 105 * @since 5.6 106 */ 107 protected Object convertStringToTargetType(String value, String forceType) { 108 if (CellType.number.name().equals(forceType)) { 109 if (StringUtils.isBlank(value)) { 110 return null; 111 } 112 return Double.valueOf(value); 113 } else if (CellType.date.name().equals(forceType)) { 114 if (StringUtils.isBlank(value)) { 115 return null; 116 } 117 Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale(); 118 int[] formats = { DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL }; 119 for (int format : formats) { 120 try { 121 return DateFormat.getDateTimeInstance(format, format, locale).parse(value); 122 } catch (ParseException e) { 123 // ignore 124 } 125 try { 126 return DateFormat.getDateInstance(format, locale).parse(value); 127 } catch (ParseException e) { 128 // ignore 129 } 130 } 131 log.warn("Could not convert value to a date instance: " + value); 132 return null; 133 } else if (CellType.bool.name().equals(forceType)) { 134 if (StringUtils.isBlank(value)) { 135 return null; 136 } 137 return Boolean.valueOf(value); 138 } 139 return value; 140 } 141 142 /** 143 * Helper method for rendering a component (usually on a facescontext with a caching reponsewriter) 144 * 145 * @param facesContext The faces context to render to 146 * @param component The component to render 147 * @return The textual representation of the component 148 * @throws IOException If the JSF helper class can't render the component 149 */ 150 public static String cmp2String(FacesContext facesContext, UIComponent component) throws IOException { 151 ResponseWriter oldResponseWriter = facesContext.getResponseWriter(); 152 String contentType = oldResponseWriter != null ? oldResponseWriter.getContentType() : DEFAULT_CONTENT_TYPE; 153 String characterEncoding = oldResponseWriter != null ? oldResponseWriter.getCharacterEncoding() 154 : DEFAULT_CHARACTER_ENCODING; 155 StringWriter cacheingWriter = new StringWriter(); 156 157 // XXX: create a response writer by hand, to control html escaping of 158 // iso characters 159 // take default values for these confs 160 Boolean scriptHiding = Boolean.FALSE; 161 Boolean scriptInAttributes = Boolean.TRUE; 162 // force escaping to true 163 WebConfiguration.DisableUnicodeEscaping escaping = WebConfiguration.DisableUnicodeEscaping.True; 164 ResponseWriter newResponseWriter = new NXHtmlResponseWriter(cacheingWriter, contentType, characterEncoding, 165 scriptHiding, scriptInAttributes, escaping); 166 // ResponseWriter newResponseWriter = renderKit.createResponseWriter( 167 // cacheingWriter, contentType, characterEncoding); 168 169 facesContext.setResponseWriter(newResponseWriter); 170 JSF.renderChild(facesContext, component); 171 if (oldResponseWriter != null) { 172 facesContext.setResponseWriter(oldResponseWriter); 173 } 174 cacheingWriter.flush(); 175 cacheingWriter.close(); 176 return cacheingWriter.toString(); 177 } 178 179 /** 180 * Returns the style attribute, used to format cells with a specific {@link #forceType}. Sample value for dates 181 * formatting: "xls-format-mask: #{nxu:basicDateFormatter()};". 182 * 183 * @since 5.6 184 */ 185 @Override 186 public String getStyle() { 187 if (style != null) { 188 return style; 189 } 190 ValueExpression ve = getValueExpression("style"); 191 if (ve != null) { 192 try { 193 return (String) ve.getValue(getFacesContext().getELContext()); 194 } catch (ELException e) { 195 throw new FacesException(e); 196 } 197 } else { 198 return null; 199 } 200 } 201 202 /** 203 * @since 5.6 204 */ 205 @Override 206 public void setStyle(String style) { 207 this.style = style; 208 } 209 210 /** 211 * Returns the force type attribute, used to force cell type to "date" or "number" for instance. 212 * 213 * @since 5.6 214 */ 215 public String getForceType() { 216 if (forceType != null) { 217 return forceType; 218 } 219 ValueExpression ve = getValueExpression("forceType"); 220 if (ve != null) { 221 try { 222 return (String) ve.getValue(getFacesContext().getELContext()); 223 } catch (ELException e) { 224 throw new FacesException(e); 225 } 226 } else { 227 return null; 228 } 229 } 230 231 /** 232 * @since 5.6 233 */ 234 public void setForceType(String forceType) { 235 this.forceType = forceType; 236 } 237 238 // state holder 239 240 @Override 241 public void restoreState(FacesContext context, Object state) { 242 Object[] values = (Object[]) state; 243 super.restoreState(context, values[0]); 244 forceType = (String) values[1]; 245 style = (String) values[2]; 246 } 247 248 @Override 249 public Object saveState(FacesContext context) { 250 return new Object[] { super.saveState(context), forceType, style }; 251 } 252 253}