/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.kernel.spring.osgi;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.PropertiesUtil;
import com.liferay.portal.kernel.util.PropsUtil;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.Validator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Provides the OSGi service properties used when publishing Spring beans as
* services.
*
* @author Raymond Augé
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface OSGiBeanProperties {
/**
* Returns <code>true</code> if the property prefix should be removed from
* <code>portal.properties</code>.
*
* @return <code>true</code> if the property prefix should be removed from
* <code>portal.properties</code>; <code>false</code> otherwise
*/
public boolean portalPropertiesRemovePrefix() default true;
/**
* Returns the value of the property prefix used for retrieving properties
* from <code>portal.properties</code>.
*
* @return the value of the property prefix
*/
public String portalPropertyPrefix() default "";
/**
* Returns the service properties.
*
* <p>
* Each property string is specified as <code>"key=value"</code>. The type
* of the property value can be specified in the key as
* <code>"key:type=value"</code>. The type must be from {@link Type}. To
* specify a property with multiple values, use multiple key-value pairs.
* For example, <code>"foo=bar", "foo=baz"</code>.
* </p>
*
* @return the service properties
*/
public String[] property() default {};
/**
* Returns the types under which the bean is published as a service.
*
* @return the service types
*/
public Class<?>[] service() default {};
/**
* Converts OSGi bean properties from the {@link OSGiBeanProperties}
* annotation into a properties map. This is a helper class.
*/
public static class Convert {
/**
* Returns a properties map representing the object's OSGi bean properties.
*
* @param object the object that is possibly annotated with {@link
* OSGiBeanProperties}
* @return a properties map representing the object's OSGi bean
* properties. The map will be <code>null</code> if the object
* is not annotated with {@link OSGiBeanProperties} or will be
* empty if the object is annotated with {@link
* OSGiBeanProperties} but has no properties.
*/
public static Map<String, Object> fromObject(Object object) {
Class<? extends Object> clazz = object.getClass();
OSGiBeanProperties osgiBeanProperties = clazz.getAnnotation(
OSGiBeanProperties.class);
if (osgiBeanProperties == null) {
return null;
}
return toMap(osgiBeanProperties);
}
/**
* Returns a properties map representing the {@link OSGiBeanProperties}
* instance.
*
* @param osgiBeanProperties the instance of {@link OSGiBeanProperties}
* read for properties
* @return a properties map representing the {@link OSGiBeanProperties}
* instance. The map will be empty if the {@link
* OSGiBeanProperties} instance has no properties.
*/
public static Map<String, Object> toMap(
OSGiBeanProperties osgiBeanProperties) {
Map<String, Object> properties = new HashMap<>();
for (String property : osgiBeanProperties.property()) {
String[] parts = property.split(StringPool.EQUAL, 2);
if (parts.length <= 1) {
continue;
}
String key = parts[0];
String className = String.class.getSimpleName();
if (key.indexOf(StringPool.COLON) != -1) {
String[] keyParts = StringUtil.split(key, StringPool.COLON);
key = keyParts[0];
className = keyParts[1];
}
String value = parts[1];
_put(key, value, className, properties);
}
String portalPropertyPrefix =
osgiBeanProperties.portalPropertyPrefix();
if (Validator.isNotNull(portalPropertyPrefix)) {
Properties portalProperties = PropsUtil.getProperties(
portalPropertyPrefix,
osgiBeanProperties.portalPropertiesRemovePrefix());
properties.putAll(PropertiesUtil.toMap(portalProperties));
}
return properties;
}
private static void _put(
String key, String value, String className,
Map<String, Object> properties) {
Type type = Type._getType(className);
Object previousValue = properties.get(key);
properties.put(key, type._convert(value, previousValue));
}
}
/**
* Obtains types under which the bean is published as a service.
*/
public static class Service {
/**
* Returns the types under which the bean is published as a service.
* If no types are specified, they are calculated through class
* introspection. If the bean is not assignable to a specified service
* type, a {@link ClassCastException} is thrown.
*
* @param object the object (bean)
* @return the service types
*/
public static Set<Class<?>> interfaces(Object object) {
Class<? extends Object> clazz = object.getClass();
OSGiBeanProperties osgiBeanProperties = clazz.getAnnotation(
OSGiBeanProperties.class);
if (osgiBeanProperties == null) {
return _getInterfaceClasses(clazz, new HashSet<Class<?>>());
}
Class<?>[] serviceClasses = osgiBeanProperties.service();
if (serviceClasses.length == 0) {
return _getInterfaceClasses(clazz, new HashSet<Class<?>>());
}
for (Class<?> serviceClazz : serviceClasses) {
serviceClazz.cast(object);
}
return new HashSet<>(Arrays.asList(osgiBeanProperties.service()));
}
private static Set<Class<?>> _getInterfaceClasses(
Class<?> clazz, Set<Class<?>> interfaceClasses) {
if (clazz.isInterface()) {
interfaceClasses.add(clazz);
}
for (Class<?> interfaceClass : clazz.getInterfaces()) {
_getInterfaceClasses(interfaceClass, interfaceClasses);
}
if ((clazz = clazz.getSuperclass()) != null) {
_getInterfaceClasses(clazz, interfaceClasses);
}
return interfaceClasses;
}
}
public enum Type {
BOOLEAN, BYTE, CHARACTER, DOUBLE, FLOAT, INTEGER, LONG, SHORT, STRING;
private static Type _getType(String name) {
name = StringUtil.toUpperCase(name);
for (Type type : values()) {
if (name.equals(type.name())) {
return type;
}
}
return Type.STRING;
}
private Object _convert(String value, Object previousValue) {
if (previousValue == null) {
return _getTypedValue(value);
}
Class<?> clazz = previousValue.getClass();
if (!clazz.isArray()) {
Object array = Array.newInstance(_getTypeClass(), 2);
Array.set(array, 0, previousValue);
Array.set(array, 1, _getTypedValue(value));
return array;
}
Object array = Array.newInstance(
_getTypeClass(), Array.getLength(previousValue) + 1);
for (int i = 0; i < Array.getLength(previousValue); i++) {
Array.set(array, i, Array.get(previousValue, i));
}
Array.set(
array, Array.getLength(previousValue), _getTypedValue(value));
return array;
}
private Class<?> _getTypeClass() {
if (this == Type.BOOLEAN) {
return java.lang.Boolean.class;
}
else if (this == Type.BYTE) {
return java.lang.Byte.class;
}
else if (this == Type.CHARACTER) {
return java.lang.Character.class;
}
else if (this == Type.DOUBLE) {
return java.lang.Double.class;
}
else if (this == Type.FLOAT) {
return java.lang.Float.class;
}
else if (this == Type.INTEGER) {
return java.lang.Integer.class;
}
else if (this == Type.LONG) {
return java.lang.Long.class;
}
else if (this == Type.SHORT) {
return java.lang.Short.class;
}
else if (this == Type.STRING) {
return java.lang.String.class;
}
return null;
}
private Object _getTypedValue(String value) {
if (this == Type.BOOLEAN) {
return GetterUtil.getBoolean(value);
}
else if (this == Type.BYTE) {
return new java.lang.Byte(value).byteValue();
}
else if (this == Type.CHARACTER) {
return value.charAt(0);
}
else if (this == Type.DOUBLE) {
return GetterUtil.getDouble(value);
}
else if (this == Type.FLOAT) {
return GetterUtil.getFloat(value);
}
else if (this == Type.INTEGER) {
return GetterUtil.getInteger(value);
}
else if (this == Type.LONG) {
return GetterUtil.getLong(value);
}
else if (this == Type.SHORT) {
return GetterUtil.getShort(value);
}
return value;
}
}
}