/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.trinidadinternal.taglib.listener;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.component.StateHolder;
import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
import javax.faces.el.ValueBinding;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.servlet.http.HttpServletResponse;
import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.bean.FacesBeanImpl;
import org.apache.myfaces.trinidad.bean.PropertyKey;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.util.ComponentUtils;
import org.apache.myfaces.trinidad.util.MessageFactory;
import org.apache.myfaces.trinidadinternal.util.MimeUtility;
/**
* @todo Look at moving to org.apache.myfaces.trinidad.event
* @todo Extending FacesBean is very lame if we make this
* class part of our public API, but the FacesBean API
* would otherwise require a private subclass of FacesBeanImpl.
* We need a better way out.
*/
public class FileDownloadActionListener extends FacesBeanImpl
implements ActionListener, StateHolder
{
static public final FacesBean.Type TYPE = new FacesBean.Type();
static public final PropertyKey FILENAME_KEY =
TYPE.registerKey("filename");
// Must be a ValueBinding
static public final PropertyKey CONTENT_TYPE_KEY =
TYPE.registerKey("contentType");
static public final PropertyKey METHOD_KEY =
TYPE.registerKey("method",
MethodBinding.class,
PropertyKey.CAP_NOT_BOUND | PropertyKey.CAP_STATE_HOLDER);
/**
* <p>The message identifier of the {@link FacesMessage} to be created when
* there is a download error.</p>
*/
public static final String DOWNLOAD_MESSAGE_ID =
"org.apache.myfaces.trinidad.event.FileDownloadActionListener.DOWNLOAD_ERROR";
static
{
TYPE.lock();
}
public FileDownloadActionListener()
{
}
public void processAction(ActionEvent event)
{
String filename = getFilename();
String contentType = getContentType();
FacesContext context = FacesContext.getCurrentInstance();
Object response = context.getExternalContext().getResponse();
if (!(response instanceof HttpServletResponse))
{
_LOG.warning("FILE_DOWNLOAD_LISTENER_REQUIRES_SERVLET");
}
else
{
HttpServletResponse hsr = (HttpServletResponse) response;
try
{
if (contentType != null)
// TODO: encoding?
hsr.setContentType(contentType);
if (filename != null)
{
// check for supported user agents. Currently IE, Gecko, and WebKit.
// IE and WebKit use UTF-8 encoding.
boolean isGecko = true;
Map<String, String> headers = context.getExternalContext().getRequestHeaderMap();
String agentName = headers.get("User-Agent").toLowerCase();
if (agentName.contains("msie") || agentName.contains("applewebkit") || agentName.contains("safari"))
isGecko = false;
// boolean isIE = CoreRenderer.isIE(RenderingContext.getCurrentInstance());
String encodeHTTPHeaderFilename = MimeUtility.encodeHTTPHeader(filename, !isGecko);
// double quotes are needed in case the filename is long. otherwise the filename gets
// truncated in Firefox.
hsr.setHeader("Content-Disposition",
"attachment; filename=\""+encodeHTTPHeaderFilename + "\"");
}
MethodBinding method = getMethod();
OutputStream out = new BufferedOutputStream(hsr.getOutputStream());
method.invoke(context, new Object[]{context, out});
out.close();
}
catch (Exception e)
{
hsr.reset();
_LOG.warning(e);
FacesMessage message = MessageFactory.getMessage(context, DOWNLOAD_MESSAGE_ID);
context.addMessage(null, message);
context.renderResponse();
return;
}
}
context.responseComplete();
}
public MethodBinding getMethod()
{
return (MethodBinding) getProperty(METHOD_KEY);
}
public void setMethod(MethodBinding method)
{
setProperty(METHOD_KEY, method);
}
public String getFilename()
{
return ComponentUtils.resolveString(getProperty(FILENAME_KEY));
}
public void setFilename(String filename)
{
setProperty(FILENAME_KEY, filename);
}
public String getContentType()
{
return ComponentUtils.resolveString(getProperty(CONTENT_TYPE_KEY));
}
public void setContentType(String contentType)
{
setProperty(CONTENT_TYPE_KEY, contentType);
}
@Override
public Type getType()
{
return TYPE;
}
public boolean isTransient()
{
return false;
}
public void setTransient(boolean newTransientValue)
{
throw new UnsupportedOperationException();
}
// saveState() and restoreState() come from FacesBeanImpl
private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(FileDownloadActionListener.class);
}