/** * 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.storage.domain; import static org.openspotlight.common.util.Assertions.checkNotNull; import static org.openspotlight.common.util.Exceptions.logAndReturnNew; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import org.apache.commons.io.IOUtils; import org.openspotlight.common.exception.SLRuntimeException; import org.openspotlight.storage.StorageSessionImpl; import org.openspotlight.storage.StorageSession; import org.openspotlight.storage.domain.Property; import org.openspotlight.storage.domain.PropertyContainer; /** * Internal (default) implementation of {@link Property}. <br> * In this implementation the property values are loaded lazy once requested. * * @author feuteston * @author porcelli */ public class PropertyImpl implements Property { /** * Internal structure that holds property values.<br> * This class uses {@link Reference} to optimize memory use. * * @author feuteston * @author porcelli */ private class PropertyValue { private boolean dirty; private boolean loaded; private byte[] realValue; private Reference<InputStream> weakValueAsStream; private Reference<String> weakValueAsString; /** * Creates a {@link SoftReference} for input parameter. * * @param <T> type of input param * @param t input to be enclosed by soft reference * @return the soft reference */ private <T> Reference<T> asSoftRef(final T t) { return t != null ? new SoftReference<T>(t) : null; } /** * Returns the reference value * * @param <T> type of input param * @param ref input to extract the value * @return value, or null if content were garbage collected */ private <T> T getSoftValue(final Reference<T> ref) { return ref != null ? ref.get() : null; } /** * Converts an {@link InputStream} to a byte array * * @param is value to be converted * @return value converted to byte array */ private byte[] asBytes(final InputStream is) { if (is == null) { return null; } try { if (is.markSupported()) { is.reset(); } final ByteArrayOutputStream os = new ByteArrayOutputStream(); IOUtils.copy(is, os); return os.toByteArray(); } catch (final Exception e) { throw logAndReturnNew(e, SLRuntimeException.class); } } /** * Converts a String to a byte array * * @param s value to be converted * @return value converted to byte array */ private byte[] asBytes(final String s) { return s != null ? s.getBytes() : null; } /** * Converts a byte array to {@link InputStream} * * @param b value to be converted * @return value converted to input stream */ private InputStream asStream(final byte[] b) { return b != null ? new ByteArrayInputStream(b) : null; } /** * Converts a byte array to String * * @param b value to be converted * @return value converted to string */ private String asString(final byte[] b) { return b != null ? new String(b) : null; } /** * Reset internal fields that stores values */ private void nullEverything() { weakValueAsString = null; weakValueAsStream = null; realValue = null; } /** * Returns the property value as byte array. * * @return the value as byte array */ public byte[] getValueAsBytes() { return realValue; } /** * Returns the property value as {@link InputStream}. * * @return the value as input stream */ public InputStream getValueAsStream() { InputStream value = getSoftValue(weakValueAsStream); if (value == null) { value = asStream(realValue); weakValueAsStream = asSoftRef(value); } if (value != null && value.markSupported()) { try { value.reset(); } catch (final Exception e) { throw logAndReturnNew(e, SLRuntimeException.class); } } return value; } /** * Returns the property value as String. * * @return the value as string */ public String getValueAsString() { String value = getSoftValue(weakValueAsString); if (value == null) { value = asString(realValue); weakValueAsString = asSoftRef(value); } return value; } /** * Checks if property value is modified. * * @return true if changed, false otherwise */ public boolean isDirty() { return dirty; } /** * Checks if property value is loaded. * * @return true if loaded, false otherwise */ public boolean isLoaded() { return loaded; } /** * Sets the dirty * * @param dirty true if changed, false otherwise */ public void setDirty(final boolean dirty) { this.dirty = dirty; } /** * Sets the loaded. * * @param loaded true if loaded, false otherwise */ public void setLoaded(final boolean loaded) { this.loaded = loaded; } /** * Sets the property value in String format. Null is an accepted value. <br> * * @param value the property value */ public void setValue(final String value) { nullEverything(); realValue = asBytes(value); weakValueAsString = asSoftRef(value); } /** * Sets the property value using {@link InputStream} format. Null is an accepted value. <br> * * @param value the property value */ public void setValue(final InputStream value) { nullEverything(); realValue = asBytes(value); } /** * Sets the property value in byte array format. Null is an accepted value. <br> * * @param value the property value */ public void setValue(final byte[] value) { nullEverything(); realValue = value; } } private final boolean indexed; private final boolean key; private final String name; private final PropertyContainer parent; private final PropertyValue propertyValue = new PropertyValue(); private PropertyImpl(final String name, final PropertyContainer parent, final boolean indexed, final boolean key) { this.name = name; this.parent = parent; this.indexed = indexed; this.key = key; } /** * Factory method that produces a property that is also a key * * @param name the property/key name * @param parent the property container reference * @return a new instance */ public static PropertyImpl createKey(final String name, final PropertyContainer parent) { final PropertyImpl property = new PropertyImpl(name, parent, true, true); return property; } /** * Factory method that produces an indexed property. * * @param name the property name * @param parent the property container reference * @return a new instance */ public static PropertyImpl createIndexed(final String name, final PropertyContainer parent) { final PropertyImpl property = new PropertyImpl(name, parent, true, false); return property; } /** * Factory method that produces a regular property. * * @param name the property name * @param parent the property container reference * @return a new instance */ public static PropertyImpl createSimple(final String name, final PropertyContainer parent) { final PropertyImpl property = new PropertyImpl(name, parent, false, false); return property; } /** * Refresh the property value if its not dirty and not already loaded. * * @param session the storage session */ private void refreshPropertyIfNecessary(final StorageSession session) { if (!propertyValue.isDirty() && !propertyValue.isLoaded()) { propertyValue.setValue(((StorageSessionImpl<?, ?>) session).getPropertyValue(this)); propertyValue.setLoaded(true); propertyValue.setDirty(false); } } /** * Checks if property can be setted. <br> * For now the unique restrition is that properties that also are keys can't be setted. * * @throws IllegalStateException if property can't be setted */ private void verifyBeforeSet() throws IllegalStateException { if (key) { throw new IllegalStateException(); } } /** * Clean property value, if its not dirty, when the value is bigger than 255 bytes. */ public void removeTransientValueIfExpensive() { if (!key && !propertyValue.isDirty() && propertyValue.isLoaded()) { if (propertyValue.getValueAsBytes() != null) { if (propertyValue.getValueAsBytes().length > 255) { propertyValue.setValue((byte[]) null); propertyValue.setLoaded(false); } } } } /** * Returns the transient property value as String. <br> * <b>Note:</b> the unique difference of this operation from {@link #getValueAsString(StorageSession)} is that this method * does not try to reload the property value. * * @return the transient value */ public String getTransientValueAsString() { return propertyValue.getValueAsString(); } /** * Returns the transient property value as Stream. <br> * <b>Note:</b> the unique difference of this operation from {@link #getValueAsStream(StorageSession)} is that this method * does not try to reload the property value. * * @return the transient value */ public InputStream getTransientValueAsStream() { return propertyValue.getValueAsStream(); } /** * Returns the transient property value as byte array. <br> * <b>Note:</b> the unique difference of this operation from {@link #getValueAsBytes(StorageSession)} is that this method does * not try to reload the property value. * * @return the transient value */ public byte[] getTransientValueAsBytes() { return propertyValue.getValueAsBytes(); } /** * Initialize the property value * * @param value the value to be loaded */ public void setStringValueOnLoad(final String value) throws IllegalArgumentException { propertyValue.setValue(value); propertyValue.setDirty(false); propertyValue.setLoaded(true); } /** * Initialize the property value * * @param value the value to be loaded * @throws IllegalArgumentException if input param is null */ public void setStreamValueOnLoad(final InputStream value) { propertyValue.setValue(value); propertyValue.setDirty(false); propertyValue.setLoaded(true); } /** * Initialize the property value * * @param value the value to be loaded */ public void setBytesValueOnLoad(final byte[] value) { propertyValue.setValue(value); propertyValue.setDirty(false); propertyValue.setLoaded(true); } /** * {@inheritDoc} */ @Override public PropertyContainer getParent() { return parent; } /** * {@inheritDoc} */ @Override public String getPropertyName() { return name; } /** * {@inheritDoc} */ @Override public boolean isIndexed() { return indexed; } /** * {@inheritDoc} */ @Override public boolean isKey() { return key; } /** * {@inheritDoc} */ @Override public String getValueAsString(final StorageSession session) throws IllegalArgumentException { checkNotNull("session", session); refreshPropertyIfNecessary(session); return propertyValue.getValueAsString(); } /** * {@inheritDoc} */ @Override public InputStream getValueAsStream(final StorageSession session) throws IllegalArgumentException { checkNotNull("session", session); refreshPropertyIfNecessary(session); return propertyValue.getValueAsStream(); } /** * {@inheritDoc} */ @Override public byte[] getValueAsBytes(final StorageSession session) throws IllegalArgumentException { checkNotNull("session", session); refreshPropertyIfNecessary(session); return propertyValue.getValueAsBytes(); } /** * {@inheritDoc} */ @Override public void setStringValue(final StorageSession session, final String value) throws IllegalArgumentException, IllegalStateException { checkNotNull("session", session); verifyBeforeSet(); propertyValue.setDirty(true); propertyValue.setValue(value); ((StorageSessionImpl<?, ?>) session).setPropertyValue(this, propertyValue.getValueAsBytes()); } /** * {@inheritDoc} */ @Override public void setStreamValue(final StorageSession session, final InputStream value) throws IllegalArgumentException, IllegalStateException { checkNotNull("session", session); verifyBeforeSet(); propertyValue.setDirty(true); propertyValue.setValue(value); ((StorageSessionImpl<?, ?>) session).setPropertyValue(this, propertyValue.getValueAsBytes()); } /** * {@inheritDoc} */ @Override public void setBytesValue(final StorageSession session, final byte[] value) throws IllegalArgumentException, IllegalStateException { checkNotNull("session", session); verifyBeforeSet(); propertyValue.setDirty(true); propertyValue.setValue(value); ((StorageSessionImpl<?, ?>) session).setPropertyValue(this, propertyValue.getValueAsBytes()); } }