001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.core.util.lang;
018
019import java.io.Serializable;
020
021import org.apache.wicket.Application;
022import org.apache.wicket.Component;
023import org.apache.wicket.WicketRuntimeException;
024import org.apache.wicket.model.IDetachable;
025import org.apache.wicket.serialize.ISerializer;
026import org.apache.wicket.serialize.java.JavaSerializer;
027import org.apache.wicket.util.io.ByteCountingOutputStream;
028import org.apache.wicket.util.string.Strings;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032/**
033 * Object (de)serialization utilities.
034 */
035public class WicketObjects
036{
037        /** log. */
038        private static final Logger log = LoggerFactory.getLogger(WicketObjects.class);
039
040        private WicketObjects()
041        {
042        }
043
044        /**
045         * @param <T>
046         *            class type
047         * @param className
048         *            Class to resolve
049         * @return Resolved class
050         */
051        @SuppressWarnings("unchecked")
052        public static <T> Class<T> resolveClass(final String className)
053        {
054                Class<T> resolved = null;
055                try
056                {
057                        if (Application.exists())
058                        {
059                                resolved = (Class<T>)Application.get()
060                                        .getApplicationSettings()
061                                        .getClassResolver()
062                                        .resolveClass(className);
063                        }
064
065                        if (resolved == null)
066                        {
067                                resolved = (Class<T>)Class.forName(className, false, Thread.currentThread()
068                                        .getContextClassLoader());
069                        }
070                }
071                catch (ClassNotFoundException cnfx)
072                {
073                        log.warn("Could not resolve class [" + className + "]", cnfx);
074                }
075                return resolved;
076        }
077
078        /**
079         * Interface that enables users to plugin the way object sizes are calculated with Wicket.
080         */
081        public static interface IObjectSizeOfStrategy
082        {
083                /**
084                 * Computes the size of an object. This typically is an estimation, not an absolute accurate
085                 * size.
086                 *
087                 * @param object
088                 *            The serializable object to compute size of
089                 * @return The size of the object in bytes.
090                 */
091                long sizeOf(Serializable object);
092        }
093
094        /**
095         * {@link IObjectSizeOfStrategy} that works by serializing the object to an instance of
096         * {@link ByteCountingOutputStream}, which records the number of bytes written to it. Hence,
097         * this gives the size of the object as it would be serialized,including all the overhead of
098         * writing class headers etc. Not very accurate (the real memory consumption should be lower)
099         * but the best we can do in a cheap way pre JDK 5.
100         */
101        public static final class SerializingObjectSizeOfStrategy implements IObjectSizeOfStrategy
102        {
103                @Override
104                public long sizeOf(Serializable object)
105                {
106                        if (object == null)
107                        {
108                                return 0;
109                        }
110
111                        ISerializer serializer = null;
112                        if (Application.exists())
113                        {
114                                serializer = Application.get().getFrameworkSettings().getSerializer();
115                        }
116
117                        if (serializer == null || serializer instanceof JavaSerializer)
118                        {
119                                // WICKET-6334 create a new instance of JavaSerializer that doesn't use custom IObjectCheckers
120                                serializer = new JavaSerializer(SerializingObjectSizeOfStrategy.class.getName());
121                        }
122
123                        byte[] serialized = serializer.serialize(object);
124                        int size = -1;
125                        if (serialized != null)
126                        {
127                                size = serialized.length;
128                        }
129                        return size;
130                }
131
132        }
133
134        /**
135         * Strategy for calculating sizes of objects. Note: I didn't make this an application setting as
136         * we have enough of those already, and the typical way this probably would be used is that
137         * install a different one according to the JDK version used, so varying them between
138         * applications doesn't make a lot of sense.
139         */
140        private static IObjectSizeOfStrategy objectSizeOfStrategy = new SerializingObjectSizeOfStrategy();
141
142        /**
143         * Makes a deep clone of an object by serializing and deserializing it. The object must be fully
144         * serializable to be cloned. No extra debug info is gathered.
145         *
146         * @param object
147         *            The object to clone
148         * @return A deep copy of the object
149         */
150        @SuppressWarnings("unchecked")
151        public static <T> T cloneObject(final T object)
152        {
153                if (object == null)
154                {
155                        return null;
156                }
157                else
158                {
159                        ISerializer serializer = null;
160                        if (Application.exists())
161                        {
162                                serializer = Application.get().getFrameworkSettings().getSerializer();
163                        }
164
165                        if (serializer == null || serializer instanceof JavaSerializer)
166                        {
167                                // WICKET-6334 create a new instance of JavaSerializer that doesn't use custom IObjectCheckers
168                                serializer = new JavaSerializer(SerializingObjectSizeOfStrategy.class.getName());
169                        }
170
171                        byte[] serialized = serializer.serialize(object);
172                        if (serialized == null)
173                        {
174                                throw new IllegalStateException("A problem occurred while serializing an object. " +
175                                                "Please check the earlier logs for more details. Problematic object: " + object);
176                        }
177                        Object deserialized = serializer.deserialize(serialized);
178                        return (T) deserialized;
179                }
180        }
181
182        /**
183         * Creates a new instance using the current application's class resolver. Returns null if
184         * className is null.
185         *
186         * @param className
187         *            The full class name
188         * @return The new object instance
189         */
190        @SuppressWarnings("unchecked")
191        public static <T> T newInstance(final String className)
192        {
193                if (!Strings.isEmpty(className))
194                {
195                        try
196                        {
197                                Class<?> c = WicketObjects.resolveClass(className);
198                                return (T) c.getDeclaredConstructor().newInstance();
199                        }
200                        catch (Exception e)
201                        {
202                                throw new WicketRuntimeException("Unable to create " + className, e);
203                        }
204                }
205                return null;
206        }
207
208        /**
209         * Sets the strategy for determining the sizes of objects.
210         *
211         * @param objectSizeOfStrategy
212         *            the strategy. Pass null to reset to the default.
213         */
214        public static void setObjectSizeOfStrategy(IObjectSizeOfStrategy objectSizeOfStrategy)
215        {
216                if (objectSizeOfStrategy == null)
217                {
218                        WicketObjects.objectSizeOfStrategy = new SerializingObjectSizeOfStrategy();
219                }
220                else
221                {
222                        WicketObjects.objectSizeOfStrategy = objectSizeOfStrategy;
223                }
224                log.info("using " + objectSizeOfStrategy + " for calculating object sizes");
225        }
226
227        /**
228         * Computes the size of an object. Note that this is an estimation, never an absolute accurate
229         * size.
230         *
231         * @param object
232         *            Object to compute size of
233         * @return The size of the object in bytes
234         */
235        public static long sizeof(final Serializable object)
236        {
237                Serializable target = object;
238
239                if (object instanceof Component)
240                {
241                        // clone to not detach the original component (WICKET-5013, 5014)
242                        Component clone = (Component) cloneObject(object);
243                        clone.detach();
244
245                        target = clone;
246                }
247                else if (object instanceof IDetachable)
248                {
249                        // clone to not detach the original IDetachable (WICKET-5013, 5014)
250                        IDetachable clone = (IDetachable) cloneObject(object);
251                        clone.detach();
252
253                        target = clone;
254                }
255
256                return objectSizeOfStrategy.sizeOf(target);
257        }
258}