Uploader avec JSF

Tagged:

Alors la je suis content j'ai réussi a uploader un fichier avec JSF (c'est malheureux hein =)) !

Et pour que plus jamais personne ne se dépatouille avec le sac de noeud que je viens de déméler, je l'écris ici !

On va utiliser les objets d'upload disponnible avec jakarta.commons qui sont somme toute assez pratique. On va également changer les comportements de JSF qui par défaut ne gère pas les enctype="multipart/form-data". Dans l'idéal on aimerai pouvoir faire :

<html:form id="upload" enctype="multipart/form-data">
	<my:inputFile value="#{app.file}" />
	<html:commandButton value="testUpload" action="#{app.action}" />
</html:form>

Pour ça on va définir le tag et dire a JSF comment s'en servir ! Voici la taglib qu'il faut mettre dans WEB-INF/tld/taglib.tld

<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
 
<taglib>
	<tlib-version>1.0</tlib-version>
	<jsp-version>1.2</jsp-version>
	<short-name>my</short-name>
	<uri>http://myCompany.com/jsftaglib</uri>
 
	<tag>
		<name>inputFile</name>
		<tag-class>jsf.taglib.InputFileTag</tag-class>
		<body-content>JSP</body-content>
		<description></description>
 
		<attribute>
			<name>value</name>
			<required>true</required>
			<rtexprvalue>false</rtexprvalue>
		</attribute>
	</tag>
 
</taglib>

Pour pouvoir s'en servir il faut ajouter dans le fichier JSP la ligne suivante :

<%@ taglib uri="http://myCompany.com/jsftaglib" prefix="my" %>

Bon on y est ... mais finalement on a rien fait encore la ! On va créer un renderer pour cet élément Input et une classe permettant de mapper le tag !

Commençons par le tag :

package jsf.taglib;
 
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.ValueHolder;
import javax.faces.webapp.UIComponentTag;
 
import com.sun.faces.util.Util;
 
public class InputFileTag extends UIComponentTag {
 
	private String id = null;
	private String styleClass = null;
	private String value = null;
 
	public void setStyleClass(String styleClass) {
		this.styleClass = styleClass;
	}
	public void setValue(String newValue) {
		value = newValue;
	}
	public String getComponentType() {
		return UIInput.COMPONENT_TYPE;
	}
	public String getRendererType() {
		return "InputFile";
	}
 
	public void release() {
		super.release();
		this.id = null;
		this.styleClass = null;
		this.value = null;
	}
 
	protected void setProperties(UIComponent component) {
		super.setProperties(component);
 
		if (component instanceof ValueHolder) {
			ValueHolder valueHolder = (ValueHolder) component;
 
			if (value != null) {
				if (isValueReference(value)) {
					component.setValueBinding("value", Util.getValueBinding(value));
				} else {
					valueHolder.setValue(value);
				}
			}
		}
	}
}

Puis le Renderer :

package jsf.renderer;
 
import java.io.IOException;
import java.util.Map;
 
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;
 
public class InputFileRenderer extends Renderer {
 
	public void decode(FacesContext context, UIComponent component) {
		if ((context == null) || (component == null)) {
			throw new NullPointerException();
		}
 
		UIInput uiInput = (UIInput) component;
 
		String clientId = component.getClientId(context);
		Map requestMap = context.getExternalContext().getRequestParameterMap();
 
		if (requestMap.containsKey(clientId)) {
			uiInput.setValue(requestMap.get(clientId));
		}
	}
 
	public void encodeEnd(FacesContext context, UIComponent component)
			throws IOException {
		if ((context == null) || (component == null)) {
			throw new NullPointerException();
		}
 
		ResponseWriter writer = context.getResponseWriter();
 
		writer.startElement("input", component);
 
		writer.writeAttribute("type", "file", "type");
		String clientId = component.getClientId(context);
		writer.writeAttribute("name", clientId, "clientId");
 
		writer.endElement("input");
 
	}
 
}

