/* * 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.wicket.core.util.lang; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.OutputStream; import java.io.Serializable; import java.util.HashMap; import org.apache.wicket.Application; import org.apache.wicket.Component; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.application.IClassResolver; import org.apache.wicket.model.IDetachable; import org.apache.wicket.serialize.ISerializer; import org.apache.wicket.serialize.java.JavaSerializer; import org.apache.wicket.settings.ApplicationSettings; import org.apache.wicket.util.io.ByteCountingOutputStream; import org.apache.wicket.util.lang.Generics; import org.apache.wicket.util.string.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Object (de)serialization utilities. */ public class WicketObjects { /** log. */ private static final Logger log = LoggerFactory.getLogger(WicketObjects.class); private WicketObjects() { } /** * @param <T> * class type * @param className * Class to resolve * @return Resolved class */ @SuppressWarnings("unchecked") public static <T> Class<T> resolveClass(final String className) { Class<T> resolved = null; try { if (Application.exists()) { resolved = (Class<T>)Application.get() .getApplicationSettings() .getClassResolver() .resolveClass(className); } if (resolved == null) { resolved = (Class<T>)Class.forName(className, false, Thread.currentThread() .getContextClassLoader()); } } catch (ClassNotFoundException cnfx) { log.warn("Could not resolve class [" + className + "]", cnfx); } return resolved; } /** * Interface that enables users to plugin the way object sizes are calculated with Wicket. */ public static interface IObjectSizeOfStrategy { /** * Computes the size of an object. This typically is an estimation, not an absolute accurate * size. * * @param object * The serializable object to compute size of * @return The size of the object in bytes. */ long sizeOf(Serializable object); } /** * {@link IObjectSizeOfStrategy} that works by serializing the object to an instance of * {@link ByteCountingOutputStream}, which records the number of bytes written to it. Hence, * this gives the size of the object as it would be serialized,including all the overhead of * writing class headers etc. Not very accurate (the real memory consumption should be lower) * but the best we can do in a cheap way pre JDK 5. */ public static final class SerializingObjectSizeOfStrategy implements IObjectSizeOfStrategy { @Override public long sizeOf(Serializable object) { if (object == null) { return 0; } ISerializer serializer = null; if (Application.exists()) { serializer = Application.get().getFrameworkSettings().getSerializer(); } if (serializer == null || serializer instanceof JavaSerializer) { // WICKET-6334 create a new instance of JavaSerializer that doesn't use custom IObjectCheckers serializer = new JavaSerializer(SerializingObjectSizeOfStrategy.class.getName()); } byte[] serialized = serializer.serialize(object); int size = -1; if (serialized != null) { size = serialized.length; } return size; } } private static final class ReplaceObjectInputStream extends ObjectInputStream { private final ClassLoader classloader; private final HashMap<String, Component> replacedComponents; private ReplaceObjectInputStream(InputStream in, HashMap<String, Component> replacedComponents, ClassLoader classloader) throws IOException { super(in); this.replacedComponents = replacedComponents; this.classloader = classloader; enableResolveObject(true); } // This override is required to resolve classes inside in different // bundle, i.e. // The classes can be resolved by OSGI classresolver implementation @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String className = desc.getName(); try { return Class.forName(className, true, classloader); } catch (ClassNotFoundException ex1) { // ignore this exception. log.debug("Class not found by using objects own classloader, trying the IClassResolver"); } Application application = Application.get(); ApplicationSettings applicationSettings = application.getApplicationSettings(); IClassResolver classResolver = applicationSettings.getClassResolver(); Class<?> candidate = null; try { candidate = classResolver.resolveClass(className); if (candidate == null) { candidate = super.resolveClass(desc); } } catch (WicketRuntimeException ex) { if (ex.getCause() instanceof ClassNotFoundException) { throw (ClassNotFoundException)ex.getCause(); } } return candidate; } @Override protected Object resolveObject(Object obj) throws IOException { Object replaced = replacedComponents.get(obj); if (replaced != null) { return replaced; } return super.resolveObject(obj); } } private static final class ReplaceObjectOutputStream extends ObjectOutputStream { private final HashMap<String, Component> replacedComponents; private ReplaceObjectOutputStream(OutputStream out, HashMap<String, Component> replacedComponents) throws IOException { super(out); this.replacedComponents = replacedComponents; enableReplaceObject(true); } @Override protected Object replaceObject(Object obj) throws IOException { if (obj instanceof Component) { final Component component = (Component)obj; String name = component.getPath(); replacedComponents.put(name, component); return name; } return super.replaceObject(obj); } } /** * Makes a deep clone of an object by serializing and deserializing it. The object must be fully * serializable to be cloned. This method will not clone wicket Components, it will just reuse * those instances so that the complete component tree is not copied over only the model data. * * <strong>Warning</strong>: this method uses Java Serialization APIs to be able to avoid cloning * of {@link org.apache.wicket.Component} instances. If the application uses custom * {@link org.apache.wicket.serialize.ISerializer} then most probably this method cannot be used. * * @param object * The object to clone * @return A deep copy of the object * @deprecated Use {@linkplain #cloneObject(Object)} instead */ @Deprecated public static <T> T cloneModel(final T object) { if (object == null) { return null; } else { try { final ByteArrayOutputStream out = new ByteArrayOutputStream(256); final HashMap<String, Component> replacedObjects = Generics.newHashMap(); ObjectOutputStream oos = new ReplaceObjectOutputStream(out, replacedObjects); oos.writeObject(object); ObjectInputStream ois = new ReplaceObjectInputStream(new ByteArrayInputStream( out.toByteArray()), replacedObjects, object.getClass().getClassLoader()); return (T) ois.readObject(); } catch (ClassNotFoundException | IOException e) { throw new WicketRuntimeException("Internal error cloning object", e); } } } /** * Strategy for calculating sizes of objects. Note: I didn't make this an application setting as * we have enough of those already, and the typical way this probably would be used is that * install a different one according to the JDK version used, so varying them between * applications doesn't make a lot of sense. */ private static IObjectSizeOfStrategy objectSizeOfStrategy = new SerializingObjectSizeOfStrategy(); /** * Makes a deep clone of an object by serializing and deserializing it. The object must be fully * serializable to be cloned. No extra debug info is gathered. * * @param object * The object to clone * @return A deep copy of the object * @see #cloneModel(Object) */ public static <T> T cloneObject(final T object) { if (object == null) { return null; } else { ISerializer serializer = null; if (Application.exists()) { serializer = Application.get().getFrameworkSettings().getSerializer(); } if (serializer == null || serializer instanceof JavaSerializer) { // WICKET-6334 create a new instance of JavaSerializer that doesn't use custom IObjectCheckers serializer = new JavaSerializer(SerializingObjectSizeOfStrategy.class.getName()); } byte[] serialized = serializer.serialize(object); if (serialized == null) { throw new IllegalStateException("A problem occurred while serializing an object. " + "Please check the earlier logs for more details. Problematic object: " + object); } Object deserialized = serializer.deserialize(serialized); return (T) deserialized; } } /** * Creates a new instance using the current application's class resolver. Returns null if * className is null. * * @param className * The full class name * @return The new object instance */ public static <T> T newInstance(final String className) { if (!Strings.isEmpty(className)) { try { Class<?> c = WicketObjects.resolveClass(className); return (T) c.newInstance(); } catch (Exception e) { throw new WicketRuntimeException("Unable to create " + className, e); } } return null; } /** * Sets the strategy for determining the sizes of objects. * * @param objectSizeOfStrategy * the strategy. Pass null to reset to the default. */ public static void setObjectSizeOfStrategy(IObjectSizeOfStrategy objectSizeOfStrategy) { if (objectSizeOfStrategy == null) { WicketObjects.objectSizeOfStrategy = new SerializingObjectSizeOfStrategy(); } else { WicketObjects.objectSizeOfStrategy = objectSizeOfStrategy; } log.info("using " + objectSizeOfStrategy + " for calculating object sizes"); } /** * Computes the size of an object. Note that this is an estimation, never an absolute accurate * size. * * @param object * Object to compute size of * @return The size of the object in bytes */ public static long sizeof(final Serializable object) { Serializable target = object; if (object instanceof Component) { // clone to not detach the original component (WICKET-5013, 5014) Component clone = (Component) cloneObject(object); clone.detach(); target = clone; } else if (object instanceof IDetachable) { // clone to not detach the original IDetachable (WICKET-5013, 5014) IDetachable clone = (IDetachable) cloneObject(object); clone.detach(); target = clone; } return objectSizeOfStrategy.sizeOf(target); } }