/*
* 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.util;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.query.LogicalFilter;
import com.evolveum.midpoint.prism.query.ObjectFilter;
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.query.OrgFilter;
import com.evolveum.midpoint.repo.sql.data.audit.RObjectDeltaOperation;
import com.evolveum.midpoint.repo.sql.data.common.*;
import com.evolveum.midpoint.repo.sql.data.common.any.*;
import com.evolveum.midpoint.repo.sql.data.common.container.RAssignment;
import com.evolveum.midpoint.repo.sql.data.common.container.RAssignmentReference;
import com.evolveum.midpoint.repo.sql.data.common.container.RExclusion;
import com.evolveum.midpoint.repo.sql.data.common.container.RTrigger;
import com.evolveum.midpoint.repo.sql.data.common.embedded.REmbeddedNamedReference;
import com.evolveum.midpoint.repo.sql.data.common.embedded.REmbeddedReference;
import com.evolveum.midpoint.repo.sql.data.common.embedded.RPolyString;
import com.evolveum.midpoint.repo.sql.data.common.enums.ROperationResultStatus;
import com.evolveum.midpoint.repo.sql.data.common.enums.SchemaEnum;
import com.evolveum.midpoint.repo.sql.data.common.other.RObjectType;
import com.evolveum.midpoint.repo.sql.data.common.other.RReferenceOwner;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.OperationResultType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.hibernate.SessionFactory;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.persister.entity.AbstractEntityPersister;
import org.hibernate.tuple.IdentifierProperty;
import org.hibernate.tuple.entity.EntityMetamodel;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Element;
import javax.persistence.Table;
import javax.xml.namespace.QName;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* @author lazyman
*/
public final class RUtil {
/**
* Currently set in ctx-session.xml as constant, used for batch inserts (e.g. in OrgClosureManager)
*/
public static final int JDBC_BATCH_SIZE = 20;
/**
* This constant is used for mapping type for {@link javax.persistence.Lob}
* fields. {@link org.hibernate.type.MaterializedClobType} was not working
* properly with PostgreSQL, causing TEXT types (clobs) to be saved not in
* table row but somewhere else and it always messed up UTF-8 encoding
*/
public static final String LOB_STRING_TYPE = "org.hibernate.type.StringClobType";
public static final int COLUMN_LENGTH_QNAME = 157;
public static final String QNAME_DELIMITER = "#";
/**
* This constant is used for oid column size in database.
*/
public static final int COLUMN_LENGTH_OID = 36;
/**
* This namespace is used for wrapping xml parts of objects during save to
* database.
*/
public static final String NS_SQL_REPO = "http://midpoint.evolveum.com/xml/ns/fake/sqlRepository-1.xsd";
public static final String SQL_REPO_OBJECT = "sqlRepoObject";
public static final QName CUSTOM_OBJECT = new QName(NS_SQL_REPO, SQL_REPO_OBJECT);
private static final Trace LOGGER = TraceManager.getTrace(RUtil.class);
private RUtil() {
}
public static <T extends Objectable> void revive(Objectable object, PrismContext prismContext)
throws DtoTranslationException {
try {
prismContext.adopt(object);
} catch (SchemaException ex) {
throw new DtoTranslationException(ex.getMessage(), ex);
}
}
public static Element createFakeParentElement() {
return DOMUtil.createElement(DOMUtil.getDocument(), CUSTOM_OBJECT);
}
public static <T> Set<T> listToSet(List<T> list) {
if (list == null || list.isEmpty()) {
return null;
}
return new HashSet<>(list);
}
public static Set<RPolyString> listPolyToSet(List<PolyStringType> list) {
if (list == null || list.isEmpty()) {
return null;
}
Set<RPolyString> set = new HashSet<>();
for (PolyStringType str : list) {
set.add(RPolyString.copyFromJAXB(str));
}
return set;
}
public static List<ObjectReferenceType> safeSetReferencesToList(Set<? extends RObjectReference> set, PrismContext prismContext) {
if (set == null || set.isEmpty()) {
return new ArrayList<>();
}
List<ObjectReferenceType> list = new ArrayList<>();
for (RObjectReference str : set) {
ObjectReferenceType ort = new ObjectReferenceType();
RObjectReference.copyToJAXB(str, ort);
list.add(ort);
}
return list;
}
public static Set safeListReferenceToSet(List<ObjectReferenceType> list, PrismContext prismContext,
RObject owner, RReferenceOwner refOwner) {
Set<RObjectReference> set = new HashSet<>();
if (list == null || list.isEmpty()) {
return set;
}
for (ObjectReferenceType ref : list) {
RObjectReference rRef = RUtil.jaxbRefToRepo(ref, prismContext, owner, refOwner);
if (rRef != null) {
set.add(rRef);
}
}
return set;
}
public static RObjectReference jaxbRefToRepo(ObjectReferenceType reference, PrismContext prismContext,
RObject owner, RReferenceOwner refOwner) {
if (reference == null) {
return null;
}
Validate.notNull(owner, "Owner of reference must not be null.");
Validate.notNull(refOwner, "Reference owner of reference must not be null.");
Validate.notEmpty(reference.getOid(), "Target oid reference must not be null.");
RObjectReference repoRef = new RObjectReference();
repoRef.setReferenceType(refOwner);
repoRef.setOwner(owner);
RObjectReference.copyFromJAXB(reference, repoRef);
return repoRef;
}
public static REmbeddedReference jaxbRefToEmbeddedRepoRef(ObjectReferenceType jaxb,
PrismContext prismContext) {
if (jaxb == null) {
return null;
}
REmbeddedReference ref = new REmbeddedReference();
REmbeddedReference.copyFromJAXB(jaxb, ref);
return ref;
}
public static REmbeddedNamedReference jaxbRefToEmbeddedNamedRepoRef(ObjectReferenceType jaxb) {
if (jaxb == null) {
return null;
}
REmbeddedNamedReference ref = new REmbeddedNamedReference();
REmbeddedNamedReference.copyFromJAXB(jaxb, ref);
return ref;
}
public static Integer getIntegerFromString(String val) {
if (val == null || !val.matches("[0-9]+")) {
return null;
}
return Integer.parseInt(val);
}
/**
* This method is used to override "hasIdentifierMapper" in EntityMetaModels
* of entities which have composite id and class defined for it. It's
* workaround for bug as found in forum
* https://forum.hibernate.org/viewtopic.php?t=978915&highlight=
*
* @param sessionFactory
*/
public static void fixCompositeIDHandling(SessionFactory sessionFactory) {
fixCompositeIdentifierInMetaModel(sessionFactory, RObjectDeltaOperation.class);
fixCompositeIdentifierInMetaModel(sessionFactory, ROrgClosure.class);
fixCompositeIdentifierInMetaModel(sessionFactory, ROExtDate.class);
fixCompositeIdentifierInMetaModel(sessionFactory, ROExtString.class);
fixCompositeIdentifierInMetaModel(sessionFactory, ROExtPolyString.class);
fixCompositeIdentifierInMetaModel(sessionFactory, ROExtReference.class);
fixCompositeIdentifierInMetaModel(sessionFactory, ROExtLong.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RAssignmentExtension.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RAExtDate.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RAExtString.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RAExtPolyString.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RAExtReference.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RAExtLong.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RObjectReference.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RAssignmentReference.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RAssignment.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RExclusion.class);
fixCompositeIdentifierInMetaModel(sessionFactory, RTrigger.class);
for (RObjectType type : ClassMapper.getKnownTypes()) {
fixCompositeIdentifierInMetaModel(sessionFactory, type.getClazz());
}
}
private static void fixCompositeIdentifierInMetaModel(SessionFactory sessionFactory, Class clazz) {
ClassMetadata classMetadata = sessionFactory.getClassMetadata(clazz);
if (classMetadata instanceof AbstractEntityPersister) {
AbstractEntityPersister persister = (AbstractEntityPersister) classMetadata;
EntityMetamodel model = persister.getEntityMetamodel();
IdentifierProperty identifier = model.getIdentifierProperty();
try {
Field field = IdentifierProperty.class.getDeclaredField("hasIdentifierMapper");
field.setAccessible(true);
field.set(identifier, true);
field.setAccessible(false);
} catch (Exception ex) {
throw new SystemException("Attempt to fix entity meta model with hack failed, reason: "
+ ex.getMessage(), ex);
}
}
}
public static void copyResultFromJAXB(ItemDefinition parentDef, QName itemName, OperationResultType jaxb,
OperationResult repo, PrismContext prismContext) throws DtoTranslationException {
Validate.notNull(repo, "Repo object must not be null.");
if (jaxb == null) {
return;
}
repo.setStatus(getRepoEnumValue(jaxb.getStatus(), ROperationResultStatus.class));
if (repo instanceof OperationResultFull) {
try {
String full = prismContext.xmlSerializer().serializeRealValue(jaxb, itemName);
((OperationResultFull) repo).setFullResult(full);
} catch (Exception ex) {
throw new DtoTranslationException(ex.getMessage(), ex);
}
}
}
public static String computeChecksum(Object... objects) {
StringBuilder builder = new StringBuilder();
for (Object object : objects) {
if (object == null) {
continue;
}
builder.append(object.toString());
}
return DigestUtils.md5Hex(builder.toString());
}
public static <T extends SchemaEnum> T getRepoEnumValue(Object object, Class<T> type) {
if (object == null) {
return null;
}
Object[] values = type.getEnumConstants();
for (Object value : values) {
T schemaEnum = (T) value;
if (schemaEnum.getSchemaValue().equals(object)) {
return schemaEnum;
}
}
throw new IllegalArgumentException("Unknown value '" + object + "' of type '" + object.getClass()
+ "', can't translate to '" + type + "'.");
}
@NotNull
public static String qnameToString(QName qname) {
StringBuilder sb = new StringBuilder();
if (qname != null) {
sb.append(qname.getNamespaceURI());
}
sb.append(QNAME_DELIMITER);
if (qname != null) {
sb.append(qname.getLocalPart());
}
return sb.toString();
}
public static QName stringToQName(String text) {
if (StringUtils.isEmpty(text)) {
return null;
}
int index = text.lastIndexOf(QNAME_DELIMITER);
String namespace = StringUtils.left(text, index);
String localPart = StringUtils.right(text, text.length() - index - 1);
if (StringUtils.isEmpty(localPart)) {
return null;
}
return new QName(namespace, localPart);
}
public static Long toLong(Short s) {
if (s == null) {
return null;
}
return s.longValue();
}
public static Long toLong(Integer i) {
if (i == null) {
return null;
}
return i.longValue();
}
public static Short toShort(Long l) {
if (l == null) {
return null;
}
if (l > Short.MAX_VALUE || l < Short.MIN_VALUE) {
throw new IllegalArgumentException("Couldn't cast value to short " + l);
}
return l.shortValue();
}
public static Integer toInteger(Long l) {
if (l == null) {
return null;
}
if (l > Integer.MAX_VALUE || l < Integer.MIN_VALUE) {
throw new IllegalArgumentException("Couldn't cast value to Integer " + l);
}
return l.intValue();
}
public static String getDebugString(RObject object) {
StringBuilder sb = new StringBuilder();
if (object.getName() != null) {
sb.append(object.getName().getOrig());
} else {
sb.append("null");
}
sb.append('(').append(object.getOid()).append(')');
return sb.toString();
}
public static String getTableName(Class hqlType) {
Table tableAnnotation = (Table) hqlType.getAnnotation(Table.class); // TODO what about performance here? (synchronized call)
if (tableAnnotation != null && StringUtils.isNotEmpty(tableAnnotation.name())) {
return tableAnnotation.name();
}
MidPointNamingStrategy namingStrategy = new MidPointNamingStrategy();
return namingStrategy.classToTableName(hqlType.getSimpleName());
}
public static byte[] getByteArrayFromXml(String xml, boolean compress) {
byte[] array;
GZIPOutputStream gzip = null;
try {
if (compress) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
gzip = new GZIPOutputStream(out);
gzip.write(xml.getBytes("utf-8"));
gzip.close();
out.close();
array = out.toByteArray();
} else {
array = xml.getBytes("utf-8");
}
} catch (Exception ex) {
throw new SystemException("Couldn't save full xml object, reason: " + ex.getMessage(), ex);
} finally {
IOUtils.closeQuietly(gzip);
}
return array;
}
public static String getXmlFromByteArray(byte[] array, boolean compressed) {
String xml;
GZIPInputStream gzip = null;
try {
if (compressed) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
gzip = new GZIPInputStream(new ByteArrayInputStream(array));
IOUtils.copy(gzip, out);
xml = new String(out.toByteArray(), "utf-8");
} else {
xml = new String(array, "utf-8");
}
} catch (Exception ex) {
throw new SystemException("Couldn't read data from full object column, reason: " + ex.getMessage(), ex);
} finally {
IOUtils.closeQuietly(gzip);
}
return xml;
}
public static OrgFilter findOrgFilter(ObjectQuery query) {
return query != null ? findOrgFilter(query.getFilter()) : null;
}
public static OrgFilter findOrgFilter(ObjectFilter filter) {
if (filter == null) {
return null;
}
if (filter instanceof OrgFilter) {
return (OrgFilter) filter;
}
if (filter instanceof LogicalFilter) {
LogicalFilter logical = (LogicalFilter) filter;
for (ObjectFilter f : logical.getConditions()) {
OrgFilter o = findOrgFilter(f);
if (o != null) {
return o;
}
}
}
return null;
}
public static String trimString(String message, int size) {
if (message == null || message.length() <= size) {
return message;
}
return message.substring(0, size - 4) + "...";
}
}