View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.client.enduser.markup.html.form;
20  
21  import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.fileinput.BootstrapFileInputField;
22  import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.fileinput.FileInputConfig;
23  import java.io.ByteArrayInputStream;
24  import java.util.ArrayList;
25  import java.util.Base64;
26  import java.util.Locale;
27  import java.util.Optional;
28  import javax.ws.rs.core.HttpHeaders;
29  import javax.ws.rs.core.MediaType;
30  import javax.ws.rs.core.Response;
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.syncope.client.enduser.SyncopeEnduserSession;
33  import org.apache.syncope.client.enduser.SyncopeWebApplication;
34  import org.apache.syncope.client.enduser.commons.PreviewUtils;
35  import org.apache.syncope.client.ui.commons.Constants;
36  import org.apache.syncope.client.ui.commons.HttpResourceStream;
37  import org.apache.syncope.client.ui.commons.markup.html.form.BaseBinaryFieldPanel;
38  import org.apache.syncope.client.ui.commons.markup.html.form.BinaryFieldDownload;
39  import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
40  import org.apache.syncope.client.ui.commons.markup.html.form.preview.BinaryPreviewer;
41  import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
42  import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
43  import org.apache.wicket.Component;
44  import org.apache.wicket.ajax.AjaxRequestTarget;
45  import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
46  import org.apache.wicket.ajax.markup.html.AjaxLink;
47  import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink;
48  import org.apache.wicket.markup.html.WebMarkupContainer;
49  import org.apache.wicket.markup.html.basic.Label;
50  import org.apache.wicket.markup.html.form.Form;
51  import org.apache.wicket.markup.html.form.StatelessForm;
52  import org.apache.wicket.markup.html.form.TextField;
53  import org.apache.wicket.markup.html.form.upload.FileUpload;
54  import org.apache.wicket.markup.html.panel.Fragment;
55  import org.apache.wicket.model.IModel;
56  import org.apache.wicket.model.ResourceModel;
57  import org.apache.wicket.model.util.ListModel;
58  import org.apache.wicket.spring.injection.annot.SpringBean;
59  import org.apache.wicket.util.lang.Bytes;
60  
61  public class BinaryFieldPanel extends BaseBinaryFieldPanel {
62  
63      private static final long serialVersionUID = 6264462604183088931L;
64  
65      @SpringBean
66      protected PreviewUtils previewUtils;
67  
68      protected final String mimeType;
69  
70      protected final WebMarkupContainer container;
71  
72      protected final AjaxLink<Void> downloadLink;
73  
74      protected final Form<?> uploadForm;
75  
76      protected final Fragment emptyFragment;
77  
78      protected final BootstrapFileInputField fileUpload;
79  
80      protected final BinaryFieldDownload fileDownload;
81  
82      protected final BinaryPreviewer previewer;
83  
84      protected final IndicatingAjaxLink<Void> resetLink;
85  
86      protected final Bytes maxUploadSize;
87  
88      protected final IModel<String> model;
89  
90      protected final String fileKey;
91  
92      public BinaryFieldPanel(
93              final String id,
94              final String name,
95              final IModel<String> model,
96              final String mimeType,
97              final String fileKey) {
98  
99          super(id, name, model);
100         this.model = model;
101         this.fileKey = fileKey;
102         this.mimeType = mimeType;
103 
104         previewer = previewUtils.getPreviewer(mimeType);
105 
106         maxUploadSize = SyncopeWebApplication.get().getMaxUploadFileSizeMB() == null
107                 ? null
108                 : Bytes.megabytes(SyncopeWebApplication.get().getMaxUploadFileSizeMB());
109         uploadForm = new StatelessForm<>("uploadForm");
110         uploadForm.setMultiPart(true);
111         add(uploadForm);
112 
113         container = new WebMarkupContainer("previewContainer");
114         container.setOutputMarkupId(true);
115 
116         emptyFragment = new Fragment("panelPreview", "emptyFragment", container);
117         emptyFragment.setOutputMarkupId(true);
118         container.add(emptyFragment);
119         uploadForm.add(container);
120 
121         field = new TextField<>("textField", model);
122         add(field.setLabel(new ResourceModel(name, name)).setOutputMarkupId(true));
123 
124         uploadForm.add(new Label("preview", StringUtils.isBlank(mimeType) ? StringUtils.EMPTY : '(' + mimeType + ')'));
125 
126         fileDownload = new BinaryFieldDownload(name, fileKey, mimeType, true) {
127 
128             private static final long serialVersionUID = 7203445884857810583L;
129 
130             @Override
131             protected HttpResourceStream getResourceStream() {
132                 return new HttpResourceStream(new ResponseHolder(buildResponse()));
133             }
134         };
135 
136         add(fileDownload);
137 
138         downloadLink = new AjaxLink<>("downloadLink") {
139 
140             private static final long serialVersionUID = -4331619903296515985L;
141 
142             @Override
143             public void onClick(final AjaxRequestTarget target) {
144                 try {
145                     fileDownload.initiate(target);
146                 } catch (Exception e) {
147                     SyncopeEnduserSession.get().onException(e);
148                 }
149             }
150         };
151         downloadLink.setOutputMarkupId(true);
152         uploadForm.add(downloadLink);
153 
154         FileInputConfig config = new FileInputConfig().
155                 showUpload(false).showRemove(false).showPreview(false).
156                 browseClass("btn btn-success").browseIcon("<i class=\"fas fa-folder-open\"></i> &nbsp;");
157         String language = SyncopeEnduserSession.get().getLocale().getLanguage();
158         if (!Locale.ENGLISH.getLanguage().equals(language)) {
159             config.withLocale(language);
160         }
161         fileUpload = new BootstrapFileInputField("fileUpload", new ListModel<>(new ArrayList<>()), config);
162         fileUpload.add(new AjaxFormSubmitBehavior(Constants.ON_CHANGE) {
163 
164             private static final long serialVersionUID = -1107858522700306810L;
165 
166             @Override
167             protected void onSubmit(final AjaxRequestTarget target) {
168                 FileUpload uploaded = fileUpload.getFileUpload();
169                 if (uploaded != null) {
170                     if (maxUploadSize != null && uploaded.getSize() > maxUploadSize.bytes()) {
171                         // SYNCOPE-1213 manage directly max upload file size (if set in properties file)
172                         SyncopeEnduserSession.get().error(getString("tooLargeFile").
173                                 replace("${maxUploadSizeB}", String.valueOf(maxUploadSize.bytes())).
174                                 replace("${maxUploadSizeMB}", String.valueOf(maxUploadSize.bytes() / 1000000L)));
175                         ((BaseWebPage) getPageReference().getPage()).getNotificationPanel().refresh(target);
176                     } else {
177                         byte[] uploadedBytes = uploaded.getBytes();
178                         String uploadedEncoded = Base64.getEncoder().encodeToString(uploadedBytes);
179                         field.setModelObject(uploadedEncoded);
180                         target.add(field);
181 
182                         Component panelPreview = previewer.preview(uploadedBytes);
183                         changePreviewer(panelPreview);
184                         fileUpload.setModelObject(null);
185                         uploadForm.addOrReplace(fileUpload);
186 
187                         setVisibleFileButtons(StringUtils.isNotBlank(uploadedEncoded));
188                         downloadLink.setEnabled(StringUtils.isNotBlank(uploadedEncoded));
189 
190                         target.add(uploadForm);
191                     }
192                 }
193             }
194         });
195         uploadForm.add(fileUpload);
196 
197         resetLink = new IndicatingAjaxLink<>("resetLink") {
198 
199             private static final long serialVersionUID = -7978723352517770644L;
200 
201             @Override
202             public void onClick(final AjaxRequestTarget target) {
203                 field.setModelObject(null);
204                 target.add(field);
205                 downloadLink.setEnabled(false);
206                 container.addOrReplace(emptyFragment);
207                 setVisibleFileButtons(false);
208                 target.add(uploadForm);
209             }
210 
211             @Override
212             public String getAjaxIndicatorMarkupId() {
213                 return Constants.VEIL_INDICATOR_MARKUP_ID;
214             }
215 
216         };
217         uploadForm.add(resetLink);
218     }
219 
220     protected Response buildResponse() {
221         return Response.ok(new ByteArrayInputStream(Base64.getMimeDecoder().decode(getModelObject()))).
222                 type(StringUtils.isBlank(mimeType) ? MediaType.APPLICATION_OCTET_STREAM : mimeType).
223                 header(HttpHeaders.LOCATION, StringUtils.EMPTY).
224                 build();
225     }
226 
227     protected void changePreviewer(final Component panelPreview) {
228         Fragment fragment = new Fragment("panelPreview", "previewFragment", container);
229         fragment.add(panelPreview);
230         container.addOrReplace(fragment);
231         uploadForm.addOrReplace(container);
232     }
233 
234     protected void setVisibleFileButtons(final boolean visible) {
235         resetLink.setVisible(visible);
236         downloadLink.setVisible(visible);
237     }
238 
239     @Override
240     public BinaryFieldPanel clone() {
241         LOG.debug("Custom clone for binary field panel...");
242         return new BinaryFieldPanel(getId(), this.name, this.model, this.mimeType, this.fileKey);
243     }
244 
245     @Override
246     public FieldPanel<String> setNewModel(final IModel<String> model) {
247         field.setModel(model);
248         String modelObj = model.getObject();
249 
250         if (StringUtils.isNotBlank(modelObj)) {
251             Optional.ofNullable(previewer.preview(modelObj)).ifPresent(this::changePreviewer);
252         }
253 
254         downloadLink.setEnabled(StringUtils.isNotBlank(modelObj));
255         setVisibleFileButtons(StringUtils.isNotBlank(modelObj));
256         return this;
257     }
258 
259     @Override
260     protected void sendError(final Exception exception) {
261         SyncopeEnduserSession.get().onException(exception);
262     }
263 
264     @Override
265     public FieldPanel<String> setReadOnly(final boolean readOnly) {
266         super.setReadOnly(readOnly);
267         fileUpload.setEnabled(!readOnly);
268         return this;
269     }
270 }