/** * OpenSpotLight - Open Source IT Governance Platform * * Copyright (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA * or third-party contributors as indicated by the @author tags or express * copyright attribution statements applied by the authors. All third-party * contributions are distributed under license by CARAVELATECH CONSULTORIA E * TECNOLOGIA EM INFORMATICA LTDA. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program 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. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA * *********************************************************************** * OpenSpotLight - Plataforma de Governança de TI de Código Aberto * * Direitos Autorais Reservados (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA * EM INFORMATICA LTDA ou como contribuidores terceiros indicados pela etiqueta * @author ou por expressa atribuição de direito autoral declarada e atribuída pelo autor. * Todas as contribuições de terceiros estão distribuídas sob licença da * CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA. * * Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo sob os * termos da Licença Pública Geral Menor do GNU conforme publicada pela Free Software * Foundation. * * Este programa é distribuído na expectativa de que seja útil, porém, SEM NENHUMA * GARANTIA; nem mesmo a garantia implícita de COMERCIABILIDADE OU ADEQUAÇÃO A UMA * FINALIDADE ESPECÍFICA. Consulte a Licença Pública Geral Menor do GNU para mais detalhes. * * Você deve ter recebido uma cópia da Licença Pública Geral Menor do GNU junto com este * programa; se não, escreva para: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.openspotlight.persist.internal; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.ref.WeakReference; import java.util.concurrent.locks.ReentrantLock; import org.openspotlight.common.exception.SLRuntimeException; import org.openspotlight.common.util.Assertions; import org.openspotlight.common.util.Exceptions; import org.openspotlight.common.util.SerializationUtil; import org.openspotlight.common.util.Sha1; import org.openspotlight.persist.annotation.SimpleNodeType; import org.openspotlight.persist.support.SimplePersistCapable; import org.openspotlight.storage.StorageSession; import org.openspotlight.storage.domain.StorageNode; import org.openspotlight.storage.domain.key.NodeKey; /** * This class should wrap any {@link SimpleNodeType} lazy property. This class has a control on few stuff, such as caching value * as a {@link WeakReference} or also saving it only when it is needed. * <p/> * It is thread safe by default, but its wrapped property should be thread safe also if you want to change its internal properties * on multiple threads. * <p/> * It should wrap only {@link Serializable} properties. If you want to store its parent, it is mandatory to extend * {@link StreamPropertyWithParent} instead of just put an annotation on it. This is done that way by performance reasons * (millions of items on a {@link Collection} became really slow by using reflection). * <p/> * To newPair new instances of this class, use its internal {@link LazyProperty.Factory} class. * <p/> * The class {@link LazyProperty.Metadata} should not be used outside * * @author feu * @param <T> */ public final class LazyProperty<T> implements Serializable { /** * Factory class. * * @author feu */ public static final class Factory { private Factory() {} /** * It creates an empty {@link LazyProperty}. All parameter are mandatory. * * @param <T> * @param parent * @return */ public static <T> LazyProperty<T> create(final Class<? super T> type, final SimpleNodeType parent) { Assertions.checkNotNull("parent", parent); return new LazyProperty<T>(parent, (Class<T>) type); } } /** * Internal metadata. Should be used only on {@link org.openspotlight.persist.support.SimplePersistCapable}. This class expose * internal information about cached and transient values. * * @author feu */ public final class Metadata implements Serializable { /** * */ private static final long serialVersionUID = -31577246921422934L; private Metadata() {} private String createSha1(final T content) { if (content == null) { return null; } try { if (content instanceof Serializable) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(content); oos.flush(); oos.close(); return Sha1.getSha1SignatureEncodedAsBase64(baos.toByteArray()); } else { final InputStream is = (InputStream) content; return Sha1.getSha1SignatureEncodedAsBase64(is); } } catch (final Exception e) { throw Exceptions.logAndReturnNew(e, SLRuntimeException.class); } } /** * Return the cached value. If there's no cached value, the session will be used to load a new one. The session isn't * mandatory, since the value should be cached, but if it isn't, it will throw a {@link NullPointerException}. * * @return */ @SuppressWarnings("unchecked") public T getCached(final SimplePersistCapable<StorageNode, StorageSession> simplePersist) { try { lock.lock(); final T cachedValue = cached == null ? null : cached.get(); if (cachedValue == null) { if (simplePersist == null && parentKey == null) { return null; } if (simplePersist == null) { throw Exceptions.logAndReturn(new IllegalStateException( "trying to retrieve a value with a null session")); } Assertions.checkNotNull("parentKey", parentKey); Assertions.checkNotEmpty("propertyName", propertyName); try { if (node == null) { node = simplePersist .getCurrentSession() .withPartition( simplePersist.getCurrentPartition()) .createCriteria() .withUniqueKey( parentKey) .buildCriteria() .andSearchUnique( simplePersist .getCurrentSession()); } final InputStream o = node.getPropertyValueAsStream(simplePersist.getCurrentSession(), propertyName); final InputStream is = o; if (is != null && is.markSupported()) { is.reset(); } final T s = (T) SerializationUtil.deserialize(o); simplePersist.getInternalMethods().beforeUnConvert(parent, (Serializable) s, null); setCached(s); return s; } catch (final Exception e) { throw Exceptions.logAndReturnNew(e, SLRuntimeException.class); } } return cachedValue; } finally { lock.unlock(); } } /** * Return the lock used internally. * * @return */ public ReentrantLock getLock() { return lock; } /** * Return the parent property. * * @return */ public SimpleNodeType getParent() { return parent; } public NodeKey getParentKey() { return parentKey; } public String getPropertyName() { return propertyName; } public Class<T> getPropertyType() { return type; } public String getSha1() { return sha1; } /** * Return the transient value if there's any one setted * * @return */ public T getTransient() { try { lock.lock(); return transientValue; } finally { lock.unlock(); } } /** * This method should not be used outside {@link org.openspotlight.persist.support.SimplePersistCapable}. * * @param sha1 */ public void internalSetSha1(final String sha1) { LazyProperty.this.sha1 = sha1; } public boolean isBlank() { return parentKey == null && transientValue == null; } public boolean isCacheLoaded() { try { lock.lock(); return cached != null && cached.get() != null; } finally { lock.unlock(); } } public boolean isFilled() { return !isBlank(); } /** * It will clean the transient value and its needs save status. */ public void markAsSaved() { try { lock.lock(); needsSave = false; LazyProperty.this.transientValue = null; } finally { lock.unlock(); } } public boolean needsSave() { try { lock.lock(); return needsSave; } finally { lock.unlock(); } } public void setCached(final T cached) { try { lock.lock(); LazyProperty.this.cached = new WeakReference<T>(cached); } finally { lock.unlock(); } } public void setParentKey(final NodeKey parentKey) { LazyProperty.this.parentKey = parentKey; } public void setPropertyName(final String propertyName) { LazyProperty.this.propertyName = propertyName; } public void setSavedNode(final StorageNode node) { LazyProperty.this.node = node; } } /** * */ private static final long serialVersionUID = 7214615570747274715L; private transient WeakReference<T> cached = null; private final ReentrantLock lock = new ReentrantLock(); private final Metadata metadata = new Metadata(); private boolean needsSave = false; private StorageNode node; private final SimpleNodeType parent; private NodeKey parentKey; private String propertyName; private String sha1; private T transientValue = null; private final Class<T> type; private LazyProperty( final SimpleNodeType parent, final Class<T> type) { this.parent = parent; this.type = type; } /** * It will try to return, in this order, the transient value, and if there's no transient value, it will return the saved * value. * * @return */ public T get(final SimplePersistCapable<StorageNode, StorageSession> simplePersist) { try { lock.lock(); T value = metadata.getTransient(); if (value == null) { value = metadata.getCached(simplePersist); } return value; } finally { lock.unlock(); } } public Metadata getMetadata() { return metadata; } /** * It will set the transient value and will activate the needsSave flag. * * @param newValue */ public void setTransient(final T newValue) { try { lock.lock(); this.transientValue = newValue; sha1 = getMetadata().createSha1(this.transientValue); needsSave = true; } finally { lock.unlock(); } } }