On va dire aussi a JSF que tout ça ça existe et qu'il faut qu'il s'en serve, et pour ça on va rajouter dans le fichier faces-config.xml :

	<render-kit>
		<renderer>
			<component-family>javax.faces.Input</component-family>
			<renderer-type>InputFile</renderer-type>
			<renderer-class>
				jsf.renderer.InputFileRenderer
			</renderer-class>
		</renderer>
	</render-kit>

Si la section render-kit existe déja il faut simplement ajouter un renderer !

Ensuite si on analyse le problème plus loin (j'ai fait une forme avec et une sans enctype et j'ai matté les logs) on voit que le problème se situe au niveau de la fonction FormRenderer.decode. Quand on va voir le code on voit qu'elle fait un context.getExternalContext().getRequestParameterMap(); et que ça ne gère pas les multipart ! Il faut donc tout changer !

Commençons par définir des Context et des ExternalContext afin de gérer ça nous même !

d'abord le ExternalContext :

package jsf.context;
 
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Principal;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
 
import javax.faces.context.ExternalContext;
 
public class CustomExternalContext extends ExternalContext {
 
	private ExternalContext context;
	private Map requestParameterMap;
	private List requestParameterNames;
 
	public CustomExternalContext(ExternalContext context) {
		this.context = context;
	}
 
	public void dispatch(String arg0) throws IOException {
		context.dispatch(arg0);
	}
 
	public String encodeActionURL(String arg0) {
		return context.encodeActionURL(arg0);
	}
 
	public String encodeNamespace(String arg0) {
		return context.encodeNamespace(arg0);
	}
 
	public String encodeResourceURL(String arg0) {
		return context.encodeResourceURL(arg0);
	}
 
	public Map getApplicationMap() {
		return context.getApplicationMap();
	}
 
	public String getAuthType() {
		return context.getAuthType();
	}
 
	public Object getContext() {
		return context.getContext();
	}
 
	public String getInitParameter(String arg0) {
		return context.getInitParameter(arg0);
	}
 
	public Map getInitParameterMap() {
		return context.getInitParameterMap();
	}
 
	public String getRemoteUser() {
		return context.getRemoteUser();
	}
 
	public Object getRequest() {
		return context.getRequest();
	}
 
	public String getRequestContextPath() {
		return context.getRequestContextPath();
	}
 
	public Map getRequestCookieMap() {
		return context.getRequestCookieMap();
	}
 
	public Map getRequestHeaderMap() {
		return context.getRequestHeaderMap();
	}
 
	public Map getRequestHeaderValuesMap() {
		return context.getRequestHeaderValuesMap();
	}
 
	public Locale getRequestLocale() {
		return context.getRequestLocale();
	}
 
	public Iterator getRequestLocales() {
		return context.getRequestLocales();
	}
 
	public Map getRequestMap() {
		return context.getRequestMap();
	}
 
	public Map getRequestParameterMap() {
		return requestParameterMap;
	}
 
	public Iterator getRequestParameterNames() {
		return requestParameterNames.iterator();
	}
 
	public Map getRequestParameterValuesMap() {
		return context.getRequestParameterValuesMap();
	}
 
	public String getRequestPathInfo() {
		return context.getRequestPathInfo();
	}
 
	public String getRequestServletPath() {
		return context.getRequestServletPath();
	}
 
	public URL getResource(String arg0) throws MalformedURLException {
		return context.getResource(arg0);
	}
 
	public InputStream getResourceAsStream(String arg0) {
		return context.getResourceAsStream(arg0);
	}
 
	public Set getResourcePaths(String arg0) {
		return context.getResourcePaths(arg0);
	}
 
	public Object getResponse() {
		return context.getResponse();
	}
 
	public Object getSession(boolean arg0) {
		return context.getSession(arg0);
	}
 
	public Map getSessionMap() {
		return context.getSessionMap();
	}
 
	public Principal getUserPrincipal() {
		return context.getUserPrincipal();
	}
 
	public boolean isUserInRole(String arg0) {
		return context.isUserInRole(arg0);
	}
 
