/*
* Copyright (c) 2010-2013 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.repo.sql.data.common;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.repo.sql.data.RepositoryContext;
import com.evolveum.midpoint.repo.sql.data.common.id.RObjectTextInfoId;
import com.evolveum.midpoint.repo.sql.query2.definition.NotQueryable;
import com.evolveum.midpoint.repo.sql.util.RUtil;
import com.evolveum.midpoint.schema.util.FullTextSearchConfigurationUtil;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.ForeignKey;
import org.jetbrains.annotations.NotNull;
import javax.persistence.*;
import java.io.Serializable;
import java.util.*;
import java.util.Objects;
import static com.evolveum.midpoint.repo.sql.data.common.RObjectTextInfo.TABLE_NAME;
/**
* @author mederly
*/
@Entity
@IdClass(RObjectTextInfoId.class)
@Table(name = TABLE_NAME)
public class RObjectTextInfo implements Serializable {
private static final Trace LOGGER = TraceManager.getTrace(RObjectTextInfo.class);
public static final String TABLE_NAME = "m_object_text_info";
public static final String COLUMN_OWNER_OID = "owner_oid";
public static final String F_TEXT = "text";
public static final int MAX_TEXT_SIZE = 255;
private RObject owner;
private String ownerOid;
private String text;
public RObjectTextInfo() {
}
public RObjectTextInfo(RObject owner, String text) {
this.owner = owner;
this.text = text;
}
@ForeignKey(name = "fk_object_text_info_owner")
@MapsId("owner")
@ManyToOne(fetch = FetchType.LAZY)
@NotQueryable
public RObject getOwner() {
return owner;
}
@Id
@Column(name = COLUMN_OWNER_OID, length = RUtil.COLUMN_LENGTH_OID)
@NotQueryable
public String getOwnerOid() {
if (ownerOid == null && owner != null) {
ownerOid = owner.getOid();
}
return ownerOid;
}
public void setOwner(RObject owner) {
this.owner = owner;
}
public void setOwnerOid(String ownerOid) {
this.ownerOid = ownerOid;
}
@Id
@Column(name = "text", length = MAX_TEXT_SIZE)
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof RObjectTextInfo))
return false;
RObjectTextInfo that = (RObjectTextInfo) o;
return Objects.equals(getOwnerOid(), that.getOwnerOid()) &&
Objects.equals(getText(), that.getText());
}
@Override
public int hashCode() {
return Objects.hash(getOwnerOid(), getText());
}
public static <T extends ObjectType> Set<RObjectTextInfo> createItemsSet(@NotNull ObjectType object, @NotNull RObject repo,
@NotNull RepositoryContext repositoryContext) {
FullTextSearchConfigurationType config = repositoryContext.repositoryService.getFullTextSearchConfiguration();
if (!FullTextSearchConfigurationUtil.isEnabled(config)) {
return Collections.emptySet();
}
Set<ItemPath> paths = FullTextSearchConfigurationUtil.getFullTextSearchItemPaths(config, object.getClass());
List<PrismValue> values = new ArrayList<>();
for (ItemPath path : paths) {
Object o = object.asPrismObject().find(path);
if (o == null) {
// shouldn't occur
} else if (o instanceof PrismValue) {
values.add((PrismValue) o);
} else if (o instanceof Item) {
values.addAll(((Item<?, ?>) o).getValues());
} else {
throw new IllegalStateException("Unexpected value " + o + " in " + object + " at " + path);
}
}
List<String> allWords = new ArrayList<>(); // not a (hash) set in order to preserve order
for (PrismValue value : values) {
if (value == null) {
continue;
}
if (value instanceof PrismPropertyValue) {
Object realValue = value.getRealValue();
if (realValue == null) {
// skip
} else if (realValue instanceof String) {
append(allWords, (String) realValue, repositoryContext.prismContext);
} else if (realValue instanceof PolyString) {
append(allWords, (PolyString) realValue, repositoryContext.prismContext);
} else {
append(allWords, realValue.toString(), repositoryContext.prismContext);
}
}
}
LOGGER.trace("Indexing {}:\n items: {}\n values: {}\n words: {}", object, paths, values, allWords);
return createItemsSet(repo, allWords);
}
private static Set<RObjectTextInfo> createItemsSet(RObject repo, List<String> allWords) {
Set<RObjectTextInfo> rv = new HashSet<>();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < allWords.size(); i++) {
String word = allWords.get(i);
if (sb.length() + word.length() + 2 <= MAX_TEXT_SIZE) {
sb.append(" ").append(word);
} else {
if (sb.length() > 0) {
sb.append(" ");
rv.add(new RObjectTextInfo(repo, sb.toString()));
sb = new StringBuilder();
} else {
// a problem - too large string
LOGGER.warn("Word too long to be correctly indexed: {}", word);
rv.add(new RObjectTextInfo(repo, " " + word.substring(0, MAX_TEXT_SIZE - 2) + " "));
allWords.set(i, word.substring(MAX_TEXT_SIZE - 2));
i--; // to reiterate
}
}
}
if (sb.length() > 0) {
sb.append(" ");
rv.add(new RObjectTextInfo(repo, sb.toString()));
}
return rv;
}
private static void append(List<String> allWords, String text, PrismContext prismContext) {
if (StringUtils.isBlank(text)) {
return;
}
String normalized = prismContext.getDefaultPolyStringNormalizer().normalize(text);
String[] words = StringUtils.split(normalized);
for (String word : words) {
if (StringUtils.isNotBlank(word)) {
if (!allWords.contains(word)) {
allWords.add(word);
}
}
}
}
private static void append(List<String> allWords, PolyString text, PrismContext prismContext) {
if (text != null) {
append(allWords, text.getOrig(), prismContext);
}
}
@Override
public String toString() {
return "RObjectTextInfo{" +
"ownerOid='" + getOwnerOid()+ '\'' +
", text='" + text + '\'' +
'}';
}
}