/**
*
* Copyright 2003-2004 The Apache Software Foundation
*
* Licensed 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.geronimo.system.configuration;
import java.beans.PropertyEditor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.common.propertyeditor.PropertyEditors;
import org.apache.geronimo.gbean.GAttributeInfo;
import org.apache.geronimo.gbean.GBeanData;
import org.apache.geronimo.gbean.GBeanInfo;
import org.apache.geronimo.gbean.GBeanInfoBuilder;
import org.apache.geronimo.gbean.GBeanLifecycle;
import org.apache.geronimo.gbean.GReferenceInfo;
import org.apache.geronimo.kernel.config.InvalidConfigException;
import org.apache.geronimo.kernel.config.ManageableAttributeStore;
import org.apache.geronimo.kernel.config.PersistentConfigurationList;
import org.apache.geronimo.system.serverinfo.ServerInfo;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Stores managed attributes in an XML file on the local filesystem.
*
* @version $Rev$ $Date$
*/
public class LocalAttributeManager implements ManageableAttributeStore, PersistentConfigurationList, GBeanLifecycle {
private final static Log log = LogFactory.getLog(LocalAttributeManager.class);
private final static String CONFIG_FILE_PROPERTY = "org.apache.geronimo.config.file";
private final static String BACKUP_EXTENSION = ".bak";
private final static String TEMP_EXTENSION = ".working";
private final static int SAVE_BUFFER_MS = 5000;
private final ServerInfo serverInfo;
private final String configFile;
private final boolean readOnly;
private File attributeFile;
private File backupFile;
private File tempFile;
private ServerOverride serverOverride;
private Timer timer;
private TimerTask currentTask;
private boolean kernelFullyStarted;
public LocalAttributeManager(String configFile, boolean readOnly, ServerInfo serverInfo) {
this.configFile = System.getProperty(CONFIG_FILE_PROPERTY, configFile);
this.readOnly = readOnly;
this.serverInfo = serverInfo;
serverOverride = new ServerOverride();
}
public boolean isReadOnly() {
return readOnly;
}
public synchronized Collection setAttributes(URI configurationName, Collection gbeanDatas, ClassLoader classLoader) throws InvalidConfigException {
// clone the datas since we will be modifying this collection
gbeanDatas = new ArrayList(gbeanDatas);
String configName = configurationName.toString();
ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
if (configuration == null) {
return gbeanDatas;
}
// index the incoming datas
Map datasByName = new HashMap();
for (Iterator iterator = gbeanDatas.iterator(); iterator.hasNext();) {
GBeanData gbeanData = (GBeanData) iterator.next();
datasByName.put(gbeanData.getName(), gbeanData);
}
// add the new GBeans
for (Iterator iterator = configuration.getGBeans().entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
Object name = entry.getKey();
GBeanOverride gbean = (GBeanOverride) entry.getValue();
if (!datasByName.containsKey(name) && gbean.getGBeanInfo() != null && gbean.isLoad()) {
if (!(name instanceof ObjectName)) {
throw new InvalidConfigException("New GBeans must be specified with a full objectName:" +
" configuration=" + configName +
" gbeanName=" + name);
}
ObjectName objectName = (ObjectName) name;
GBeanInfo gbeanInfo = GBeanInfo.getGBeanInfo(gbean.getGBeanInfo(), classLoader);
GBeanData gBeanData = new GBeanData(objectName, gbeanInfo);
gbeanDatas.add(gBeanData);
}
}
// set the attributes
for (Iterator iterator = gbeanDatas.iterator(); iterator.hasNext();) {
GBeanData data = (GBeanData) iterator.next();
boolean load = setAttributes(data, configuration, configName, classLoader);
if (!load) {
iterator.remove();
}
}
return gbeanDatas;
}
/**
* Set the attributes from the attribute store on a single gbean, and return whether or not to load the gbean.
*
* @param data
* @param configuration
* @param configName
* @param classLoader
* @return true if the gbean should be loaded, false otherwise.
* @throws org.apache.geronimo.kernel.config.InvalidConfigException
*/
private synchronized boolean setAttributes(GBeanData data, ConfigurationOverride configuration, String configName, ClassLoader classLoader) throws InvalidConfigException {
ObjectName gbeanName = data.getName();
GBeanOverride gbean = configuration.getGBean(gbeanName);
if (gbean == null) {
gbean = configuration.getGBean(gbeanName.getKeyProperty("name"));
}
if (gbean == null) {
//no attr info, load by default
return true;
}
if (!gbean.isLoad()) {
return false;
}
GBeanInfo gbeanInfo = data.getGBeanInfo();
// set attributes
for (Iterator iterator = gbean.getAttributes().entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String attributeName = (String) entry.getKey();
GAttributeInfo attributeInfo = gbeanInfo.getAttribute(attributeName);
if (attributeInfo == null) {
throw new InvalidConfigException("No attribute: " + attributeName + " for gbean: " + data.getName());
}
String valueString = (String) entry.getValue();
Object value = getValue(attributeInfo, valueString, configName, gbeanName, classLoader);
data.setAttribute(attributeName, value);
}
// set references
for (Iterator iterator = gbean.getReferences().entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String referenceName = (String) entry.getKey();
GReferenceInfo referenceInfo = gbeanInfo.getReference(referenceName);
if (referenceInfo == null) {
throw new InvalidConfigException("No reference: " + referenceName + " for gbean: " + data.getName());
}
Set referencePatterns = (Set) entry.getValue();
data.setReferencePatterns(referenceName, referencePatterns);
}
return true;
}
private synchronized Object getValue(GAttributeInfo attribute, String value, String configurationName, ObjectName gbeanName, ClassLoader classLoader) {
if (value == null) {
return null;
}
try {
PropertyEditor editor = PropertyEditors.findEditor(attribute.getType(), classLoader);
if (editor == null) {
log.debug("Unable to parse attribute of type " + attribute.getType() + "; no editor found");
return null;
}
editor.setAsText(value);
log.debug("Setting value for " + configurationName + "/" + gbeanName + "/" + attribute.getName() + " to value " + value);
return editor.getValue();
} catch (ClassNotFoundException e) {
log.error("Unable to load attribute type " + attribute.getType());
return null;
}
}
public synchronized void setValue(String configurationName, ObjectName gbeanName, GAttributeInfo attribute, Object value) {
if (readOnly) {
return;
}
ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
GBeanOverride gbean = configuration.getGBean(gbeanName);
if (gbean == null) {
gbean = configuration.getGBean(gbeanName.getKeyProperty("name"));
if (gbean == null) {
gbean = new GBeanOverride(gbeanName, true);
configuration.addGBean(gbeanName, gbean);
}
}
try {
gbean.setAttribute(attribute.getName(), value, attribute.getType());
attributeChanged();
} catch (InvalidAttributeException e) {
// attribute can not be represented as a string
log.error(e.getMessage());
return;
}
}
public synchronized void setReferencePattern(String configurationName, ObjectName gbeanName, GReferenceInfo reference, ObjectName pattern) {
setReferencePatterns(configurationName, gbeanName, reference, Collections.singleton(pattern));
}
public synchronized void setReferencePatterns(String configurationName, ObjectName gbeanName, GReferenceInfo reference, Set patterns) {
if (readOnly) {
return;
}
ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
GBeanOverride gbean = configuration.getGBean(gbeanName);
if (gbean == null) {
gbean = configuration.getGBean(gbeanName.getKeyProperty("name"));
if (gbean == null) {
gbean = new GBeanOverride(gbeanName, true);
configuration.addGBean(gbeanName, gbean);
}
}
gbean.setReferencePatterns(reference.getName(), patterns);
attributeChanged();
}
public synchronized void setShouldLoad(String configurationName, ObjectName gbeanName, boolean load) {
if (readOnly) {
return;
}
ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
GBeanOverride gbean = configuration.getGBean(gbeanName);
if (gbean == null) {
// attempt to lookup by short name
gbean = configuration.getGBean(gbeanName.getKeyProperty("name"));
}
if (gbean == null) {
gbean = new GBeanOverride(gbeanName, load);
configuration.addGBean(gbeanName, gbean);
} else {
gbean.setLoad(load);
}
attributeChanged();
}
public void addGBean(String configurationName, GBeanData gbeanData) {
if (readOnly) {
return;
}
ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName);
if (configuration == null) {
log.debug("Can not add GBean; Configuration not found " + configurationName);
return;
}
try {
GBeanOverride gbean = new GBeanOverride(gbeanData);
configuration.addGBean(gbean);
attributeChanged();
} catch (InvalidAttributeException e) {
// attribute can not be represented as a string
log.error(e.getMessage());
return;
}
}
public synchronized void load() throws IOException {
ensureParentDirectory();
if (!attributeFile.exists()) {
return;
}
InputSource in = new InputSource(new FileInputStream(attributeFile));
DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
try {
Document doc = dfactory.newDocumentBuilder().parse(in);
Element root = doc.getDocumentElement();
serverOverride = new ServerOverride(root);
} catch (SAXException e) {
log.error("Unable to read saved manageable attributes", e);
} catch (ParserConfigurationException e) {
log.error("Unable to read saved manageable attributes", e);
} catch (MalformedObjectNameException e) {
log.error("Unable to read saved manageable attributes", e);
}
}
public synchronized void save() throws IOException {
if (readOnly) {
return;
}
ensureParentDirectory();
if (!tempFile.exists() && !tempFile.createNewFile()) {
throw new IOException("Unable to create manageable attribute working file for save " + tempFile.getAbsolutePath());
}
if (!tempFile.canWrite()) {
throw new IOException("Unable to write to manageable attribute working file for save " + tempFile.getAbsolutePath());
}
// write the new configuration to the temp file
PrintWriter out = new PrintWriter(new FileWriter(tempFile), true);
serverOverride.writeXml(out);
out.close();
// delete the current backup file
if (backupFile.exists()) {
if (!backupFile.delete()) {
throw new IOException("Unable to delete old backup file in order to back up current manageable attribute working file for save");
}
}
// rename the existing configuration file to the backup file
if (attributeFile.exists()) {
if (!attributeFile.renameTo(backupFile)) {
throw new IOException("Unable to rename " + attributeFile.getAbsolutePath() + " to " + backupFile.getAbsolutePath() + " in order to back up manageable attribute save file");
}
}
// rename the temp file the the configuration file
if (!tempFile.renameTo(attributeFile)) {
throw new IOException("EXTREMELY CRITICAL! Unable to move manageable attributes working file to proper file name! Configuration will revert to defaults unless this is manually corrected! (could not rename " + tempFile.getAbsolutePath() + " to " + attributeFile.getAbsolutePath() + ")");
}
}
//PersistentConfigurationList
public synchronized boolean isKernelFullyStarted() {
return kernelFullyStarted;
}
public synchronized void setKernelFullyStarted(boolean kernelFullyStarted) {
this.kernelFullyStarted = kernelFullyStarted;
}
public synchronized List restore() throws IOException {
List configs = new ArrayList();
for (Iterator iterator = serverOverride.getConfigurations().entrySet().iterator(); iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
ConfigurationOverride configuration = (ConfigurationOverride) entry.getValue();
if (configuration.isLoad()) {
String configName = (String) entry.getKey();
try {
URI configID = new URI(configName);
configs.add(configID);
} catch (URISyntaxException e) {
throw new IOException("Could not construct URI configID for " + configName);
}
}
}
return configs;
}
public synchronized void addConfiguration(String configurationName) {
ConfigurationOverride configuration = serverOverride.getConfiguration(configurationName, true);
configuration.setLoad(true);
attributeChanged();
}
public synchronized void removeConfiguration(String configName) {
ConfigurationOverride configuration = serverOverride.getConfiguration(configName);
if (configuration == null) {
log.error("Trying to stop unknown configuration: " + configName);
} else {
if (configuration.getGBeans().isEmpty()) {
serverOverride.removeConfiguration(configName);
} else {
configuration.setLoad(false);
}
attributeChanged();
}
}
//GBeanLifeCycle
public synchronized void doStart() throws Exception {
load();
if (!readOnly) {
timer = new Timer();
}
log.debug("Started LocalAttributeManager with data on " + serverOverride.getConfigurations().size() + " configurations");
}
public synchronized void doStop() throws Exception {
boolean doSave = false;
synchronized (this) {
if (timer != null) {
timer.cancel();
if (currentTask != null) {
currentTask.cancel();
doSave = true;
}
}
}
if (doSave) {
save();
}
log.debug("Stopped LocalAttributeManager with data on " + serverOverride.getConfigurations().size() + " configurations");
serverOverride = new ServerOverride();
}
public synchronized void doFail() {
synchronized (this) {
if (timer != null) {
timer.cancel();
if (currentTask != null) {
currentTask.cancel();
}
}
}
serverOverride = new ServerOverride();
}
private synchronized void ensureParentDirectory() throws IOException {
if (attributeFile == null) {
attributeFile = serverInfo.resolve(configFile);
tempFile = new File(attributeFile.getAbsolutePath() + TEMP_EXTENSION);
backupFile = new File(attributeFile.getAbsolutePath() + BACKUP_EXTENSION);
}
File parent = attributeFile.getParentFile();
if (!parent.isDirectory()) {
if (!parent.mkdirs()) {
throw new IOException("Unable to create directory for list:" + parent);
}
}
if (!parent.canRead() || !parent.canWrite()) {
throw new IOException("Unable to write manageable attribute files to directory " + parent.getAbsolutePath());
}
}
private synchronized void attributeChanged() {
if (currentTask != null) {
currentTask.cancel();
}
if (timer != null) {
currentTask = new TimerTask() {
public void run() {
try {
LocalAttributeManager.this.save();
} catch (IOException e) {
log.error("Error saving attributes", e);
}
}
};
timer.schedule(currentTask, SAVE_BUFFER_MS);
}
}
public static final GBeanInfo GBEAN_INFO;
static {
GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(LocalAttributeManager.class, "AttributeStore");
infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
infoFactory.addAttribute("configFile", String.class, true);
infoFactory.addAttribute("readOnly", boolean.class, true);
infoFactory.addInterface(ManageableAttributeStore.class);
infoFactory.addInterface(PersistentConfigurationList.class);
infoFactory.setConstructor(new String[]{"configFile", "readOnly", "ServerInfo"});
GBEAN_INFO = infoFactory.getBeanInfo();
}
public static GBeanInfo getGBeanInfo() {
return GBEAN_INFO;
}
}