/*
* 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.syncope.client.console.panels.search;
import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggle;
import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.checkbox.bootstraptoggle.BootstrapToggleConfig;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.StringValueTransformer;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.client.console.commons.Constants;
import org.apache.syncope.client.console.panels.search.SearchClause.Comparator;
import org.apache.syncope.client.console.panels.search.SearchClause.Operator;
import org.apache.syncope.client.console.panels.search.SearchClause.Type;
import org.apache.syncope.client.console.rest.RelationshipTypeRestClient;
import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxEventBehavior;
import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel;
import org.apache.syncope.common.lib.to.RelationshipTypeTO;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxCallListener;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.event.IEventSink;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.CheckBox;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
public class SearchClausePanel extends FieldPanel<SearchClause> {
private static final long serialVersionUID = -527351923968737757L;
private final boolean required;
private final IModel<List<SearchClause.Type>> types;
private final IModel<List<String>> anames;
private final IModel<List<String>> dnames;
private final IModel<Map<String, String>> groupNames;
private final IModel<List<String>> roleNames;
private final IModel<List<String>> resourceNames;
private IModel<SearchClause> clause;
private final LoadableDetachableModel<List<Comparator>> comparators;
private final LoadableDetachableModel<List<String>> properties;
private final Fragment operatorFragment;
private final Fragment searchButtonFragment;
private final AjaxSubmitLink searchButton;
private IEventSink resultContainer;
public SearchClausePanel(
final String id,
final String name,
final Model<SearchClause> clause,
final boolean required,
final IModel<List<SearchClause.Type>> types,
final IModel<List<String>> anames,
final IModel<List<String>> dnames,
final IModel<Map<String, String>> groupNames,
final IModel<List<String>> roleNames,
final IModel<List<String>> resourceNames) {
super(id, name, clause);
this.clause = clause == null ? new Model<SearchClause>(null) : clause;
this.required = required;
this.types = types;
this.anames = anames;
this.dnames = dnames;
this.groupNames = groupNames;
this.roleNames = roleNames;
this.resourceNames = resourceNames;
searchButton = new AjaxSubmitLink("search") {
private static final long serialVersionUID = 5538299138211283825L;
@Override
protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
if (resultContainer == null) {
send(this, Broadcast.BUBBLE, new SearchEvent(target));
} else {
send(resultContainer, Broadcast.EXACT, new SearchEvent(target));
}
}
};
searchButtonFragment = new Fragment("operator", "searchButtonFragment", this);
searchButtonFragment.add(searchButton.setEnabled(false));
operatorFragment = new Fragment("operator", "operatorFragment", this);
field = new FormComponent<SearchClause>("container", this.clause) {
private static final long serialVersionUID = -8204140666393922700L;
};
add(field);
comparators = new LoadableDetachableModel<List<Comparator>>() {
private static final long serialVersionUID = 5275935387613157437L;
@Override
protected List<Comparator> load() {
if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) {
return Collections.<Comparator>emptyList();
}
switch (field.getModel().getObject().getType()) {
case ATTRIBUTE:
return Arrays.asList(SearchClause.Comparator.values());
case ROLE_MEMBERSHIP:
case GROUP_MEMBERSHIP:
case GROUP_MEMBER:
case RESOURCE:
return Arrays.asList(
SearchClause.Comparator.EQUALS,
SearchClause.Comparator.NOT_EQUALS);
case RELATIONSHIP:
return Arrays.asList(
SearchClause.Comparator.IS_NOT_NULL,
SearchClause.Comparator.IS_NULL,
SearchClause.Comparator.EQUALS,
SearchClause.Comparator.NOT_EQUALS);
default:
return Collections.<Comparator>emptyList();
}
}
};
properties = new LoadableDetachableModel<List<String>>() {
private static final long serialVersionUID = 5275935387613157437L;
@Override
protected List<String> load() {
if (field.getModel().getObject() == null || field.getModel().getObject().getType() == null) {
return Collections.<String>emptyList();
}
switch (field.getModel().getObject().getType()) {
case ATTRIBUTE:
final List<String> names = new ArrayList<>(dnames.getObject());
if (anames != null && anames.getObject() != null && !anames.getObject().isEmpty()) {
names.addAll(anames.getObject());
}
Collections.sort(names);
return names;
case GROUP_MEMBERSHIP:
final List<String> groups = CollectionUtils.collect(groupNames.getObject().keySet(),
StringValueTransformer.<String>stringValueTransformer(), new ArrayList<String>());
Collections.sort(groups);
return groups;
case ROLE_MEMBERSHIP:
final List<String> roles = new ArrayList<>(roleNames.getObject());
Collections.sort(roles);
return roles;
case RESOURCE:
final List<String> resources = new ArrayList<>(resourceNames.getObject());
Collections.sort(resources);
return resources;
case RELATIONSHIP:
final List<String> relations = CollectionUtils.collect(
new RelationshipTypeRestClient().list(), new Transformer<RelationshipTypeTO, String>() {
@Override
public String transform(final RelationshipTypeTO input) {
return input.getKey();
}
}, new ArrayList<String>());
return relations;
default:
return Collections.<String>emptyList();
}
}
};
}
public void enableSearch(final IEventSink resultContainer) {
this.resultContainer = resultContainer;
this.searchButton.setEnabled(true);
field.add(AttributeModifier.replace(
"onkeydown",
Model.of("if(event.keyCode == 13) {event.preventDefault();}")));
field.add(new AjaxEventBehavior("onkeydown") {
private static final long serialVersionUID = -7133385027739964990L;
@Override
protected void onEvent(final AjaxRequestTarget target) {
if (resultContainer == null) {
send(SearchClausePanel.this, Broadcast.BUBBLE, new SearchEvent(target));
} else {
send(resultContainer, Broadcast.EXACT, new SearchEvent(target));
}
}
@Override
protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new AjaxCallListener() {
private static final long serialVersionUID = 7160235486520935153L;
@Override
public CharSequence getPrecondition(final Component component) {
return "if (Wicket.Event.keyCode(attrs.event) == 13) { return true; } else { return false; }";
}
});
}
});
}
@Override
public SearchClause getModelObject() {
return this.clause.getObject();
}
@Override
public FieldPanel<SearchClause> setModelObject(final SearchClause object) {
this.clause.setObject(object);
return super.setModelObject(object);
}
@Override
@SuppressWarnings("rawtypes")
public FieldPanel<SearchClause> setNewModel(final ListItem item) {
clause.setObject(SearchClause.class.cast(item.getModelObject()));
return this;
}
@Override
public FieldPanel<SearchClause> setNewModel(final IModel<SearchClause> model) {
clause = model;
return super.setNewModel(model);
}
@Override
public final MarkupContainer add(final Component... childs) {
return super.add(childs);
}
@Override
public FieldPanel<SearchClause> settingsDependingComponents() {
final SearchClause searchClause = this.clause.getObject();
final WebMarkupContainer operatorContainer = new WebMarkupContainer("operatorContainer");
operatorContainer.setOutputMarkupId(true);
field.add(operatorContainer);
final BootstrapToggleConfig config = new BootstrapToggleConfig().
withOnStyle(BootstrapToggleConfig.Style.info).
withOffStyle(BootstrapToggleConfig.Style.warning).
withSize(BootstrapToggleConfig.Size.mini);
operatorFragment.add(new BootstrapToggle("operator", new Model<Boolean>() {
private static final long serialVersionUID = -7157802546272668001L;
@Override
public Boolean getObject() {
return searchClause.getOperator() == Operator.AND;
}
@Override
public void setObject(final Boolean object) {
searchClause.setOperator(object ? Operator.AND : Operator.OR);
}
}, config) {
private static final long serialVersionUID = 2969634208049189343L;
@Override
protected IModel<String> getOffLabel() {
return Model.of("OR");
}
@Override
protected IModel<String> getOnLabel() {
return Model.of("AND");
}
@Override
protected CheckBox newCheckBox(final String id, final IModel<Boolean> model) {
final CheckBox checkBox = super.newCheckBox(id, model);
checkBox.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(final AjaxRequestTarget target) {
}
});
return checkBox;
}
}.setOutputMarkupPlaceholderTag(true));
if (getIndex() > 0) {
operatorContainer.add(operatorFragment);
} else {
operatorContainer.add(searchButtonFragment);
}
final AjaxDropDownChoicePanel<String> property = new AjaxDropDownChoicePanel<>(
"property", "property", new PropertyModel<String>(searchClause, "property"));
property.hideLabel().setRequired(required).setOutputMarkupId(true);
property.setChoices(properties);
property.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
private static final long serialVersionUID = -1107858522700306810L;
@Override
protected void onUpdate(final AjaxRequestTarget target) {
}
});
field.add(property);
final AjaxDropDownChoicePanel<SearchClause.Comparator> comparator = new AjaxDropDownChoicePanel<>(
"comparator", "comparator", new PropertyModel<SearchClause.Comparator>(searchClause, "comparator"));
comparator.setChoices(comparators);
comparator.setNullValid(false).hideLabel().setOutputMarkupId(true);
comparator.setRequired(required);
comparator.setChoiceRenderer(getComparatorRender(field.getModel()));
field.add(comparator);
final AjaxTextFieldPanel value = new AjaxTextFieldPanel(
"value", "value", new PropertyModel<String>(searchClause, "value"), false);
value.hideLabel().setOutputMarkupId(true);
field.add(value);
value.getField().add(AttributeModifier.replace(
"onkeydown",
Model.of("if(event.keyCode == 13) {event.preventDefault();}")));
value.getField().add(new IndicatorAjaxEventBehavior("onkeydown") {
private static final long serialVersionUID = -7133385027739964990L;
@Override
protected void onEvent(final AjaxRequestTarget target) {
target.focusComponent(null);
value.getField().inputChanged();
value.getField().validate();
if (value.getField().isValid()) {
value.getField().valid();
value.getField().updateModel();
}
}
@Override
protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new AjaxCallListener() {
private static final long serialVersionUID = 7160235486520935153L;
@Override
public CharSequence getPrecondition(final Component component) {
return "if (Wicket.Event.keyCode(attrs.event) == 13) { return true; } else { return false; }";
}
});
}
});
final AjaxDropDownChoicePanel<SearchClause.Type> type = new AjaxDropDownChoicePanel<>(
"type", "type", new PropertyModel<SearchClause.Type>(searchClause, "type"));
type.setChoices(types).hideLabel().setRequired(required).setOutputMarkupId(true);
type.setNullValid(false);
type.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
private static final long serialVersionUID = -1107858522700306810L;
@Override
protected void onUpdate(final AjaxRequestTarget target) {
final SearchClause searchClause = new SearchClause();
searchClause.setType(Type.valueOf(type.getDefaultModelObjectAsString()));
SearchClausePanel.this.clause.setObject(searchClause);
setFieldAccess(searchClause.getType(), property, comparator, value);
target.add(property);
target.add(comparator);
target.add(value);
}
});
field.add(type);
comparator.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
private static final long serialVersionUID = -1107858522700306810L;
@Override
protected void onUpdate(final AjaxRequestTarget target) {
if (type.getModelObject() == SearchClause.Type.ATTRIBUTE
|| type.getModelObject() == SearchClause.Type.RELATIONSHIP) {
if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL
|| comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) {
value.setModelObject(StringUtils.EMPTY);
value.setEnabled(false);
} else {
value.setEnabled(true);
}
target.add(value);
}
if (type.getModelObject() == SearchClause.Type.RELATIONSHIP) {
if (comparator.getModelObject() == SearchClause.Comparator.EQUALS
|| comparator.getModelObject() == SearchClause.Comparator.NOT_EQUALS) {
property.setEnabled(false);
} else {
property.setEnabled(true);
}
final SearchClause searchClause = new SearchClause();
searchClause.setType(Type.valueOf(type.getDefaultModelObjectAsString()));
searchClause.setComparator(comparator.getModelObject());
SearchClausePanel.this.clause.setObject(searchClause);
target.add(property);
}
}
});
setFieldAccess(searchClause.getType(), property, comparator, value);
return this;
}
private void setFieldAccess(
final Type type,
final AjaxDropDownChoicePanel<String> property,
final FieldPanel<Comparator> comparator,
final FieldPanel<String> value) {
if (type != null) {
property.setEnabled(true);
comparator.setEnabled(true);
value.setEnabled(true);
switch (type) {
case ATTRIBUTE:
if (!comparator.isEnabled()) {
comparator.setEnabled(true);
comparator.setRequired(true);
}
if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL
|| comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) {
value.setEnabled(false);
value.setModelObject(StringUtils.EMPTY);
}
property.setChoiceRenderer(new DefaultChoiceRender());
break;
case ROLE_MEMBERSHIP:
property.setChoiceRenderer(new DefaultChoiceRender());
value.setEnabled(false);
value.setModelObject(StringUtils.EMPTY);
break;
case GROUP_MEMBERSHIP:
property.setChoiceRenderer(new GroupChoiceRender());
value.setEnabled(false);
value.setModelObject(StringUtils.EMPTY);
break;
case GROUP_MEMBER:
value.setEnabled(true);
property.setEnabled(false);
property.setModelObject(null);
break;
case RESOURCE:
property.setChoiceRenderer(new DefaultChoiceRender());
value.setEnabled(false);
value.setModelObject(StringUtils.EMPTY);
break;
case RELATIONSHIP:
property.setChoiceRenderer(new DefaultChoiceRender());
if (comparator.getModelObject() == SearchClause.Comparator.IS_NULL
|| comparator.getModelObject() == SearchClause.Comparator.IS_NOT_NULL) {
value.setEnabled(false);
value.setModelObject(StringUtils.EMPTY);
property.setEnabled(true);
} else {
value.setEnabled(true);
property.setEnabled(false);
property.setModelObject(null);
}
break;
default:
break;
}
}
}
private IChoiceRenderer<SearchClause.Comparator> getComparatorRender(final IModel<SearchClause> clause) {
return new IChoiceRenderer<SearchClause.Comparator>() {
private static final long serialVersionUID = -9086043750227867686L;
@Override
public Object getDisplayValue(final SearchClause.Comparator object) {
if (clause == null || clause.getObject() == null || clause.getObject().getType() == null) {
return object.toString();
}
String display;
switch (clause.getObject().getType()) {
case ATTRIBUTE:
switch (object) {
case IS_NULL:
display = "NULL";
break;
case IS_NOT_NULL:
display = "NOT NULL";
break;
case EQUALS:
display = "==";
break;
case NOT_EQUALS:
display = "!=";
break;
case LESS_THAN:
display = "<";
break;
case LESS_OR_EQUALS:
display = "<=";
break;
case GREATER_THAN:
display = ">";
break;
case GREATER_OR_EQUALS:
display = ">=";
break;
default:
display = StringUtils.EMPTY;
}
break;
case GROUP_MEMBERSHIP:
switch (object) {
case EQUALS:
display = "IN";
break;
case NOT_EQUALS:
display = "NOT IN";
break;
default:
display = StringUtils.EMPTY;
}
break;
case GROUP_MEMBER:
switch (object) {
case EQUALS:
display = "WITH";
break;
case NOT_EQUALS:
display = "NOT WITH";
break;
default:
display = StringUtils.EMPTY;
}
break;
case ROLE_MEMBERSHIP:
case RESOURCE:
switch (object) {
case EQUALS:
display = "HAS";
break;
case NOT_EQUALS:
display = "HAS NOT";
break;
default:
display = StringUtils.EMPTY;
}
break;
case RELATIONSHIP:
switch (object) {
case IS_NOT_NULL:
display = "EXIST";
break;
case IS_NULL:
display = "NOT EXIST";
break;
case EQUALS:
display = "WITH";
break;
case NOT_EQUALS:
display = "NOT WITH";
break;
default:
display = StringUtils.EMPTY;
}
break;
default:
display = object.toString();
}
return display;
}
@Override
public String getIdValue(final SearchClause.Comparator object, final int index) {
return getDisplayValue(object).toString();
}
@Override
public SearchClause.Comparator getObject(
final String id, final IModel<? extends List<? extends SearchClause.Comparator>> choices) {
if (id == null) {
return SearchClause.Comparator.EQUALS;
}
final SearchClause.Comparator res;
switch (id) {
case "HAS":
case "IN":
case "WITH":
res = SearchClause.Comparator.EQUALS;
break;
case "HAS NOT":
case "NOT IN":
case "NOT WITH":
res = SearchClause.Comparator.NOT_EQUALS;
break;
case "NULL":
case "NOT EXIST":
res = SearchClause.Comparator.IS_NULL;
break;
case "NOT NULL":
case "EXIST":
res = SearchClause.Comparator.IS_NOT_NULL;
break;
case "==":
res = SearchClause.Comparator.EQUALS;
break;
case "!=":
res = SearchClause.Comparator.NOT_EQUALS;
break;
case "<":
res = SearchClause.Comparator.LESS_THAN;
break;
case "<=":
res = SearchClause.Comparator.LESS_OR_EQUALS;
break;
case ">":
res = SearchClause.Comparator.GREATER_THAN;
break;
case ">=":
res = SearchClause.Comparator.GREATER_OR_EQUALS;
break;
default:
// EQUALS to be used as default value
res = SearchClause.Comparator.EQUALS;
break;
}
return res;
}
};
}
@Override
public FieldPanel<SearchClause> clone() {
final SearchClausePanel panel = new SearchClausePanel(
getId(), name, null, required, types, anames, dnames, groupNames, roleNames, resourceNames);
panel.setReadOnly(this.isReadOnly());
panel.setRequired(this.isRequired());
if (searchButton.isEnabled()) {
panel.enableSearch(resultContainer);
}
return panel;
}
private class DefaultChoiceRender implements IChoiceRenderer<String> {
private static final long serialVersionUID = -8034248752951761058L;
@Override
public Object getDisplayValue(final String object) {
return object;
}
@Override
public String getIdValue(final String object, final int index) {
return object;
}
@Override
public String getObject(final String id, final IModel<? extends List<? extends String>> choices) {
return id;
}
}
private class GroupChoiceRender extends DefaultChoiceRender {
private static final long serialVersionUID = -8034248752951761058L;
@Override
public String getIdValue(final String object, final int index) {
return object;
}
@Override
public String getObject(final String id, final IModel<? extends List<? extends String>> choices) {
return id;
}
@Override
public Object getDisplayValue(final String object) {
return groupNames.getObject().get(object);
}
}
public static class SearchEvent implements Serializable {
private static final long serialVersionUID = 2693338614198749301L;
private final AjaxRequestTarget target;
public SearchEvent(final AjaxRequestTarget target) {
this.target = target;
}
public AjaxRequestTarget getTarget() {
return target;
}
}
}