blob: d3a440dc4ebcf614d9c7ff88639d8ba6f1c19331 [file] [log] [blame]
/*
* 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.directory.server.component.handler.ipojo;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;
import org.apache.directory.server.component.handler.ipojo.property.DirectoryProperty;
import org.apache.directory.server.component.handler.ipojo.property.DirectoryPropertyDescription;
import org.apache.directory.server.hub.api.component.util.ComponentConstants;
import org.apache.felix.ipojo.ConfigurationException;
import org.apache.felix.ipojo.HandlerFactory;
import org.apache.felix.ipojo.PrimitiveHandler;
import org.apache.felix.ipojo.annotations.Handler;
import org.apache.felix.ipojo.architecture.ComponentTypeDescription;
import org.apache.felix.ipojo.architecture.HandlerDescription;
import org.apache.felix.ipojo.architecture.PropertyDescription;
import org.apache.felix.ipojo.handlers.dependency.Dependency;
import org.apache.felix.ipojo.handlers.dependency.DependencyHandler;
import org.apache.felix.ipojo.handlers.providedservice.ProvidedServiceHandler;
import org.apache.felix.ipojo.metadata.Attribute;
import org.apache.felix.ipojo.metadata.Element;
import org.apache.felix.ipojo.parser.FieldMetadata;
import org.apache.felix.ipojo.parser.MethodMetadata;
import org.apache.felix.ipojo.parser.PojoMetadata;
import org.apache.felix.ipojo.util.Callback;
import org.apache.felix.ipojo.util.Property;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
/**
* Handler Managing ApacheDS configuration.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public abstract class AbstractDcHandler extends PrimitiveHandler
{
/**
* List of the configurable fields.
*/
private List m_configurableProperties = new ArrayList( 1 );
/**
* Owning ApacheDS instance of this component.
*/
private String m_ownerADSInstance;
/**
* the handler description.
*/
private DirectoryComponentHandlerDescription m_description;
/**
* Updated method.
* This method is called when a reconfiguration is completed.
*/
private Callback m_updated;
/**
* Initialize the component type.
* @param desc : component type description to populate.
* @param metadata : component type metadata.
* @throws ConfigurationException : metadata are incorrect.
*/
public void initializeComponentFactory( ComponentTypeDescription desc, Element metadata )
throws ConfigurationException
{
Element[] confs = metadata.getElements( getHandlerName(), getHandlerNamespaceName() );
if ( confs == null )
{
return;
}
Element[] configurables = confs[0].getElements( "DirectoryProperty", getHandlerNamespaceName() );
for ( int i = 0; configurables != null && i < configurables.length; i++ )
{
String fieldName = configurables[i].getAttribute( "field" );
String methodName = configurables[i].getAttribute( "method" );
String paramIndex = configurables[i].getAttribute( "constructor-parameter" );
if ( fieldName == null && methodName == null && paramIndex == null )
{
throw new ConfigurationException( "Malformed property : The property needs to contain" +
" at least a field, a method or a constructor-parameter" );
}
String name = configurables[i].getAttribute( "name" );
if ( name == null )
{
if ( fieldName == null && methodName != null )
{
name = methodName;
}
else if ( fieldName == null && paramIndex != null )
{
name = paramIndex;
}
else
{
name = fieldName;
}
configurables[i].addAttribute( new Attribute( "name", name ) ); // Add the type to avoid configure checking
}
// Detect the type of the property
PojoMetadata manipulation = getFactory().getPojoMetadata();
String type = null;
if ( methodName != null )
{
MethodMetadata[] method = manipulation.getMethods( methodName );
if ( method.length == 0 )
{
type = configurables[i].getAttribute( "type" );
if ( type == null )
{
throw new ConfigurationException(
"Malformed property : The type of the property cannot be discovered, add a 'type' attribute" );
}
}
else
{
if ( method[0].getMethodArguments().length != 1 )
{
throw new ConfigurationException( "Malformed property : The method " + methodName
+ " does not have one argument" );
}
type = method[0].getMethodArguments()[0];
configurables[i].addAttribute( new Attribute( "type", type ) ); // Add the type to avoid configure checking
}
}
else if ( fieldName != null )
{
FieldMetadata field = manipulation.getField( fieldName );
if ( field == null )
{
throw new ConfigurationException( "Malformed property : The field " + fieldName
+ " does not exist in the implementation class" );
}
type = field.getFieldType();
configurables[i].addAttribute( new Attribute( "type", type ) ); // Add the type to avoid configure checking
}
else if ( paramIndex != null )
{
int index = Integer.parseInt( paramIndex );
type = configurables[i].getAttribute( "type" );
MethodMetadata[] cts = manipulation.getConstructors();
// If we don't have a type, try to get the first constructor and get the type of the parameter
// we the index 'index'.
if ( type == null && cts.length > 0 && cts[0].getMethodArguments().length > index )
{
type = cts[0].getMethodArguments()[index];
}
else if ( type == null )
{ // Applied only if type was not determined.
throw new ConfigurationException( "Cannot determine the type of the property " + index +
", please use the type attribute" );
}
configurables[i].addAttribute( new Attribute( "type", type ) );
}
boolean mandatory = false;
String man = configurables[i].getAttribute( "mandatory" );
mandatory = man != null && man.equalsIgnoreCase( "true" );
boolean immutable = false;
String imm = configurables[i].getAttribute( "immutable" );
immutable = imm != null && imm.equalsIgnoreCase( "true" );
// If property is constructor index then it must be mandatory.
if ( paramIndex != null )
{
immutable = true;
mandatory = true;
}
String description = configurables[i].getAttribute( "description" );
String containertype = configurables[i].getAttribute( "containertype" );
DirectoryPropertyDescription pd = new DirectoryPropertyDescription( name, type, null, description,
containertype, immutable );
if ( mandatory )
{
pd.setMandatory();
}
desc.addProperty( pd );
}
desc.addProperty( new DirectoryPropertyDescription( true, ComponentConstants.DC_NATURE_INDICATOR, "true" ) );
Hashtable<String, String> constantProperties = extractConstantProperties( metadata );
if ( constantProperties != null )
{
for ( String key : constantProperties.keySet() )
{
DirectoryPropertyDescription pd = new DirectoryPropertyDescription( true, key,
constantProperties.get( key ) );
desc.addProperty( pd );
}
}
}
/**
* Configures the handler.
* Access to field does not require synchronization as this method is executed
* before any thread access to this object.
* @param metadata the metadata of the component
* @param configuration the instance configuration
* @throws ConfigurationException one property metadata is not correct
*/
public void configure( Element metadata, Dictionary configuration ) throws ConfigurationException
{
// Owning ApacheDS instance
String ownerADS = ( String ) configuration.get( DcHandlerConstants.DSCOMPONENT_OWNER_PROP_NAME );
m_ownerADSInstance = ownerADS;
// Build the map
Element[] confs = metadata.getElements( getHandlerName(), getHandlerNamespaceName() );
Element[] configurables = confs[0].getElements( "DirectoryProperty", getHandlerNamespaceName() );
// updated method
String upd = confs[0].getAttribute( "DirectoryUpdated", getHandlerNamespaceName() );
if ( upd != null )
{
MethodMetadata method = getPojoMetadata().getMethod( upd );
if ( method == null )
{
throw new ConfigurationException( "The updated method is not found in the class "
+ getInstanceManager().getClassName() );
}
else if ( method.getMethodArguments().length == 0 )
{
m_updated = new Callback( upd, new Class[0], false, getInstanceManager() );
}
else if ( method.getMethodArguments().length == 1
&& method.getMethodArguments()[0].equals( Dictionary.class.getName() ) )
{
m_updated = new Callback( upd, new Class[]
{ Dictionary.class }, false, getInstanceManager() );
}
else
{
throw new ConfigurationException( "The updated method is found in the class "
+ getInstanceManager().getClassName() + " must have either no argument or a Dictionary" );
}
}
for ( int i = 0; configurables != null && i < configurables.length; i++ )
{
String fieldName = configurables[i].getAttribute( "field" );
String methodName = configurables[i].getAttribute( "method" );
String paramIndex = configurables[i].getAttribute( "constructor-parameter" );
int index = -1;
String name = configurables[i].getAttribute( "name" ); // The initialize method has fixed the property name.
String type = configurables[i].getAttribute( "type" ); // The initialize method has fixed the property name.
String desc = configurables[i].getAttribute( "description" );
String container = configurables[i].getAttribute( "containertype" );
DirectoryProperty prop = null;
if ( paramIndex != null )
{
index = Integer.parseInt( paramIndex );
}
prop = new DirectoryProperty( name, fieldName, methodName, index,
null, type, desc, container, getInstanceManager(), this );
addProperty( prop );
// Check if the instance configuration contains value for the current property :
if ( configuration.get( name ) != null )
{
prop.setValue( configuration.get( name ) );
}
else
{
if ( fieldName != null && configuration.get( fieldName ) != null )
{
prop.setValue( configuration.get( fieldName ) );
}
}
if ( fieldName != null )
{
FieldMetadata field = new FieldMetadata( fieldName, type );
getInstanceManager().register( field, prop );
}
if ( index != -1 )
{
getInstanceManager().register( index, prop );
}
}
m_description = new DirectoryComponentHandlerDescription( this, m_configurableProperties );
}
/**
* Stop method.
* This method is synchronized to avoid the configuration admin pushing a configuration during the un-registration.
* Do nothing.
* @see org.apache.felix.ipojo.Handler#stop()
*/
public synchronized void stop()
{
}
/**
* Start method.
*/
public synchronized void start()
{
/*
* Here we provide way to provide one ApacheDS's instances from anothers in same OSGI container.
* If some ApacheDS component is requiring some other ApacheDS component by @Dependency system, it is ensured
* to get the component which is instantiated under its own ApacheDS instance.
*/
if ( m_ownerADSInstance != null )
{
// Get the provided service handler :
ProvidedServiceHandler m_providedServiceHandler = ( ProvidedServiceHandler ) getHandler( HandlerFactory.IPOJO_NAMESPACE
+ ":provides" );
// Add owning ApacheDS information to every published service of this component.
if ( m_ownerADSInstance != null )
{
Properties ownerADSProps = new Properties();
ownerADSProps.put( DcHandlerConstants.DSCOMPONENT_OWNER_PROP_NAME, m_ownerADSInstance );
m_providedServiceHandler.addProperties( ownerADSProps );
}
// Get the dependency handler
DependencyHandler m_dependencyHandler = ( DependencyHandler ) getHandler( HandlerFactory.IPOJO_NAMESPACE
+ ":requires" );
// Add owning ApacheDS instance filter into every Dependency for cross-instance safety
Dependency[] dependencies = m_dependencyHandler.getDependencies();
for ( Dependency dep : dependencies )
{
String currentFilter = dep.getFilter();
String ownerProp = DcHandlerConstants.DSCOMPONENT_OWNER_PROP_NAME;
String owningRestriction = "(|(!(" + ownerProp + "=*))(" + ownerProp + "=" + m_ownerADSInstance + "))";
String augmentedFilter = "(&" + currentFilter + owningRestriction + ")";
try
{
Filter filter = getInstanceManager().getContext().createFilter( augmentedFilter );
dep.setFilter( filter );
}
catch ( InvalidSyntaxException e )
{
info( "Augmented requirement filter is invalid : " + augmentedFilter
+ " - " + e.getMessage() );
}
}
}
// Give initial values and reset the 'invoked' flag.
for ( int i = 0; i < m_configurableProperties.size(); i++ )
{
DirectoryProperty prop = ( DirectoryProperty ) m_configurableProperties.get( i );
prop.reset(); // Clear the invoked flag.
if ( prop.hasField() && prop.getValue() != Property.NO_VALUE && prop.getValue() != null )
{
getInstanceManager().onSet( null, prop.getField(), prop.getValue() );
}
}
}
/**
* Adds the given property metadata to the property metadata list.
*
* @param prop : property metadata to add
*/
protected void addProperty( DirectoryProperty prop )
{
m_configurableProperties.add( prop );
}
/**
* Checks if the list contains the property.
*
* @param name : name of the property
* @return true if the property exist in the list
*/
protected boolean containsProperty( String name )
{
for ( int i = 0; i < m_configurableProperties.size(); i++ )
{
if ( ( ( DirectoryProperty ) m_configurableProperties.get( i ) ).getName().equals( name ) )
{
return true;
}
}
return false;
}
/**
* Reconfigure the component instance.
* Check if the new configuration modifies the current configuration.
* Invokes the updated method is needed.
* @param configuration : the new configuration
*/
public synchronized void reconfigure( Dictionary configuration )
{
info( getInstanceManager().getInstanceName() + " is reconfiguring the properties : " + configuration );
reconfigureProperties( configuration );
if ( getInstanceManager().getPojoObjects() != null )
{
try
{
notifyUpdated( null );
}
catch ( Throwable e )
{
error( "Cannot call the updated method : " + e.getMessage(), e );
}
}
}
/**
* Reconfigured configuration properties and returns non matching properties.
* When called, it must hold the monitor lock.
* @param configuration : new configuration
* @return the properties that does not match with configuration properties
*/
private Properties reconfigureProperties( Dictionary configuration )
{
Properties nonmatchingProps = new Properties();
Enumeration keysEnumeration = configuration.keys();
while ( keysEnumeration.hasMoreElements() )
{
String name = ( String ) keysEnumeration.nextElement();
Object value = configuration.get( name );
boolean found = false;
// Check if the name is a configurable property
for ( int i = 0; i < m_configurableProperties.size(); i++ )
{
DirectoryProperty prop = ( DirectoryProperty ) m_configurableProperties.get( i );
if ( prop.getName().equals( name ) )
{
reconfigureProperty( prop, value );
found = true;
break; // Exit the search loop
}
}
if ( !found )
{
// The property is not a configurable property, add it to the toPropagate list.
nonmatchingProps.put( name, value );
}
}
// Every removed configurable property gets reset to its default value
for ( int i = 0; i < m_configurableProperties.size(); i++ )
{
DirectoryProperty prop = ( DirectoryProperty ) m_configurableProperties.get( i );
if ( configuration.get( prop.getName() ) == null )
{
reconfigureProperty( prop, prop.getDefaultValue() );
}
}
// Complex typed property change notification detection, and setter execution
String innerReconfTarget = ( String ) configuration.get( "ads-inner-reconfiguration" );
if ( innerReconfTarget != null )
{
for ( int i = 0; i < m_configurableProperties.size(); i++ )
{
DirectoryProperty prop = ( DirectoryProperty ) m_configurableProperties.get( i );
if ( prop.getName().equals( innerReconfTarget ) )
{
if ( prop.getValue().equals( configuration.get( innerReconfTarget ) ) )
{
/*
* Then above code didn't called property method.
* If it has method, we want it to be called.
*/
if ( prop.hasMethod() )
{
if ( getInstanceManager().getPojoObjects() != null )
{
prop.reset();
prop.invoke( getInstanceManager().getPojoObject() );
}
}
}
}
}
}
return nonmatchingProps;
}
/**
* Reconfigures the given property with the given value.
* This methods handles {@link org.apache.felix.ipojo.InstanceManager#onSet(Object, String, Object)}
* call and the callback invocation.
* The reconfiguration occurs only if the value changes.
* @param prop the property object to reconfigure
* @param value the new value.
*/
public void reconfigureProperty( DirectoryProperty prop, Object value )
{
if ( prop.getValue() == null || !prop.getValue().equals( value ) )
{
prop.setValue( value );
if ( prop.hasField() )
{
getInstanceManager().onSet( null, prop.getField(), prop.getValue() ); // Notify other handler of the field value change.
}
if ( prop.hasMethod() )
{
if ( getInstanceManager().getPojoObjects() != null )
{
prop.invoke( null ); // Call on all created pojo objects.
}
}
}
}
/**
* Handler createInstance method.
* This method is override to allow delayed callback invocation.
* Invokes the updated method is needed.
* @param instance : the created object
*/
public void onCreation( Object instance )
{
for ( int i = 0; i < m_configurableProperties.size(); i++ )
{
DirectoryProperty prop = ( DirectoryProperty ) m_configurableProperties.get( i );
if ( prop.hasMethod() )
{
prop.invoke( instance );
}
}
try
{
notifyUpdated( instance );
}
catch ( Throwable e )
{
error( "Cannot call the updated method : " + e.getMessage(), e );
}
}
/**
* Invokes the updated method.
* This method build the dictionary containing all valued properties,
* as well as properties propagated to the provided service handler (
* only if the propagation is enabled).
* @param instance the instance on which the callback must be called.
* If <code>null</code> the callback is called on all the existing
* object.
*/
private void notifyUpdated( Object instance )
{
if ( m_updated == null )
{
return;
}
if ( m_updated.getArguments().length == 0 )
{
// We don't have to compute the properties,
// we just call the callback.
try
{
if ( instance == null )
{
m_updated.call( new Object[0] );
}
else
{
m_updated.call( instance, new Object[0] );
}
}
catch ( Exception e )
{
error( "Cannot call the updated method " + m_updated.getMethod() + " : " + e.getMessage() );
}
return;
}
// Else we must compute the properties.
Properties props = new Properties();
for ( int i = 0; i < m_configurableProperties.size(); i++ )
{
String n = ( ( Property ) m_configurableProperties.get( i ) ).getName();
Object v = ( ( Property ) m_configurableProperties.get( i ) ).getValue();
if ( v != Property.NO_VALUE )
{
props.put( n, v );
}
}
try
{
if ( instance == null )
{
m_updated.call( new Object[]
{ props } );
}
else
{
m_updated.call( instance, new Object[]
{ props } );
}
}
catch ( Exception e )
{
error( "Cannot call the updated method " + m_updated.getMethod() + " : " + e.getMessage() );
}
}
/**
* Gets the directory component handler description.
* @return the directory component handler description.
*/
public HandlerDescription getDescription()
{
return m_description;
}
protected abstract String getHandlerName();
protected abstract String getHandlerNamespaceName();
protected abstract Hashtable<String, String> extractConstantProperties( Element ipojoMetadata );
}