/**
* 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.metamodel.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Comparator;
import org.apache.metamodel.query.AggregateFunction;
import org.apache.metamodel.query.FunctionType;
import org.apache.metamodel.query.OperatorType;
import org.apache.metamodel.schema.ColumnType;
import org.apache.metamodel.schema.SuperColumnType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A specialized {@link ObjectInputStream} for MetaModel which can be used or
* extended if it is needed to deserialize legacy MetaModel objects. This is
* needed since the namespace of MetaModel was changed from org.apache.metamodel
* to org.apache.metamodel.
*/
public class LegacyDeserializationObjectInputStream extends ObjectInputStream {
private static final Logger logger = LoggerFactory.getLogger(LegacyDeserializationObjectInputStream.class);
/**
* Implementation of the new {@link FunctionType} and
* {@link AggregateFunction} interfaces which still adheres to the
* constant/enum values of the old FunctionType definition. While
* deserializing old FunctionType objects, we will convert them to this
* enum.
*/
protected static enum LegacyFunctionType implements AggregateFunction {
COUNT(FunctionType.COUNT), AVG(FunctionType.AVG), SUM(FunctionType.SUM), MAX(FunctionType.MAX), MIN(
FunctionType.MIN);
private final AggregateFunction _delegate;
private LegacyFunctionType(AggregateFunction delegate) {
_delegate = delegate;
}
@Override
public ColumnType getExpectedColumnType(ColumnType type) {
return _delegate.getExpectedColumnType(type);
}
@Override
public String getFunctionName() {
return _delegate.getFunctionName();
}
@Override
public AggregateBuilder<?> createAggregateBuilder() {
return _delegate.createAggregateBuilder();
}
@Override
public Object evaluate(Object... values) {
return _delegate.evaluate(values);
}
}
/**
* Implementation of the new {@link ColumnType} interface which still
* adheres to the constant/enum values of the old ColumnType definition.
* While deserializing old ColumnType objects, we will convert them to this
* enum.
*/
protected static enum LegacyColumnType implements ColumnType {
CHAR(ColumnType.CHAR), VARCHAR(ColumnType.VARCHAR), LONGVARCHAR(ColumnType.LONGVARCHAR), CLOB(ColumnType.CLOB), NCHAR(
ColumnType.NCHAR), NVARCHAR(ColumnType.NVARCHAR), LONGNVARCHAR(ColumnType.LONGNVARCHAR), NCLOB(
ColumnType.NCLOB), TINYINT(ColumnType.TINYINT), SMALLINT(ColumnType.SMALLINT), INTEGER(
ColumnType.INTEGER), BIGINT(ColumnType.BIGINT), FLOAT(ColumnType.FLOAT), REAL(ColumnType.REAL), DOUBLE(
ColumnType.DOUBLE), NUMERIC(ColumnType.NUMERIC), DECIMAL(ColumnType.DECIMAL), DATE(ColumnType.DATE), TIME(
ColumnType.TIME), TIMESTAMP(ColumnType.TIMESTAMP), BIT(ColumnType.BIT), BOOLEAN(ColumnType.BOOLEAN), BINARY(
ColumnType.BINARY), VARBINARY(ColumnType.VARBINARY), LONGVARBINARY(ColumnType.LONGVARBINARY), BLOB(
ColumnType.BLOB), NULL(ColumnType.NULL), OTHER(ColumnType.OTHER), JAVA_OBJECT(ColumnType.JAVA_OBJECT), DISTINCT(
ColumnType.DISTINCT), STRUCT(ColumnType.STRUCT), ARRAY(ColumnType.ARRAY), REF(ColumnType.REF), DATALINK(
ColumnType.DATALINK), ROWID(ColumnType.ROWID), SQLXML(ColumnType.SQLXML), LIST(ColumnType.LIST), MAP(
ColumnType.MAP);
private final ColumnType _delegate;
private LegacyColumnType(ColumnType delegate) {
_delegate = delegate;
}
@Override
public String getName() {
return _delegate.getName();
}
@Override
public Comparator<Object> getComparator() {
return _delegate.getComparator();
}
@Override
public boolean isBoolean() {
return _delegate.isBoolean();
}
@Override
public boolean isBinary() {
return _delegate.isBinary();
}
@Override
public boolean isNumber() {
return _delegate.isNumber();
}
@Override
public boolean isTimeBased() {
return _delegate.isTimeBased();
}
@Override
public boolean isLiteral() {
return _delegate.isLiteral();
}
@Override
public boolean isLargeObject() {
return _delegate.isLargeObject();
}
@Override
public Class<?> getJavaEquivalentClass() {
return _delegate.getJavaEquivalentClass();
}
@Override
public SuperColumnType getSuperType() {
return _delegate.getSuperType();
}
@Override
public int getJdbcType() throws IllegalStateException {
return _delegate.getJdbcType();
}
}
/**
* Implementation of the new {@link OperatorType} interface which still
* adheres to the constant/enum values of the old OperatorType definition.
* While deserializing old OperatorType objects, we will convert them to
* this enum.
*/
protected static enum LegacyOperatorType implements OperatorType {
EQUALS_TO(OperatorType.EQUALS_TO), DIFFERENT_FROM(OperatorType.DIFFERENT_FROM), LIKE(OperatorType.LIKE), GREATER_THAN(
OperatorType.GREATER_THAN), GREATER_THAN_OR_EQUAL(OperatorType.GREATER_THAN_OR_EQUAL), LESS_THAN(
OperatorType.LESS_THAN), LESS_THAN_OR_EQUAL(OperatorType.LESS_THAN_OR_EQUAL), IN(OperatorType.IN);
private final OperatorType _delegate;
private LegacyOperatorType(OperatorType delegate) {
_delegate = delegate;
}
@Override
public boolean isSpaceDelimited() {
return _delegate.isSpaceDelimited();
}
@Override
public String toSql() {
return _delegate.toSql();
}
}
private static final String OLD_CLASS_NAME_COLUMN_TYPE = "org.eobjects.metamodel.schema.ColumnType";
private static final String CLASS_NAME_OPERATOR_TYPE = "org.apache.metamodel.query.OperatorType";
private static final String CLASS_NAME_FUNCTION_TYPE = "org.apache.metamodel.query.FunctionType";
public LegacyDeserializationObjectInputStream(InputStream in) throws IOException, SecurityException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
final String className = desc.getName();
if (className.startsWith("org.eobjects.metamodel") || className.startsWith("[Lorg.eobjects.metamodel")) {
final String newClassName;
if (OLD_CLASS_NAME_COLUMN_TYPE.equals(className)) {
// since ColumnType was changed from enum to interface, there's
// some special treatment here.
newClassName = LegacyColumnType.class.getName();
} else {
newClassName = className.replace("org.eobjects", "org.apache");
}
return Class.forName(newClassName);
}
return super.resolveClass(desc);
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
final ObjectStreamClass objectStreamClass = super.readClassDescriptor();
final String className = objectStreamClass.getName();
switch (className) {
case OLD_CLASS_NAME_COLUMN_TYPE:
final ObjectStreamClass legacyColumnTypeResult = ObjectStreamClass.lookup(LegacyColumnType.class);
return legacyColumnTypeResult;
case CLASS_NAME_OPERATOR_TYPE:
if (isEnumExpected(objectStreamClass)) {
final ObjectStreamClass legacyOperatorTypeResult = ObjectStreamClass.lookup(LegacyOperatorType.class);
return legacyOperatorTypeResult;
}
break;
case CLASS_NAME_FUNCTION_TYPE:
if (isEnumExpected(objectStreamClass)) {
final ObjectStreamClass legacyOperatorTypeResult = ObjectStreamClass.lookup(LegacyFunctionType.class);
return legacyOperatorTypeResult;
}
break;
}
return objectStreamClass;
}
/**
* Method that uses the (non-public) isEnum() method of
* {@link ObjectStreamClass} to determine if an enum is expected.
*
* @param objectStreamClass
* @return
*/
private boolean isEnumExpected(ObjectStreamClass objectStreamClass) {
try {
final Field initializedField = ObjectStreamClass.class.getDeclaredField("initialized");
initializedField.setAccessible(true);
final Boolean initialized = (Boolean) initializedField.get(objectStreamClass);
if (!initialized) {
/*
* Snippet from the JDK source:
*
* void initNonProxy(ObjectStreamClass model,
* Class<?> cl,
* ClassNotFoundException resolveEx,
* ObjectStreamClass superDesc)
**/
final Method initMethod = ObjectStreamClass.class.getDeclaredMethod("initNonProxy", ObjectStreamClass.class,
Class.class, ClassNotFoundException.class, ObjectStreamClass.class);
initMethod.setAccessible(true);
initMethod.invoke(objectStreamClass, objectStreamClass, null, null, null);
}
} catch (NoSuchFieldError e) {
logger.debug("Failed to access boolean field 'initialized' in {}", objectStreamClass.getName(), e);
} catch (Exception e) {
logger.debug("Failed to access invoke ObjectStreamClass.initialize() to prepare {}", objectStreamClass
.getName(), e);
}
try {
final Method isEnumMethod = ObjectStreamClass.class.getDeclaredMethod("isEnum");
isEnumMethod.setAccessible(true);
final Boolean result = (Boolean) isEnumMethod.invoke(objectStreamClass);
return result.booleanValue();
} catch (Exception e) {
logger.warn("Failed to access and invoke ObjectStreamClass.isEnum to determine if {} is an enum",
objectStreamClass.getName(), e);
}
return false;
}
}