/* * Copyright (c) 2010-2015 Evolveum * * Licensed 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 com.evolveum.midpoint.web.component.search; import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.marshaller.QueryConvertor; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyStringNormalizer; import com.evolveum.midpoint.prism.query.*; import com.evolveum.midpoint.prism.query.builder.QueryBuilder; import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.DisplayableValue; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.SearchBoxModeType; import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * @author Viliam Repan (lazyman) */ public class Search implements Serializable, DebugDumpable { public static final String F_AVAILABLE_DEFINITIONS = "availableDefinitions"; public static final String F_ITEMS = "items"; public static final String F_ADVANCED_QUERY = "advancedQuery"; public static final String F_ADVANCED_ERROR = "advancedError"; public static final String F_FULL_TEXT = "fullText"; private static final Trace LOGGER = TraceManager.getTrace(Search.class); private SearchBoxModeType searchType; private boolean showAdvanced = false; private boolean isFullTextSearchEnabled = false; private String advancedQuery; private String advancedError; private String fullText; private Class<? extends ObjectType> type; private Map<ItemPath, ItemDefinition> allDefinitions; private List<ItemDefinition> availableDefinitions = new ArrayList<>(); private List<SearchItem> items = new ArrayList<>(); public Search(Class<? extends ObjectType> type, Map<ItemPath, ItemDefinition> allDefinitions) { this(type, allDefinitions, false, null); } public Search(Class<? extends ObjectType> type, Map<ItemPath, ItemDefinition> allDefinitions, boolean isFullTextSearchEnabled, SearchBoxModeType searchBoxModeType) { this.type = type; this.allDefinitions = allDefinitions; this.isFullTextSearchEnabled = isFullTextSearchEnabled; if (searchBoxModeType != null){ searchType = searchBoxModeType; } else if (isFullTextSearchEnabled ){ searchType = SearchBoxModeType.FULLTEXT; } else { searchType = SearchBoxModeType.BASIC; } availableDefinitions.addAll(allDefinitions.values()); } public List<SearchItem> getItems() { return Collections.unmodifiableList(items); } public List<ItemDefinition> getAvailableDefinitions() { return Collections.unmodifiableList(availableDefinitions); } public List<ItemDefinition> getAllDefinitions() { return new ArrayList<>(allDefinitions.values()); } public SearchItem addItem(ItemDefinition def) { boolean isPresent = false; for (ItemDefinition itemDefinition : availableDefinitions){ if (itemDefinition.getName() != null && itemDefinition.getName().equals(def.getName())){ isPresent = true; break; } } if (!isPresent){ return null; } ItemPath path = null; ItemDefinition itemToRemove = null; for (Map.Entry<ItemPath, ItemDefinition> entry : allDefinitions.entrySet()) { if (entry.getValue().getName().equals(def.getName())) { path = entry.getKey(); itemToRemove = entry.getValue(); break; } } if (path == null) { return null; } SearchItem item = new SearchItem(this, path, def); item.getValues().add(new SearchValue<>()); items.add(item); if (itemToRemove != null) { availableDefinitions.remove(itemToRemove); } return item; } public void delete(SearchItem item) { if (items.remove(item)) { availableDefinitions.add(item.getDefinition()); } } public Class<? extends ObjectType> getType() { return type; } public ObjectQuery createObjectQuery(PrismContext ctx) { LOGGER.debug("Creating query from {}", this); if (SearchBoxModeType.ADVANCED.equals(searchType)){ return createObjectQueryAdvanced(ctx); } else if (SearchBoxModeType.FULLTEXT.equals(searchType)){ return createObjectQueryFullText(ctx); } else { return createObjectQuerySimple(ctx); } } public ObjectQuery createObjectQuerySimple(PrismContext ctx) { List<SearchItem> searchItems = getItems(); if (searchItems.isEmpty()) { return null; } List<ObjectFilter> conditions = new ArrayList<>(); for (SearchItem item : searchItems) { ObjectFilter filter = createFilterForSearchItem(item, ctx); if (filter != null) { conditions.add(filter); } } switch (conditions.size()) { case 0: return null; case 1: return ObjectQuery.createObjectQuery(conditions.get(0)); default: AndFilter and = AndFilter.createAnd(conditions); return ObjectQuery.createObjectQuery(and); } } private ObjectFilter createFilterForSearchItem(SearchItem item, PrismContext ctx) { if (item.getValues().isEmpty()) { return null; } List<ObjectFilter> conditions = new ArrayList<>(); for (DisplayableValue value : (List<DisplayableValue>) item.getValues()) { if (value.getValue() == null) { continue; } ObjectFilter filter = createFilterForSearchValue(item, value, ctx); if (filter != null) { conditions.add(filter); } } switch (conditions.size()) { case 0: return null; case 1: return conditions.get(0); default: return OrFilter.createOr(conditions); } } private ObjectFilter createFilterForSearchValue(SearchItem item, DisplayableValue searchValue, PrismContext ctx) { ItemDefinition definition = item.getDefinition(); ItemPath path = item.getPath(); if (definition instanceof PrismReferenceDefinition) { return QueryBuilder.queryFor(ObjectType.class, ctx) .item(path, definition).ref((PrismReferenceValue) searchValue.getValue()) .buildFilter(); } PrismPropertyDefinition propDef = (PrismPropertyDefinition) definition; if ((propDef.getAllowedValues() != null && !propDef.getAllowedValues().isEmpty()) || DOMUtil.XSD_BOOLEAN.equals(propDef.getTypeName())) { //we're looking for enum value, therefore equals filter is ok //or if it's boolean value DisplayableValue displayableValue = (DisplayableValue) searchValue.getValue(); Object value = displayableValue.getValue(); return QueryBuilder.queryFor(ObjectType.class, ctx) .item(path, propDef).eq(value).buildFilter(); } else if (DOMUtil.XSD_INT.equals(propDef.getTypeName()) || DOMUtil.XSD_INTEGER.equals(propDef.getTypeName()) || DOMUtil.XSD_LONG.equals(propDef.getTypeName()) || DOMUtil.XSD_SHORT.equals(propDef.getTypeName())) { String text = (String) searchValue.getValue(); if (!StringUtils.isNumeric(text) && (searchValue instanceof SearchValue)) { ((SearchValue) searchValue).clear(); return null; } Object value = Long.parseLong((String) searchValue.getValue()); return QueryBuilder.queryFor(ObjectType.class, ctx) .item(path, propDef).eq(value).buildFilter(); } else if (DOMUtil.XSD_STRING.equals(propDef.getTypeName())) { String text = (String) searchValue.getValue(); return QueryBuilder.queryFor(ObjectType.class, ctx) .item(path, propDef).contains(text).matchingCaseIgnore().buildFilter(); } else if (SchemaConstants.T_POLY_STRING_TYPE.equals(propDef.getTypeName())) { //we're looking for string value, therefore substring filter should be used String text = (String) searchValue.getValue(); PolyStringNormalizer normalizer = ctx.getDefaultPolyStringNormalizer(); String value = normalizer.normalize(text); return QueryBuilder.queryFor(ObjectType.class, ctx) .item(path, propDef).contains(text).matchingNorm().buildFilter(); } //we don't know how to create filter from search item, should not happen, ha ha ha :) //at least we try to cleanup field if (searchValue instanceof SearchValue) { ((SearchValue) searchValue).clear(); } return null; } public boolean isShowAdvanced() { return showAdvanced; } public void setShowAdvanced(boolean showAdvanced) { this.showAdvanced = showAdvanced; } public String getAdvancedQuery() { return advancedQuery; } public void setAdvancedQuery(String advancedQuery) { this.advancedQuery = advancedQuery; } public String getFullText() { return fullText; } public void setFullText(String fullText) { this.fullText = fullText; } public ObjectQuery createObjectQueryAdvanced(PrismContext ctx) { try { advancedError = null; ObjectFilter filter = createAdvancedObjectFilter(ctx); if (filter == null) { return null; } return ObjectQuery.createObjectQuery(filter); } catch (Exception ex) { advancedError = createErrorMessage(ex); } return null; } public ObjectQuery createObjectQueryFullText(PrismContext ctx) { if (StringUtils.isEmpty(fullText)){ return null; } ObjectQuery query = QueryBuilder.queryFor(type, ctx) .fullText(fullText) .build(); return query; } private ObjectFilter createAdvancedObjectFilter(PrismContext ctx) throws SchemaException { if (StringUtils.isEmpty(advancedQuery)) { return null; } SearchFilterType search = ctx.parserFor(advancedQuery).type(SearchFilterType.COMPLEX_TYPE).parseRealValue(); return QueryConvertor.parseFilter(search, type, ctx); } public boolean isAdvancedQueryValid(PrismContext ctx) { try { advancedError = null; createAdvancedObjectFilter(ctx); return true; } catch (Exception ex) { advancedError = createErrorMessage(ex); } return false; } public SearchBoxModeType getSearchType() { return searchType; } public void setSearchType(SearchBoxModeType searchType) { this.searchType = searchType; } public boolean isFullTextSearchEnabled() { return isFullTextSearchEnabled; } public void setFullTextSearchEnabled(boolean fullTextSearchEnabled) { isFullTextSearchEnabled = fullTextSearchEnabled; } private String createErrorMessage(Exception ex) { StringBuilder sb = new StringBuilder(); Throwable t = ex; while (t != null) { sb.append(t.getMessage()).append('\n'); t = t.getCause(); } return sb.toString(); } public String getAdvancedError() { return advancedError; } @Override public String toString() { return new ToStringBuilder(this) .append("items", items) .toString(); } @Override public String debugDump() { return debugDump(0); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); DebugUtil.indentDebugDump(sb, indent); sb.append("Search\n"); DebugUtil.debugDumpWithLabelLn(sb, "showAdvanced", showAdvanced, indent+1); DebugUtil.debugDumpWithLabelLn(sb, "advancedQuery", advancedQuery, indent+1); DebugUtil.debugDumpWithLabelLn(sb, "advancedError", advancedError, indent+1); DebugUtil.debugDumpWithLabelLn(sb, "type", type, indent+1); DebugUtil.debugDumpWithLabelLn(sb, "allDefinitions", allDefinitions, indent+1); DebugUtil.debugDumpWithLabelLn(sb, "availableDefinitions", availableDefinitions, indent+1); DebugUtil.debugDumpWithLabel(sb, "items", items, indent+1); return sb.toString(); } }