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}