oracle.kv.impl.param.LoadParameters Maven / Gradle / Ivy
/*-
* Copyright (C) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle NoSQL
* Database made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/nosqldb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle NoSQL Database for a copy of the license and
* additional information.
*/
package oracle.kv.impl.param;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import oracle.kv.impl.security.util.FileSysUtils;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
/**
* Manage and bundle up a set of ParameterMaps. Used to transport multiple
* ParameterMaps across the network, and to load parameters from a
* configuration file. Note that each map must have a name and a type associated
* with it.
*
* The config.xml represents a simple"schema""
*
*
*
*
* ...
*
*
* ...
*
* ...
*
*
* The bundle contains at most one map with a given name.
*/
public class LoadParameters implements Serializable {
private static final long serialVersionUID = 1L;
/* This persisted field is not used at runtime. */
private List maps;
private int version;
/**
* Parameters are kept in a sorted map while in memory. We sort just to
* aid navigation of the configuration file. The parameter maps
* are persisted via the maps field for compatibility.
*/
private transient SortedMap parameterMaps;
/**
* Read parameters by type from file. Also check if file is writable,
* if so, attempts to set file as read only.
*/
public static synchronized
LoadParameters getParameters(File file, Logger logger) {
final LoadParameters lp = new LoadParameters();
lp.load(file, false, true, true, logger);
return lp;
}
/**
* Read parameters by type from file. Also check if file is writable,
* if so, attempts to set file as read only.
*/
public static synchronized LoadParameters getParametersByType(File file) {
return getParametersByType(file, true, null);
}
/**
* Read parameters by type from file. Also check if file is writable,
* if so, attempts to set file as read only.
*/
public static synchronized
LoadParameters getParametersByType(File file, Logger logger) {
final LoadParameters lp = new LoadParameters();
lp.load(file, true, true, true, logger);
return lp;
}
public LoadParameters() {
parameterMaps = new TreeMap();
version = ParameterState.PARAMETER_VERSION;
}
public static synchronized LoadParameters getParameters(File file,
boolean checkReadOnly,
Logger logger) {
final LoadParameters lp = new LoadParameters();
lp.load(file, false, true, checkReadOnly, logger);
return lp;
}
public static synchronized LoadParameters getParametersByType(File file,
boolean checkReadOnly) {
return getParametersByType(file, checkReadOnly, null);
}
public static synchronized LoadParameters getParametersByType(File file,
boolean checkReadOnly,
Logger logger) {
final LoadParameters lp = new LoadParameters();
lp.load(file, true, true, checkReadOnly, logger);
return lp;
}
/**
* Save parameters to given file and set this file read only.
*/
public void saveParameters(File file) {
saveParameters(file, true, null /* logger */);
}
/**
* Save parameters to given file and set this file read only.
*/
public void saveParameters(File file, Logger logger) {
synchronized(this.getClass()) {
saveParameters(file, true, logger);
}
}
/**
* In order to make this modification more atomic, write first to a
* temporary file and rename. Because of file system semantics on
* some platforms the rename can fail. Handle that with a retry.
* This will only fail if there is a reader. Writers must be
* synchronized by the callers (e.g. the SNA).
* @param readonly if set this file read only. Ignore the error of
* setting file permission.
*/
public void saveParameters(File file, boolean readonly, Logger logger) {
PrintWriter writer = null;
File temp = null;
try {
temp = File.createTempFile(file.getName(), null,
file.getParentFile());
final FileOutputStream fos = new FileOutputStream(temp);
writer = new PrintWriter(fos);
writer.print(
"\n");
writer.printf("\n", version);
for (ParameterMap map : parameterMaps.values()) {
map.write(writer);
}
writer.printf(" \n");
} catch (Exception e) {
throw new IllegalStateException("Problem creating config file: " +
temp + ": " + e);
} finally {
if (writer != null) {
writer.close();
}
}
if (temp != null) {
final int nretries = 5;
final int sleepMs = 200;
for (int i = 0; i < nretries; i++) {
if (temp.renameTo(file)) {
if (readonly) {
if (makeReadAccessOnly(file)) {
return;
}
logMessage(logger, Level.FINE,
"Unable to set config file " + file +
" read only");
}
return;
}
/**
* It seems that on Windows renameTo() often fails without a
* delete. This shows up in tests, at least. This is not a
* high performance path, so if it fails once, do the explicit
* delete on Windows. This should not be unconditional because
* it makes the operation less atomic, leaving a small window
* available for a read failure.
*/
final String os = System.getProperty("os.name");
if (os.indexOf("Windows") != -1) {
file.delete();
}
try {
Thread.sleep(sleepMs);
} catch (Exception ignored) {
}
}
}
throw new IllegalStateException("Unable to save config file: " + file);
}
List getMaps() {
return new ArrayList(parameterMaps.values());
}
/**
* Adds the specified map if not null. If a map with the same name already
* exist it is replaced. The map must have a non-null name and type.
*/
public void addMap(ParameterMap map) {
if (map == null) {
return;
}
if ((map.getName() == null) || (map.getType() == null)) {
throw new IllegalArgumentException("ParameterMap must have a " +
"name and type");
}
parameterMaps.put(map.getName(), map);
}
/**
* Removes maps with the specified name. If a map was removed it is
* returned, otherwise null is returned.
*/
public ParameterMap removeMap(String name) {
return parameterMaps.remove(name);
}
/**
* Removes all maps with the specified type. If no maps are removed null
* is returned. If one or more map is removed there is no guarantee which
* map is returned.
*/
public ParameterMap removeMapByType(String type) {
ParameterMap map = null;
final Iterator itr = parameterMaps.values().iterator();
while (itr.hasNext()) {
final ParameterMap canidate = itr.next();
if (type.equals(canidate.getType())) {
itr.remove();
map = canidate;
}
}
return map;
}
/**
* Gets the parameter map with the specified name and type. If no map
* matches null is returned.
*/
public ParameterMap getMap(String name, String type) {
final ParameterMap map = parameterMaps.get(name);
if ((map != null) && type.equals(map.getType())) {
return map;
}
return null;
}
/**
* Gets a map of the specified type. If no map matches null is returned.
* If more than one map of the same type is present, there is no guarantee
* on which is returned.
*/
public ParameterMap getMapByType(String type) {
for (ParameterMap map : parameterMaps.values()) {
if (type.equals(map.getType())) {
return map;
}
}
return null;
}
public List getAllMaps(String type) {
final ArrayList list = new ArrayList();
for (ParameterMap map : parameterMaps.values()) {
if (type.equals(map.getType())) {
list.add(map);
}
}
return list;
}
/**
* Returns the map with the specified name. If no map is found null is
* returned.
*/
public ParameterMap getMap(String name) {
return parameterMaps.get(name);
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
/**
* Given an abstract file, attempt to change permissions to remove all
* write access.
* @param f a File referencing a file or directory on which permissions are
* to be changed.
* @return true if the permissions were successfully changed
*/
public static boolean makeReadAccessOnly(File f) {
if (!f.exists()) {
return false;
}
/* We don't support setting file permission on windows.
* To allow user doing experiment on Windows, only set
* file read only on Unix-based OS.
*/
final String os = System.getProperty("os.name");
if (os.indexOf("Windows") >= 0) {
return true;
}
final FileSysUtils.Operations osOps =
FileSysUtils.selectOsOperations();
try {
return osOps.makeReadAccessOnly(f);
} catch (IOException ioe) {
return false;
}
}
private void load(File file, boolean useTypes, boolean ignoreUnknown,
boolean checkReadOnly, Logger logger) {
InputStream is = null;
try {
/*
* Check if configuration file is read only, which might not be
* read only because upgrading from previous version or users
* changed the file permission. Attempts to change it to read only,
* ignoring the failure here to make sure parameters can be loaded.
*/
if (checkReadOnly && !file.isDirectory() && file.canWrite()) {
if (!makeReadAccessOnly(file)) {
logMessage(logger, Level.FINE,
"Reading a config file " + file +
", attempts to change it as read only but fail");
}
}
final URL url = file.toURI().toURL();
final SAXParserFactory factory = SAXParserFactory.newInstance();
final XMLReader xr = factory.newSAXParser().getXMLReader();
final ConfigHandler handler =
new ConfigHandler(useTypes, ignoreUnknown, this, logger);
xr.setContentHandler(handler);
xr.setErrorHandler(handler);
is = url.openStream();
xr.parse(new InputSource(is));
} catch (SAXParseException e) {
final String msg = "Error while parsing line " + e.getLineNumber() +
" of " + file + ": " + e.getMessage();
throw new IllegalStateException(msg);
} catch (SAXException e) {
throw new IllegalStateException("Problem with XML: " + e);
} catch (ParserConfigurationException e) {
throw new IllegalStateException(e.getMessage());
} catch (MalformedURLException me) {
throw new IllegalStateException
("Could not translate file to URL: " + file);
} catch (IOException io) {
throw new IllegalStateException
("IOException parsing file: " + file + ": " + io);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ignored) {
}
}
}
}
private void logMessage(Logger logger, Level level, String msg) {
if (logger != null) {
logger.log(level, msg);
}
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();
parameterMaps = new TreeMap();
if (maps == null) {
return;
}
for (ParameterMap map : maps) {
parameterMaps.put(map.getName(), map);
}
maps = null;
}
private void writeObject(ObjectOutputStream out)
throws IOException {
maps = getMaps();
out.defaultWriteObject();
maps = null;
}
private static class ConfigHandler extends DefaultHandler {
Locator locator;
ParameterMap curMap;
final boolean useTypes;
final boolean ignoreUnknown;
final Logger logger;
final LoadParameters lp;
private ConfigHandler(boolean useTypes,
boolean ignoreUnknown,
LoadParameters lp,
Logger logger) {
this.logger = logger;
this.useTypes = useTypes;
this.ignoreUnknown = ignoreUnknown;
this.lp = lp;
}
@Override
public void startElement(String uri,
String localName,
String qName,
Attributes attributes)
throws SAXException {
if (qName.equals("config")) {
/* get version */
final String stringVersion = attributes.getValue("version");
if (stringVersion == null) {
throw new SAXParseException
("config element must specify version",
locator);
}
lp.setVersion(Integer.parseInt(stringVersion));
} else if (qName.equals("component")) {
final String curComponent = attributes.getValue("name");
final String curType = attributes.getValue("type");
final String validateString = attributes.getValue("validate");
boolean validate = true;
if (validateString != null) {
validate = Boolean.parseBoolean(validateString);
}
curMap = new ParameterMap(curComponent, curType,
validate, lp.getVersion());
/* Check for a badly formed component tag. */
if (curComponent == null || curType == null) {
throw new SAXParseException
("component element must specify name and type",
locator);
}
} else if (qName.equals("property")) {
final String name = attributes.getValue("name");
final String value = attributes.getValue("value");
if (attributes.getLength() < 2 ||
attributes.getLength() > 3 ||
name == null ||
value == null) {
throw new SAXParseException
("property element must only have 'name', 'value'" +
" and (optional) 'type' attributes", locator);
}
if (curMap == null) {
throw new SAXParseException
("property elements are not allowed at global scope",
locator);
}
if (curMap.exists(name)) {
throw new SAXParseException
("Duplicate property: " + name, locator);
}
final String type = attributes.getValue("type");
Parameter param = null;
if (useTypes) {
if (type == null) {
throw new SAXParseException
("type attribute required on property element in" +
" this path", locator);
}
param = Parameter.createKnownType(name, value, type);
if (param != null) {
curMap.put(param);
} else if (logger != null) {
logger.warning("Could not create parameter: " +
name);
}
} else {
if (!curMap.setParameter(name, value, type,
ignoreUnknown)) {
if (logger != null) {
logger.warning("Ignoring unknown parameter: " +
name);
}
}
}
} else {
throw new SAXParseException
("Unknown element '" + qName + "'", locator);
}
}
@Override
public void characters(char ch[], int start, int length)
throws SAXParseException {
/* ignore content and whitespace */
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXParseException {
if (qName.equals("component")) {
lp.addMap(curMap);
curMap = null;
} else if (qName.equals("property")) {
/* nothing to do */
}
}
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy