1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.internal.renderkit.renderer;
21
22 import org.apache.myfaces.tobago.context.TobagoContext;
23 import org.apache.myfaces.tobago.internal.component.AbstractUIFile;
24 import org.apache.myfaces.tobago.internal.util.HtmlRendererUtils;
25 import org.apache.myfaces.tobago.internal.util.HttpPartWrapper;
26 import org.apache.myfaces.tobago.renderkit.css.BootstrapClass;
27 import org.apache.myfaces.tobago.renderkit.css.Icons;
28 import org.apache.myfaces.tobago.renderkit.css.TobagoClass;
29 import org.apache.myfaces.tobago.renderkit.html.CustomAttributes;
30 import org.apache.myfaces.tobago.renderkit.html.HtmlAttributes;
31 import org.apache.myfaces.tobago.renderkit.html.HtmlElements;
32 import org.apache.myfaces.tobago.renderkit.html.HtmlInputTypes;
33 import org.apache.myfaces.tobago.util.ComponentUtils;
34 import org.apache.myfaces.tobago.util.ResourceUtils;
35 import org.apache.myfaces.tobago.validator.FileItemValidator;
36 import org.apache.myfaces.tobago.webapp.TobagoResponseWriter;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import javax.faces.context.FacesContext;
41 import javax.faces.event.ComponentSystemEvent;
42 import javax.faces.event.ComponentSystemEventListener;
43 import javax.faces.event.ListenerFor;
44 import javax.faces.event.PostAddToViewEvent;
45 import javax.faces.validator.Validator;
46 import javax.servlet.http.HttpServletRequest;
47 import javax.servlet.http.Part;
48 import java.io.IOException;
49 import java.lang.invoke.MethodHandles;
50 import java.util.ArrayList;
51 import java.util.List;
52
53 @ListenerFor(systemEventClass = PostAddToViewEvent.class)
54 public class FileRenderer<T extends AbstractUIFile>
55 extends MessageLayoutRendererBase<T> implements ComponentSystemEventListener {
56
57 private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
58
59 @Override
60 protected boolean isOutputOnly(T component) {
61 return component.isDisabled() || component.isReadonly();
62 }
63
64 @Override
65 public HtmlElements getComponentTag() {
66 return HtmlElements.TOBAGO_FILE;
67 }
68
69 @Override
70 public void processEvent(final ComponentSystemEvent event) {
71 TobagoContext.getInstance(FacesContext.getCurrentInstance()).setEnctype("multipart/form-data");
72 }
73
74 @Override
75 public boolean getRendersChildren() {
76 return true;
77 }
78
79 @Override
80 public void decodeInternal(final FacesContext facesContext, final T component) {
81 if (isOutputOnly(component)) {
82 return;
83 }
84
85 final boolean multiple = component.isMultiple() && !component.isRequired();
86 final Object request = facesContext.getExternalContext().getRequest();
87 if (request instanceof HttpServletRequest) {
88 try {
89 final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
90 if (multiple) {
91 final List<Part> parts = new ArrayList<>();
92 for (final Part part : httpServletRequest.getParts()) {
93 if (component.getClientId(facesContext).equals(part.getName())) {
94 LOG.debug("Uploaded file '{}', size={}, type='{}'",
95 part.getSubmittedFileName(), part.getSize(), part.getContentType());
96 parts.add(new HttpPartWrapper(part));
97 }
98 component.setSubmittedValue(parts.toArray(new Part[0]));
99 }
100 } else {
101 final Part part = httpServletRequest.getPart(component.getClientId(facesContext));
102 final String submittedFileName = part.getSubmittedFileName();
103 if (LOG.isDebugEnabled()) {
104 LOG.debug("Uploaded file '{}', size={}, type='{}'",
105 submittedFileName, part.getSize(), part.getContentType());
106 }
107 if (submittedFileName.length() > 0) {
108 component.setSubmittedValue(new HttpPartWrapper(part));
109 }
110 }
111 } catch (final Exception e) {
112 LOG.error("", e);
113 component.setValid(false);
114 }
115 } else {
116 LOG.warn("Unsupported request type: " + request.getClass().getName());
117 }
118
119 decodeClientBehaviors(facesContext, component);
120 }
121
122 @Override
123 protected void encodeAttributes(final FacesContext facesContext, final T component) throws IOException {
124 final String placeholder = component.getPlaceholder();
125 final String multiFormat = ResourceUtils.getString(facesContext, "file.selected");
126
127 final TobagoResponseWriter writer = getResponseWriter(facesContext);
128 writer.writeAttribute(HtmlAttributes.PLACEHOLDER, placeholder, true);
129 writer.writeAttribute(CustomAttributes.MULTI_FORMAT, multiFormat, true);
130 }
131
132 @Override
133 protected void encodeBeginField(final FacesContext facesContext, final T component) throws IOException {
134
135 final String clientId = component.getClientId(facesContext);
136 final String fieldId = component.getFieldId(facesContext);
137 final String accept = createAcceptFromValidators(component);
138 final boolean multiple = component.isMultiple() && !component.isRequired();
139 final boolean disabled = component.isDisabled();
140 final boolean readonly = component.isReadonly();
141 if (component.isMultiple() && component.isRequired()) {
142 LOG.warn("Required multiple file upload is not supported.");
143 }
144
145 final TobagoResponseWriter writer = getResponseWriter(facesContext);
146
147 writer.startElement(HtmlElements.DIV);
148 writer.writeClassAttribute(
149 BootstrapClass.FORM_FILE,
150 TobagoClass.FILE.createMarkup(component.getMarkup()),
151 component.getCustomClass(),
152 BootstrapClass.FORM_CONTROL_PLAINTEXT);
153 HtmlRendererUtils.writeDataAttributes(facesContext, writer, component);
154
155 writer.startElement(HtmlElements.INPUT);
156 writer.writeAttribute(HtmlAttributes.MULTIPLE, multiple);
157 writer.writeAttribute(HtmlAttributes.TYPE, HtmlInputTypes.FILE);
158 writer.writeAttribute(HtmlAttributes.ACCEPT, accept, true);
159 writer.writeAttribute(HtmlAttributes.TABINDEX, -1);
160 writer.writeIdAttribute(fieldId);
161 writer.writeClassAttribute(
162 BootstrapClass.FORM_FILE_INPUT,
163 BootstrapClass.borderColor(ComponentUtils.getMaximumSeverity(component)));
164 writer.writeNameAttribute(clientId);
165
166 writer.writeAttribute(HtmlAttributes.DISABLED, disabled || readonly);
167 writer.writeAttribute(HtmlAttributes.READONLY, readonly);
168 writer.writeAttribute(HtmlAttributes.REQUIRED, component.isRequired());
169
170
171 final String title = HtmlRendererUtils.getTitleFromTipAndMessages(facesContext, component);
172 if (title != null) {
173 writer.writeAttribute(HtmlAttributes.TITLE, title, true);
174 }
175 writer.endElement(HtmlElements.INPUT);
176
177 encodeBehavior(writer, facesContext, component);
178
179 writer.startElement(HtmlElements.LABEL);
180 writer.writeClassAttribute(BootstrapClass.FORM_FILE_LABEL);
181 writer.writeAttribute(HtmlAttributes.FOR, fieldId, false);
182 writer.startElement(HtmlElements.SPAN);
183 writer.writeClassAttribute(BootstrapClass.FORM_FILE_TEXT);
184 writer.endElement(HtmlElements.SPAN);
185 writer.startElement(HtmlElements.SPAN);
186 writer.writeClassAttribute(BootstrapClass.FORM_FILE_BUTTON);
187 writer.startElement(HtmlElements.I);
188
189 writer.writeAttribute(HtmlAttributes.TITLE, "Browse", false);
190 writer.writeClassAttribute(Icons.FA, Icons.FOLDER_OPEN);
191 writer.endElement(HtmlElements.I);
192 writer.endElement(HtmlElements.SPAN);
193 writer.endElement(HtmlElements.LABEL);
194 }
195
196 private String createAcceptFromValidators(final AbstractUIFile file) {
197 final StringBuilder builder = new StringBuilder();
198 for (final Validator validator : file.getValidators()) {
199 if (validator instanceof FileItemValidator) {
200 final FileItemValidator fileItemValidator = (FileItemValidator) validator;
201 for (final String contentType : fileItemValidator.getContentType()) {
202 builder.append(",");
203 builder.append(contentType);
204 }
205 }
206 }
207 if (builder.length() > 0) {
208 return builder.substring(1);
209 } else {
210 return null;
211 }
212 }
213
214 @Override
215 protected void encodeEndField(final FacesContext facesContext, final T component) throws IOException {
216 final TobagoResponseWriter writer = getResponseWriter(facesContext);
217 writer.endElement(HtmlElements.DIV);
218 }
219
220 @Override
221 protected String getFieldId(final FacesContext facesContext, final T component) {
222 return component.getFieldId(facesContext);
223 }
224 }