	public void log(String arg0, Throwable arg1) {
		context.log(arg0, arg1);
	}
 
	public void log(String arg0) {
		context.log(arg0);
	}
 
	public void redirect(String arg0) throws IOException {
		context.redirect(arg0);
	}
 
	public void setRequestParameterMap(Map requestParameterMap) {
		this.requestParameterMap = requestParameterMap;
	}
 
	public void setRequestParameterNames(List requestParameterNames) {
		this.requestParameterNames = requestParameterNames;
	}
}

puis le Context:

package jsf.context;
 
import java.util.Iterator;
 
import javax.faces.application.Application;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseStream;
import javax.faces.context.ResponseWriter;
import javax.faces.render.RenderKit;
 
public class CustomFacesContext extends FacesContext {
 
	private FacesContext context;
	private ExternalContext externalContext;
 
	public CustomFacesContext(FacesContext context, ExternalContext externalContext) {
		this.context = context;
		this.externalContext = externalContext;
	}
 
	public ExternalContext getExternalContext() {
 
		// return our own context!!!
		return externalContext;
	}
 
	public void addMessage(String arg0, FacesMessage arg1) {
		context.addMessage(arg0, arg1);
	}
 
	public Application getApplication() {
		return context.getApplication();
	}
 
	public Iterator getClientIdsWithMessages() {
		return context.getClientIdsWithMessages();
	}
 
	public Severity getMaximumSeverity() {
		return context.getMaximumSeverity();
	}
 
	public Iterator getMessages() {
		return context.getMessages();
	}
 
	public Iterator getMessages(String arg0) {
		return context.getMessages(arg0);
	}
 
	public RenderKit getRenderKit() {
		return context.getRenderKit();
	}
 
	public boolean getRenderResponse() {
		return context.getRenderResponse();
	}
 
	public boolean getResponseComplete() {
		return context.getResponseComplete();
	}
 
	public ResponseStream getResponseStream() {
		return context.getResponseStream();
	}
 
	public ResponseWriter getResponseWriter() {
		return context.getResponseWriter();
	}
 
	public UIViewRoot getViewRoot() {
		return context.getViewRoot();
	}
 
	public void release() {
		context.release();
	}
 
	public void renderResponse() {
		context.renderResponse();
	}
 
	public void responseComplete() {
		context.responseComplete();
	}
 
	public void setResponseStream(ResponseStream arg0) {
		context.setResponseStream(arg0);
	}
 
	public void setResponseWriter(ResponseWriter arg0) {
		context.setResponseWriter(arg0);
	}
 
	public void setViewRoot(UIViewRoot arg0) {
		context.setViewRoot(arg0);
 
	}
 
	public void setContext(FacesContext context) {
		this.context = context;
	}
 
	public void setExternalContext(ExternalContext externalContext) {
		this.externalContext = externalContext;
	}
}

Voila on y est !!! la dernière brique : Il faut uploader le fichier effectivement ! Sinon il se passe toujours rien la ! Et pour ça on refait la fonction qui posait problème avant mais avec un traitement particulier pour le multipart :

package jsf.component;
 
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
 
import javax.faces.component.UIForm;
import javax.faces.context.FacesContext;
import javax.servlet.http.HttpServletRequest;
 
import jsf.context.CustomExternalContext;
import jsf.context.CustomFacesContext;
 
import org.apache.commons.fileupload.DiskFileUpload;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.log4j.Logger;
 
public class UploadUIForm extends UIForm {
 
	private Logger log = Logger.getLogger(getClass());
	
	public UploadUIForm() {
		super();
	}
 
