/*************************************************************************
* Copyright 2009-2014 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
*
* This file may incorporate work covered under the following copyright
* and permission notice:
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Regents of the University of California
* All rights reserved.
*
* Redistribution and use of this software in source and binary forms,
* with or without modification, are permitted provided that the
* following conditions are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
* THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
* COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
* AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
* IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
* SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
* WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
* REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
* IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
* NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
************************************************************************/
package com.eucalyptus.configurable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.EntityTransaction;
import org.apache.log4j.Logger;
import com.eucalyptus.configurable.PropertyDirectory.NoopEventListener;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionResource;
import com.eucalyptus.records.Logs;
import com.eucalyptus.util.Classes;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
public abstract class AbstractConfigurableProperty implements ConfigurableProperty {
private static Logger LOG = Logger.getLogger( AbstractConfigurableProperty.class );
private final String entrySetName;
private final String fieldName;
private final String qualifiedName;
private final String description;
private final PropertyTypeParser typeParser;
private final String defaultValue;
private final Class definingClass;
private final Constructor noArgConstructor;
private final Boolean readOnly;
private final String displayName;
private final ConfigurableFieldType widgetType;
private final String alias;
private final PropertyChangeListener changeListener;
private final Field field;
private final Method getter;
private final Method setter;
private final Class[] setArgs;
private final boolean deferred;
public AbstractConfigurableProperty( Class definingClass, String entrySetName, Field field, String defaultValue, String description,
PropertyTypeParser typeParser, Boolean readOnly, String displayName, ConfigurableFieldType widgetType, String alias ) {
this( definingClass, entrySetName, field, defaultValue, description, typeParser, readOnly, displayName, widgetType, alias,
NoopEventListener.NOOP );
}
public AbstractConfigurableProperty( Class definingClass, String entrySetName, Field field, String defaultValue, String description,
PropertyTypeParser typeParser, Boolean readOnly, String displayName, ConfigurableFieldType widgetType, String alias,
PropertyChangeListener changeListener ) {
this.definingClass = definingClass;
this.field = field;
this.fieldName = this.field.getName( ).toLowerCase( );
this.entrySetName = entrySetName.toLowerCase( );
this.qualifiedName = this.entrySetName + "." + this.fieldName;
this.description = description;
this.typeParser = typeParser;
this.defaultValue = defaultValue;
this.readOnly = readOnly;
this.displayName = displayName;
this.widgetType = widgetType;
this.alias = alias;
this.changeListener = changeListener;
Constructor cons = null;
try {
cons = this.definingClass.getConstructor( new Class[] {} );
cons.setAccessible( true );
} catch ( Exception ex ) {
if ( !Modifier.isStatic( field.getModifiers( ) ) ) {
LOG.debug( "Known declared constructors: " + this.getDefiningClass( ).getDeclaredConstructors( ) );
LOG.debug( "Known constructors: " + this.getDefiningClass( ).getConstructors( ) );
LOG.debug( ex, ex );
throw new RuntimeException( ex );
} else {
//that a default no-arg constructor is required indicates there is too much specialized junk in here.
}
}
this.noArgConstructor = cons;
this.setArgs = new Class[] { this.field.getType( ) };
this.getter = this.getReflectedMethod( "get", this.field );
this.setter = this.getReflectedMethod( "set", this.field, this.setArgs );
ConfigurableClass configurableAnnot = ( ConfigurableClass ) definingClass.getAnnotation( ConfigurableClass.class );
this.deferred = configurableAnnot.deferred( );
}
private Method getReflectedMethod( String namePrefix, Field field, Class... setArgs2 ) {
try {
String name = namePrefix + this.field.getName( ).substring( 0, 1 ).toUpperCase( ) + this.field.getName( ).substring( 1 );
Method m = definingClass.getDeclaredMethod( name, setArgs2 );
m.setAccessible( true );
return m;
} catch ( Exception e ) {
if( !Modifier.isStatic( field.getModifiers( ) ) ) {
LOG.debug( "Known declared methods: " + this.getDefiningClass( ).getDeclaredMethods( ) );
LOG.debug( "Known methods: " + this.getDefiningClass( ).getMethods( ) );
LOG.debug( e, e );
}
return null;
}
}
protected abstract Object getQueryObject( ) throws Exception;
protected Object getInitialObject( ) throws Exception { return null; };
public String getFieldName( ) {
return this.fieldName;
}
public String getEntrySetName( ) {
return this.entrySetName;
}
public String getQualifiedName( ) {
return this.qualifiedName;
}
public String getDescription( ) {
return this.description;
}
public PropertyTypeParser getTypeParser( ) {
return this.typeParser;
}
public String getDefaultValue( ) {
return this.defaultValue;
}
public String getValue( ) {
try ( final TransactionResource trans = Entities.transactionFor( this.getDefiningClass( ) ) ) {
//Unique result gets first found value if multiple exist, should work if all are kept in sync
Object o = Entities.uniqueResult( this.getQueryObject( ) );
Object prop = this.getter.invoke( o );
String result = prop != null
? prop.toString( )
: "";
trans.commit( );
return result;
} catch (Exception e) {
Logs.exhaust().error(e, e);
return "";
}
}
public String setValue( String s ) throws ConfigurablePropertyException {
try ( final TransactionResource trans = Entities.transactionFor( this.getDefiningClass( ) ) ) {
//This should return all matching objects
List<Object> resultList = Entities.query( this.getQueryObject( ) );
Object prop = this.getTypeParser( ).apply( s );
if(resultList == null || resultList.size() == 0) {
Object initial = this.getInitialObject( );
if ( initial != null ) {
resultList = Collections.singletonList( Entities.persist( initial ) );
} else {
throw new EucalyptusCloudException( "Property '" + getQualifiedName( ) +
"' is not ready to be changed. Make sure that you have all needed modules loaded." );
}
}
this.fireChange( prop ); //Fire change only once
LOG.debug("Running setters.");
for(Object obj : resultList) {
this.setter.invoke( obj, prop );
}
trans.commit( );
return s;
} catch ( Exception e ) {
Logs.exhaust( ).error( e, e );
Exceptions.findAndRethrow( e, ConfigurablePropertyException.class );
throw new ConfigurablePropertyException( e.getMessage( ), e );
}
}
public Class getDefiningClass( ) {
return this.definingClass;
}
public Constructor getNoArgConstructor( ) {
return this.noArgConstructor;
}
public String getDisplayName( ) {
return this.displayName;
}
public ConfigurableFieldType getWidgetType( ) {
return this.widgetType;
}
public String getAlias( ) {
return this.alias;
}
protected void fireChange( Object newValue ) throws ConfigurablePropertyException {
if ( !NoopEventListener.class.equals( this.changeListener.getClass( ) ) ) {
this.changeListener.fireChange( this, newValue );
}
}
@Override
public Boolean getReadOnly( ) {
return this.readOnly;
}
public PropertyChangeListener getChangeListener( ) {
return this.changeListener;
}
public Field getField( ) {
return this.field;
}
public boolean isDeferred( ) {
return this.deferred;
}
@Nullable
protected Object getInitialObjectByAnnotation( ) throws Exception {
final Method init = findInitMethod( getDefiningClass( ) );
if ( init == null ) {
return null;
}
init.setAccessible( true );
final Object initial = getQueryObject( );
init.invoke( initial );
return initial;
}
@Nullable
protected static Method findInitMethod( @Nonnull final Class definingClass ) throws SecurityException {
for ( final Class ancestor : Classes.classAncestors( definingClass ) ) {
for ( final Method method : ancestor.getDeclaredMethods( ) ) {
if ( method.isAnnotationPresent( ConfigurableInit.class ) ) {
return method;
}
}
}
return null;
}
@Nonnull
protected static String configurableFieldInitial( @Nonnull ConfigurableField configurableField ) {
return
configurableField.initialInt( ) == Integer.MIN_VALUE ?
configurableField.initial( ) :
String.valueOf( configurableField.initialInt( ) );
}
}