/* | |
* 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.felix.ipojo.handler.properties; | |
import java.io.File; | |
import java.io.FileInputStream; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.util.Dictionary; | |
import java.util.Enumeration; | |
import java.util.Properties; | |
import org.apache.felix.ipojo.ComponentInstance; | |
import org.apache.felix.ipojo.ConfigurationException; | |
import org.apache.felix.ipojo.PrimitiveHandler; | |
import org.apache.felix.ipojo.annotations.Handler; | |
import org.apache.felix.ipojo.architecture.HandlerDescription; | |
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.PojoMetadata; | |
/** | |
* This handler load a properties file containing property value. | |
* The handler injects this values inside fields. When stopped the handler stores updated value inside the file. The | |
* properties file contains <pre>field-name : field-value</pre> (field-value are strings) | |
* | |
* Instances can override file locations by setting the {@literal properties.file} property. | |
* | |
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> | |
*/ | |
@Handler(name="properties", namespace = PropertiesHandler.NAMESPACE) | |
public class PropertiesHandler extends PrimitiveHandler { | |
/** | |
* The Handler namespace. | |
*/ | |
public static final String NAMESPACE = "org.apache.felix.ipojo.handler.properties"; | |
/** | |
* The loaded properties. | |
*/ | |
private Properties m_properties = new Properties(); | |
/** | |
* The properties file location, configured in the component's metadata. | |
*/ | |
private String m_file; | |
/** | |
* This method is the first to be invoked. | |
* This method aims to configure the handler. It receives the component type metadata and the instance | |
* configuration. The method parses given metadata and registers fields to inject. | |
* | |
* Step 3 : when the instance configuration contains the properties.file property, it overrides the properties file location. | |
* | |
* @param metadata : component type metadata | |
* @param configuration : instance description | |
* @throws ConfigurationException : the configuration of the handler has failed. | |
*/ | |
@SuppressWarnings("unchecked") | |
public void configure(Element metadata, Dictionary configuration) throws ConfigurationException { | |
// Parse metadata to get <properties file="$file"/> | |
// Get all elements to configure the handler | |
Element[] elem = metadata.getElements("properties", NAMESPACE); | |
switch (elem.length) { | |
case 0: | |
// No matching element in metadata, throw a configuration error. | |
// It actually happen only if you force the handler to be plugged. | |
throw new ConfigurationException("No properties found"); | |
case 1: | |
// One 'properties' found, get attributes. | |
m_file = elem[0].getAttribute("file"); | |
if (m_file == null) { | |
// if file is null, throw a configuration error. | |
throw new ConfigurationException("Malformed properties element : file attribute must be set"); | |
} | |
break; | |
default: | |
// To simplify we handle only one properties element. | |
throw new ConfigurationException("Only one properties element is supported"); | |
} | |
// Look if the instance overrides file location : | |
String instanceFile = (String) configuration.get("properties.file"); | |
if (instanceFile != null) { | |
m_file = instanceFile; | |
} | |
// Load properties | |
try { | |
loadProperties(); | |
} catch (IOException e) { | |
throw new ConfigurationException("Error when reading the " + m_file + " file : " + e.getMessage()); | |
} | |
// Register fields | |
// By convention, properties file entry are field name, so look for each property to get field list. | |
//First get Pojo Metadata metadata : | |
PojoMetadata pojoMeta = getPojoMetadata(); | |
Enumeration e = m_properties.keys(); | |
while (e.hasMoreElements()) { | |
String field = (String) e.nextElement(); | |
FieldMetadata fm = pojoMeta.getField(field); | |
if (fm == null) { // The field does not exist | |
throw new ConfigurationException("The field " + field + " is declared in the properties file but does not exist in the pojo"); | |
} | |
// Then check that the field is a String field | |
if (!fm.getFieldType().equals(String.class.getName())) { | |
throw new ConfigurationException("The field " + field + " exists in the pojo, but is not a String"); | |
} | |
// All checks are ok, register the interceptor. | |
getInstanceManager().register(fm, this); | |
} | |
// Finally register the field to listen | |
} | |
/** | |
* This method is called when the instance start (after the configure method). We just print stored properties. | |
* @see org.apache.felix.ipojo.Handler#start() | |
*/ | |
public void start() { | |
// The properties are already loaded (in the configure method), just print values. | |
m_properties.list(System.out); | |
} | |
/** | |
* This method is called when the instance stops. We save the properties to not lost the instance state and clear the stored properties. | |
* @see org.apache.felix.ipojo.Handler#stop() | |
*/ | |
public void stop() { | |
try { | |
saveProperties(); | |
} catch (IOException e) { | |
error("Cannot read the file : " + m_file, e); // Log an error message by using the iPOJO logger | |
} | |
m_properties = null; | |
} | |
/** | |
* This method is called at each time the pojo 'get' a listened field. The method return the stored value. | |
* @param pojo : pojo object getting the field | |
* @param field : field name. | |
* @param o : previous value. | |
* @return the stored value. | |
*/ | |
public Object onGet(Object pojo, String field, Object o) { | |
// When the pojo requires a value for a managed field, this method is invoked. | |
// So, we have just to return the stored value. | |
return m_properties.get(field); | |
} | |
/** | |
* This method is called at each time the pojo 'set' a listened field. This method updates the local properties. | |
* @param pojo : pojo object setting the field | |
* @param field : field name | |
* @param newvalue : new value | |
*/ | |
public void onSet(Object pojo, String field, Object newvalue) { | |
// When the pojo set a value to a managed field, this method is invoked. | |
// So, we update the stored value. | |
m_properties.put(field, newvalue); | |
} | |
/** | |
* Step 2 : state properties when the instance becomes invalid. | |
* @param newState : the instance state | |
* @see org.apache.felix.ipojo.Handler#stateChanged(int) | |
*/ | |
public void stateChanged(int newState) { | |
// This method is invoked each times that the instance state changed. | |
// If the new state is invalid, save the properties. | |
if (newState == ComponentInstance.INVALID) { | |
// Reload properties | |
try { | |
saveProperties(); | |
} catch (IOException e) { | |
error("Cannot read the file : " + m_file, e); // Log an error message by using the iPOJO logger | |
} | |
} | |
} | |
/** | |
* Step 5 : dynamic reconfiguration. This method is call when the instance is reconfigured externally. The given property contains property value. | |
* @param dict : new properties | |
* @see org.apache.felix.ipojo.Handler#reconfigure(java.util.Dictionary) | |
*/ | |
@SuppressWarnings("unchecked") | |
public synchronized void reconfigure(Dictionary dict) { | |
// For each property, look if a new value is contained in the new configuration. | |
Enumeration e = m_properties.keys(); | |
while (e.hasMoreElements()) { | |
String field = (String) e.nextElement(); | |
String value = (String) dict.get(field); | |
// If the dictionary contains a value, update the stored value. | |
if (value != null) { | |
m_properties.put(field, value); | |
} | |
} | |
} | |
/** | |
* Returns handler description. | |
* @return the handler description. | |
* @see org.apache.felix.ipojo.Handler#getDescription() | |
*/ | |
public HandlerDescription getDescription() { | |
return new Description(this); | |
} | |
/** | |
* Helper method just loading the properties. | |
* @throws IOException : the file cannot be read. | |
*/ | |
private void loadProperties() throws IOException { | |
// Load the properties file from file system | |
File file = new File(m_file); | |
InputStream is = new FileInputStream(file); | |
m_properties.load(is); | |
} | |
/** | |
* Helper method writing properties. | |
* @throws IOException : the file cannot be written. | |
*/ | |
private void saveProperties() throws IOException { | |
// Store the file, modified the last modification date. | |
File file = new File(m_file); | |
OutputStream os = new FileOutputStream(file); | |
m_properties.store(os, ""); | |
} | |
/** | |
* Step 3 : The handler will participate to the instance architecture. | |
* This class describing the handler. | |
*/ | |
private class Description extends HandlerDescription { | |
/** | |
* Instantiates a new description. | |
* @param h the h | |
*/ | |
public Description(PrimitiveHandler h) { | |
super(h); | |
} | |
/** | |
* This method must return the Element describing the handler. The description of this handler contains the list of properties with attached | |
* value. | |
* @return the description of the handler. | |
* @see org.apache.felix.ipojo.architecture.HandlerDescription#getHandlerInfo() | |
*/ | |
@SuppressWarnings("unchecked") | |
public Element getHandlerInfo() { | |
Element elem = super.getHandlerInfo(); // This method must be called to get the root description element. | |
Enumeration e = m_properties.keys(); | |
while (e.hasMoreElements()) { | |
String field = (String) e.nextElement(); | |
Element prop = new Element("property", ""); // Create an element for the actual property. | |
// Add two attribute (the field and the value). | |
prop.addAttribute(new Attribute("field", field)); | |
prop.addAttribute(new Attribute("value", (String) m_properties.get(field))); | |
elem.addElement(prop); // Attach the current element to the root element. | |
} | |
return elem; | |
} | |
} | |
} |