	public void processDecodes(FacesContext context) {
 
		HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
		boolean isMultipart = FileUpload.isMultipartContent(request);
		
		if (isMultipart) {
 
			try {
				CustomExternalContext myExternalContext = new CustomExternalContext(context.getExternalContext());
				CustomFacesContext myContext = new CustomFacesContext(context, myExternalContext);
 
				Map requestParameterMap = new HashMap();
				List requestParameterNames = new ArrayList();
 
				// Create a new file upload handler
				DiskFileUpload upload = new DiskFileUpload();
 
				// Set upload parameters - CHANGE THIS FOR YOUR NEEDS!
				upload.setSizeThreshold(1000000);
				upload.setSizeMax(1000000);
				upload.setRepositoryPath("/tmp/");
 
				// Parse the request
				List items = upload.parseRequest(request);
 
				// Process the uploaded items
				Iterator iter = items.iterator();
				while (iter.hasNext()) {
					FileItem item = (FileItem) iter.next();
 
					if (item.isFormField()) { // Form
					// Process a regular form field
						String name = item.getFieldName();
						String value = item.getString();
						requestParameterMap.put(name, value);
						requestParameterNames.add(name);
 
					} else {
						String fieldName = item.getFieldName();
 
						requestParameterMap.put(fieldName, item);
						requestParameterNames.add(fieldName);
					}
				}
 
				myExternalContext.setRequestParameterMap(requestParameterMap);
				myExternalContext.setRequestParameterNames(requestParameterNames);
 
				super.processDecodes(myContext);
 
			} catch (FileUploadException e) {
				log.error(e,e);
			}
 
		} else
			super.processDecodes(context);
	}
 
}

Et il faut dire a JSF qu'il doit utiliser cette classe plutot que la sienne en ajoutant dans faces-context.xml :

	<component>
		<component-type>javax.faces.HtmlForm</component-type>
		<component-class>jsf.component.UploadUIForm</component-class>
	</component>

Voila du coup si on reprend du départ mon fichier jsp, le backing bean associé ressemble a ça :

	public String action() {
		log.debug("here");
		return null;
	}
	
	public void setFile(Object o) {
		if (o instanceof FileItem) {
			FileItem file = (FileItem)o;
			log.debug(file.getName());
		}
	}

Et voila ... il faut simplement (ce mot n'a plus de sens après tout ça mais une fois que c'est fait c'est réellement simple !) manipuler le FileItem !!!

PS: Une grande partie de ce tuto a été trouvée ici

Comments

J'adore cette technologie !

Java, c'est vraiment le top, surtout pour ceux qui dirige une SSII, et qui vendent des humains à leurs clients Java fans aux nombres de jour/homme.

<pipo>
"Ouaaaaais mais tu comprends c'est super cool parce que tu as la totale maitrise de tout le processus et tu peux en faire ce que tu veuuux ....
</pipo>

Ca remplace aisément un trop compliqué/pas adapté/boupabobeurk :

if (is_uploaded_file($fichier)) {
move_uploaded_file($fichier, $final);
}

En php =)

"JSF qui par défaut ne gère pas les enctype="multipart/form-data"."

c'est faux...JSF gére le enctype="multipart/form-data.

Le composant inputfileUpload de tomahawk en est la preuve.

D'ailleurs pourquoi avoir refait un composant qui existe déjà ?

Ce n'est donc pas faux ... la specif JSF ne gère pas ça il faut rajouter des composants fait par untel ou untel pour que ça marche ...

Je l'ai refait parce que j'utilise simplement JSF et que je n'avais pas besoin de tomohawk mais l'intérêt de comprendre comment tout ça ça fonctionne et en quelle manière tu peux interférer avec c'est quand même pas rien !

Et ben chapeau! T'as du bien te tirer les cheveux pour arriver a tes fins, lol!

Ca ne fonctionne pas chez moi. Dans ma jsp, le champ inputFile est invisible et lorsque je clique sur le commandButton, je ne passe pas dans la méthode "action". Je passe dans la méthode "action" uniquement si je retire l'attribut "enctype", mais dans ce cas, le formulaire ne gère plus l'upload.

Si quelqu'un a rencontré ce genre de problème, qu'il se manifeste !

Merci

Hello
Je trouve cela très intéressant et pense l'implémenter.
Une fois mes fichiers sauvegardé en dehors de ma webapp, comment puis-je faire pour les visualiser depuis une page JFS?

salah tabib

salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib salah tabib