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.console.wizards;
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.List;
26  import java.util.Locale;
27  import java.util.stream.Collectors;
28  import java.util.stream.Stream;
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.syncope.client.console.SyncopeConsoleSession;
31  import org.apache.syncope.client.console.SyncopeWebApplication;
32  import org.apache.syncope.client.console.pages.BasePage;
33  import org.apache.syncope.client.console.panels.CSVConfPanel;
34  import org.apache.syncope.client.console.rest.ImplementationRestClient;
35  import org.apache.syncope.client.console.rest.ReconciliationRestClient;
36  import org.apache.syncope.client.ui.commons.Constants;
37  import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
38  import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
39  import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
40  import org.apache.syncope.common.lib.to.ImplementationTO;
41  import org.apache.syncope.common.lib.to.ProvisioningReport;
42  import org.apache.syncope.common.lib.types.ConflictResolutionAction;
43  import org.apache.syncope.common.lib.types.IdMImplementationType;
44  import org.apache.syncope.common.lib.types.MatchingRule;
45  import org.apache.syncope.common.lib.types.UnmatchingRule;
46  import org.apache.syncope.common.rest.api.beans.CSVPullSpec;
47  import org.apache.wicket.PageReference;
48  import org.apache.wicket.ajax.AjaxRequestTarget;
49  import org.apache.wicket.ajax.form.AjaxFormSubmitBehavior;
50  import org.apache.wicket.ajax.markup.html.form.AjaxCheckBox;
51  import org.apache.wicket.event.IEventSink;
52  import org.apache.wicket.extensions.wizard.WizardModel;
53  import org.apache.wicket.extensions.wizard.WizardStep;
54  import org.apache.wicket.markup.html.basic.Label;
55  import org.apache.wicket.markup.html.form.Radio;
56  import org.apache.wicket.markup.html.form.RadioGroup;
57  import org.apache.wicket.markup.html.form.upload.FileUpload;
58  import org.apache.wicket.markup.html.list.ListItem;
59  import org.apache.wicket.markup.html.list.ListView;
60  import org.apache.wicket.model.IModel;
61  import org.apache.wicket.model.LoadableDetachableModel;
62  import org.apache.wicket.model.Model;
63  import org.apache.wicket.model.PropertyModel;
64  import org.apache.wicket.model.util.ListModel;
65  import org.apache.wicket.util.lang.Bytes;
66  
67  public class CSVPullWizardBuilder extends BaseAjaxWizardBuilder<CSVPullSpec> {
68  
69      private static final long serialVersionUID = -4049787433975145383L;
70  
71      public enum LineSeparator {
72          LF("\n"),
73          CR("\r"),
74          CRLF("\r\n");
75  
76          private final String repr;
77  
78          LineSeparator(final String repr) {
79              this.repr = repr;
80          }
81  
82          public String getRepr() {
83              return repr;
84          }
85  
86          public static LineSeparator byRepr(final String repr) {
87              for (LineSeparator value : values()) {
88                  if (value.getRepr().equals(repr)) {
89                      return value;
90                  }
91              }
92              return null;
93          }
94      }
95  
96      protected final Model<byte[]> csv = new Model<>();
97  
98      protected final ReconciliationRestClient reconciliationRestClient;
99  
100     protected final ImplementationRestClient implementationRestClient;
101 
102     protected final Bytes maxUploadSize;
103 
104     public CSVPullWizardBuilder(
105             final CSVPullSpec defaultItem,
106             final ReconciliationRestClient reconciliationRestClient,
107             final ImplementationRestClient implementationRestClient,
108             final PageReference pageRef) {
109 
110         super(defaultItem, pageRef);
111 
112         this.reconciliationRestClient = reconciliationRestClient;
113         this.implementationRestClient = implementationRestClient;
114         this.maxUploadSize = Bytes.megabytes(SyncopeWebApplication.get().getMaxUploadFileSizeMB());
115     }
116 
117     @Override
118     public CSVPullWizardBuilder setEventSink(final IEventSink eventSink) {
119         super.setEventSink(eventSink);
120         return this;
121     }
122 
123     @Override
124     protected ArrayList<ProvisioningReport> onApplyInternal(final CSVPullSpec modelObject) {
125         return reconciliationRestClient.pull(modelObject, new ByteArrayInputStream(csv.getObject()));
126     }
127 
128     @Override
129     protected WizardModel buildModelSteps(final CSVPullSpec modelObject, final WizardModel wizardModel) {
130         wizardModel.add(new Details(modelObject));
131         wizardModel.add(new Columns(modelObject));
132         wizardModel.add(new PullTask(modelObject));
133         return wizardModel;
134     }
135 
136     public class Details extends WizardStep {
137 
138         private static final long serialVersionUID = -4736870165235853919L;
139 
140         public Details(final CSVPullSpec spec) {
141             FileInputConfig csvFile = new FileInputConfig().
142                     showUpload(false).showRemove(false).showPreview(false).
143                     browseClass("btn btn-success").browseIcon("<i class=\"fas fa-folder-open\"></i> &nbsp;");
144             String language = SyncopeConsoleSession.get().getLocale().getLanguage();
145             if (!Locale.ENGLISH.getLanguage().equals(language)) {
146                 csvFile.withLocale(language);
147             }
148             BootstrapFileInputField csvUpload =
149                     new BootstrapFileInputField("csvUpload", new ListModel<>(new ArrayList<>()), csvFile);
150             csvUpload.add(new AjaxFormSubmitBehavior(Constants.ON_CHANGE) {
151 
152                 private static final long serialVersionUID = 5538299138211283825L;
153 
154                 @Override
155                 protected void onSubmit(final AjaxRequestTarget target) {
156                     FileUpload uploadedFile = csvUpload.getFileUpload();
157                     if (uploadedFile != null) {
158                         if (maxUploadSize != null && uploadedFile.getSize() > maxUploadSize.bytes()) {
159                             SyncopeConsoleSession.get().error(getString("tooLargeFile").
160                                     replace("${maxUploadSizeB}", String.valueOf(maxUploadSize.bytes())).
161                                     replace("${maxUploadSizeMB}", String.valueOf(maxUploadSize.bytes() / 1000000L)));
162                             ((BasePage) getPageReference().getPage()).getNotificationPanel().refresh(target);
163                         } else {
164                             csv.setObject(uploadedFile.getBytes());
165                         }
166                     }
167                 }
168             });
169             add(csvUpload.setRequired(true).setOutputMarkupId(true));
170 
171             add(new CSVConfPanel("csvconf", spec));
172         }
173     }
174 
175     public class Columns extends WizardStep implements WizardModel.ICondition {
176 
177         private static final long serialVersionUID = -4136764210454067728L;
178 
179         private final CSVPullSpec spec;
180 
181         private final ListModel<String> columnsModel = new ListModel<>();
182 
183         public Columns(final CSVPullSpec spec) {
184             this.spec = spec;
185 
186             RadioGroup<String> keyColumn = new RadioGroup<>("keyColumn", new PropertyModel<>(spec, "keyColumn"));
187             keyColumn.setOutputMarkupId(true);
188             keyColumn.setOutputMarkupPlaceholderTag(true);
189             keyColumn.setRenderBodyOnly(false);
190             keyColumn.setRequired(true);
191             add(keyColumn);
192 
193             keyColumn.add(new ListView<>("columns", columnsModel) {
194 
195                 private static final long serialVersionUID = -9112553137618363167L;
196 
197                 @Override
198                 protected void populateItem(final ListItem<String> item) {
199                     item.add(new Label("column", item.getModelObject()));
200                     item.add(new Radio<>("key", new Model<>(item.getModelObject()), keyColumn));
201                     item.add(new AjaxCheckBox("ignore", new Model<>()) {
202 
203                         private static final long serialVersionUID = -6139318907146065915L;
204 
205                         @Override
206                         protected void onUpdate(final AjaxRequestTarget target) {
207                             if (spec.getIgnoreColumns().contains(item.getModelObject())) {
208                                 spec.getIgnoreColumns().remove(item.getModelObject());
209                             } else {
210                                 spec.getIgnoreColumns().add(item.getModelObject());
211                             }
212                         }
213                     });
214                 }
215             }.setReuseItems(true));
216         }
217 
218         @Override
219         public boolean evaluate() {
220             if (csv.getObject() != null) {
221                 String header = StringUtils.substringBefore(new String(csv.getObject()), spec.getLineSeparator());
222                 columnsModel.setObject(Stream.of(StringUtils.split(header, spec.getColumnSeparator())).
223                         map(value -> {
224                             String unquoted = value;
225                             if (unquoted.charAt(0) == spec.getQuoteChar()) {
226                                 unquoted = unquoted.substring(1);
227                             }
228                             if (unquoted.charAt(unquoted.length() - 1) == spec.getQuoteChar()) {
229                                 unquoted = unquoted.substring(0, unquoted.length() - 1);
230                             }
231                             return unquoted;
232                         }).collect(Collectors.toList()));
233             }
234 
235             return true;
236         }
237     }
238 
239     public class PullTask extends WizardStep {
240 
241         private static final long serialVersionUID = -8954789648303078732L;
242 
243         private final IModel<List<String>> pullActions = new LoadableDetachableModel<>() {
244 
245             private static final long serialVersionUID = 4659376149825914247L;
246 
247             @Override
248             protected List<String> load() {
249                 return implementationRestClient.list(IdMImplementationType.PULL_ACTIONS).stream().
250                         map(ImplementationTO::getKey).sorted().collect(Collectors.toList());
251             }
252         };
253 
254         private final IModel<List<String>> pullCorrelationRules = new LoadableDetachableModel<>() {
255 
256             private static final long serialVersionUID = 4659376149825914247L;
257 
258             @Override
259             protected List<String> load() {
260                 return implementationRestClient.list(IdMImplementationType.PULL_CORRELATION_RULE).stream().
261                         map(ImplementationTO::getKey).sorted().collect(Collectors.toList());
262             }
263         };
264 
265         public PullTask(final CSVPullSpec spec) {
266             AjaxCheckBoxPanel remediation = new AjaxCheckBoxPanel(
267                     "remediation", "remediation", new PropertyModel<>(spec, "remediation"), false);
268             add(remediation);
269 
270             AjaxDropDownChoicePanel<MatchingRule> matchingRule = new AjaxDropDownChoicePanel<>(
271                     "matchingRule", "matchingRule", new PropertyModel<>(spec, "matchingRule"), false);
272             matchingRule.setChoices(List.of(MatchingRule.values()));
273             add(matchingRule);
274 
275             AjaxDropDownChoicePanel<UnmatchingRule> unmatchingRule = new AjaxDropDownChoicePanel<>(
276                     "unmatchingRule", "unmatchingRule", new PropertyModel<>(spec, "unmatchingRule"),
277                     false);
278             unmatchingRule.setChoices(List.of(UnmatchingRule.values()));
279             add(unmatchingRule);
280 
281             AjaxPalettePanel<String> provisioningActions =
282                     new AjaxPalettePanel.Builder<String>().build("provisioningActions",
283                             new PropertyModel<>(spec, "provisioningActions"), new ListModel<>(pullActions.getObject()));
284             add(provisioningActions);
285 
286             AjaxDropDownChoicePanel<ConflictResolutionAction> conflictResolutionAction = new AjaxDropDownChoicePanel<>(
287                     "conflictResolutionAction", "conflictResolutionAction",
288                     new PropertyModel<>(spec, "conflictResolutionAction"), false);
289             conflictResolutionAction.setChoices(List.of(ConflictResolutionAction.values()));
290             conflictResolutionAction.setRequired(true);
291             add(conflictResolutionAction);
292 
293             AjaxDropDownChoicePanel<String> pullCorrelationRule = new AjaxDropDownChoicePanel<>(
294                     "pullCorrelationRule", "pullCorrelationRule", new PropertyModel<>(spec, "pullCorrelationRule"),
295                     false);
296             pullCorrelationRule.setChoices(pullCorrelationRules);
297             add(pullCorrelationRule);
298         }
299     }
300 }