001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.spring.osgi;
016    
017    import com.liferay.portal.kernel.util.GetterUtil;
018    import com.liferay.portal.kernel.util.PropertiesUtil;
019    import com.liferay.portal.kernel.util.PropsUtil;
020    import com.liferay.portal.kernel.util.StringPool;
021    import com.liferay.portal.kernel.util.StringUtil;
022    import com.liferay.portal.kernel.util.Validator;
023    
024    import java.lang.annotation.ElementType;
025    import java.lang.annotation.Retention;
026    import java.lang.annotation.RetentionPolicy;
027    import java.lang.annotation.Target;
028    import java.lang.reflect.Array;
029    
030    import java.util.Arrays;
031    import java.util.HashMap;
032    import java.util.HashSet;
033    import java.util.Map;
034    import java.util.Properties;
035    import java.util.Set;
036    
037    /**
038     * Provides the OSGi service properties used when publishing Spring beans as
039     * services.
040     *
041     * @author Raymond Aug??
042     */
043    @Retention(RetentionPolicy.RUNTIME)
044    @Target(ElementType.TYPE)
045    public @interface OSGiBeanProperties {
046    
047            /**
048             * Returns <code>true</code> if the property prefix should be removed from
049             * <code>portal.properties</code>.
050             *
051             * @return <code>true</code> if the property prefix should be removed from
052             *         <code>portal.properties</code>; <code>false</code> otherwise
053             */
054            public boolean portalPropertiesRemovePrefix() default true;
055    
056            /**
057             * Returns the value of the property prefix used for retrieving properties
058             * from <code>portal.properties</code>.
059             *
060             * @return the value of the property prefix
061             */
062            public String portalPropertyPrefix() default "";
063    
064            /**
065             * Returns the service properties.
066             *
067             * <p>
068             * Each property string is specified as <code>"key=value"</code>. The type
069             * of the property value can be specified in the key as
070             * <code>"key:type=value"</code>. The type must be from {@link Type}. To
071             * specify a property with multiple values, use multiple key-value pairs.
072             * For example, <code>"foo=bar", "foo=baz"</code>.
073             * </p>
074             *
075             * @return the service properties
076             */
077            public String[] property() default {};
078    
079            /**
080             * Returns the types under which the bean is published as a service.
081             *
082             * @return the service types
083             */
084            public Class<?>[] service() default {};
085    
086            /**
087             * Converts OSGi bean properties from the {@link OSGiBeanProperties}
088             * annotation into a properties map. This is a helper class.
089             */
090            public static class Convert {
091    
092                    /**
093                     * Returns a properties map representing the object's OSGi bean properties.
094                     *
095                     * @param  object the object that is possibly annotated with {@link
096                     *         OSGiBeanProperties}
097                     * @return a properties map representing the object's OSGi bean
098                     *         properties. The map will be <code>null</code> if the object
099                     *         is not annotated with {@link OSGiBeanProperties} or will be
100                     *         empty if the object is annotated with {@link
101                     *         OSGiBeanProperties} but has no properties.
102                     */
103                    public static Map<String, Object> fromObject(Object object) {
104                            Class<? extends Object> clazz = object.getClass();
105    
106                            OSGiBeanProperties osgiBeanProperties = clazz.getAnnotation(
107                                    OSGiBeanProperties.class);
108    
109                            if (osgiBeanProperties == null) {
110                                    return null;
111                            }
112    
113                            return toMap(osgiBeanProperties);
114                    }
115    
116                    /**
117                     * Returns a properties map representing the {@link OSGiBeanProperties}
118                     * instance.
119                     *
120                     * @param  osgiBeanProperties the instance of {@link OSGiBeanProperties}
121                     *         read for properties
122                     * @return a properties map representing the {@link OSGiBeanProperties}
123                     *         instance. The map will be empty if the {@link
124                     *         OSGiBeanProperties} instance has no properties.
125                     */
126                    public static Map<String, Object> toMap(
127                            OSGiBeanProperties osgiBeanProperties) {
128    
129                            Map<String, Object> properties = new HashMap<>();
130    
131                            for (String property : osgiBeanProperties.property()) {
132                                    String[] parts = property.split(StringPool.EQUAL, 2);
133    
134                                    if (parts.length <= 1) {
135                                            continue;
136                                    }
137    
138                                    String key = parts[0];
139                                    String className = String.class.getSimpleName();
140    
141                                    if (key.indexOf(StringPool.COLON) != -1) {
142                                            String[] keyParts = StringUtil.split(key, StringPool.COLON);
143    
144                                            key = keyParts[0];
145                                            className = keyParts[1];
146                                    }
147    
148                                    String value = parts[1];
149    
150                                    _put(key, value, className, properties);
151                            }
152    
153                            String portalPropertyPrefix =
154                                    osgiBeanProperties.portalPropertyPrefix();
155    
156                            if (Validator.isNotNull(portalPropertyPrefix)) {
157                                    Properties portalProperties = PropsUtil.getProperties(
158                                            portalPropertyPrefix,
159                                            osgiBeanProperties.portalPropertiesRemovePrefix());
160    
161                                    properties.putAll(PropertiesUtil.toMap(portalProperties));
162                            }
163    
164                            return properties;
165                    }
166    
167                    private static void _put(
168                            String key, String value, String className,
169                            Map<String, Object> properties) {
170    
171                            Type type = Type._getType(className);
172    
173                            Object previousValue = properties.get(key);
174    
175                            properties.put(key, type._convert(value, previousValue));
176                    }
177    
178            }
179    
180            /**
181             * Obtains types under which the bean is published as a service.
182             */
183            public static class Service {
184    
185                    /**
186                     * Returns the types under which the bean is published as a service.
187                     * If no types are specified, they are calculated through class
188                     * introspection. If the bean is not assignable to a specified service
189                     * type, a {@link ClassCastException} is thrown.
190                     *
191                     * @param  object the object (bean)
192                     * @return the service types
193                     */
194                    public static Set<Class<?>> interfaces(Object object) {
195                            Class<? extends Object> clazz = object.getClass();
196    
197                            OSGiBeanProperties osgiBeanProperties = clazz.getAnnotation(
198                                    OSGiBeanProperties.class);
199    
200                            if (osgiBeanProperties == null) {
201                                    return _getInterfaceClasses(clazz, new HashSet<Class<?>>());
202                            }
203    
204                            Class<?>[] serviceClasses = osgiBeanProperties.service();
205    
206                            if (serviceClasses.length == 0) {
207                                    return _getInterfaceClasses(clazz, new HashSet<Class<?>>());
208                            }
209    
210                            for (Class<?> serviceClazz : serviceClasses) {
211                                    serviceClazz.cast(object);
212                            }
213    
214                            return new HashSet<>(Arrays.asList(osgiBeanProperties.service()));
215                    }
216    
217                    private static Set<Class<?>> _getInterfaceClasses(
218                            Class<?> clazz, Set<Class<?>> interfaceClasses) {
219    
220                            if (clazz.isInterface()) {
221                                    interfaceClasses.add(clazz);
222                            }
223    
224                            for (Class<?> interfaceClass : clazz.getInterfaces()) {
225                                    _getInterfaceClasses(interfaceClass, interfaceClasses);
226                            }
227    
228                            if ((clazz = clazz.getSuperclass()) != null) {
229                                    _getInterfaceClasses(clazz, interfaceClasses);
230                            }
231    
232                            return interfaceClasses;
233                    }
234    
235            }
236    
237            public enum Type {
238    
239                    BOOLEAN, BYTE, CHARACTER, DOUBLE, FLOAT, INTEGER, LONG, SHORT, STRING;
240    
241                    private static Type _getType(String name) {
242                            name = StringUtil.toUpperCase(name);
243    
244                            for (Type type : values()) {
245                                    if (name.equals(type.name())) {
246                                            return type;
247                                    }
248                            }
249    
250                            return Type.STRING;
251                    }
252    
253                    private Object _convert(String value, Object previousValue) {
254                            if (previousValue == null) {
255                                    return _getTypedValue(value);
256                            }
257    
258                            Class<?> clazz = previousValue.getClass();
259    
260                            if (!clazz.isArray()) {
261                                    Object array = Array.newInstance(_getTypeClass(), 2);
262    
263                                    Array.set(array, 0, previousValue);
264                                    Array.set(array, 1, _getTypedValue(value));
265    
266                                    return array;
267                            }
268    
269                            Object array = Array.newInstance(
270                                    _getTypeClass(), Array.getLength(previousValue) + 1);
271    
272                            for (int i = 0; i < Array.getLength(previousValue); i++) {
273                                    Array.set(array, i, Array.get(previousValue, i));
274                            }
275    
276                            Array.set(
277                                    array, Array.getLength(previousValue), _getTypedValue(value));
278    
279                            return array;
280                    }
281    
282                    private Class<?> _getTypeClass() {
283                            if (this == Type.BOOLEAN) {
284                                    return java.lang.Boolean.class;
285                            }
286                            else if (this == Type.BYTE) {
287                                    return java.lang.Byte.class;
288                            }
289                            else if (this == Type.CHARACTER) {
290                                    return java.lang.Character.class;
291                            }
292                            else if (this == Type.DOUBLE) {
293                                    return java.lang.Double.class;
294                            }
295                            else if (this == Type.FLOAT) {
296                                    return java.lang.Float.class;
297                            }
298                            else if (this == Type.INTEGER) {
299                                    return java.lang.Integer.class;
300                            }
301                            else if (this == Type.LONG) {
302                                    return java.lang.Long.class;
303                            }
304                            else if (this == Type.SHORT) {
305                                    return java.lang.Short.class;
306                            }
307                            else if (this == Type.STRING) {
308                                    return java.lang.String.class;
309                            }
310    
311                            return null;
312                    }
313    
314                    private Object _getTypedValue(String value) {
315                            if (this == Type.BOOLEAN) {
316                                    return GetterUtil.getBoolean(value);
317                            }
318                            else if (this == Type.BYTE) {
319                                    return new java.lang.Byte(value).byteValue();
320                            }
321                            else if (this == Type.CHARACTER) {
322                                    return value.charAt(0);
323                            }
324                            else if (this == Type.DOUBLE) {
325                                    return GetterUtil.getDouble(value);
326                            }
327                            else if (this == Type.FLOAT) {
328                                    return GetterUtil.getFloat(value);
329                            }
330                            else if (this == Type.INTEGER) {
331                                    return GetterUtil.getInteger(value);
332                            }
333                            else if (this == Type.LONG) {
334                                    return GetterUtil.getLong(value);
335                            }
336                            else if (this == Type.SHORT) {
337                                    return GetterUtil.getShort(value);
338                            }
339    
340                            return value;
341                    }
342    
343            }
344    
345    }