blob: a3e663d329d1b1b6fac7516ce461871e2744e49e [file] [log] [blame]
/*
* Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use 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.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 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.
*/
/*
* This source code is provided to illustrate the usage of a given feature
* or technique and has been deliberately simplified. Additional steps
* required for a production-quality application, such as security checks,
* input validation and proper error handling, might not be present in
* this sample code.
*/
package com.sun.jmx.examples.scandir.config;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
/**
* The class XmlConfigUtils is used to deal with XML serialization
* and XML files.
*
* @author Sun Microsystems, 2006 - All rights reserved.
*/
public class XmlConfigUtils {
/**
* A URI for our XML configuration namespace. This doesn't start with
* http:// because we are not going to publish this private schema
* anywhere.
**/
public static final String NAMESPACE =
"jmx:com.sun.jmx.examples.scandir.config";
/**
* A logger for this class.
**/
private static final Logger LOG =
Logger.getLogger(XmlConfigUtils.class.getName());
// Our JAXBContext.
private static JAXBContext context;
// The file name of the XML file in which an instance of this object
// will read and write XML data.
final String file;
/**
* Creates a new instance of XmlConfigUtils.
* @param file The file name of the XML file in which an instance of this
* object will read and write XML data.
*/
public XmlConfigUtils(String file) {
this.file = file;
}
/**
* Write the given bean to the XML file.
* <p>
* Performs an atomic write, first writing in {@code <file>.new}, then
* renaming {@code <file>} to {@code <file>~}, then renaming
* renaming {@code <file>.new} to {@code <file>}.
* </p>
* @param bean The configuration to write in the XML file.
* @throws IOException if write to file failed.
**/
public synchronized void writeToFile(ScanManagerConfig bean)
throws IOException {
// Creates a new file named <file>.new
final File f = newXmlTmpFile(file);
try {
final FileOutputStream out = new FileOutputStream(f);
boolean failed = true;
try {
// writes to <file>.new
write(bean,out,false);
// no exception: set failed=false for finaly {} block.
failed = false;
} finally {
out.close();
// An exception was raised: delete temporary file.
if (failed == true) f.delete();
}
// rename <file> to <file>~ and <file>.new to <file>
commit(file,f);
} catch (JAXBException x) {
final IOException io =
new IOException("Failed to write SessionConfigBean to " +
file+": "+x,x);
throw io;
}
}
/**
* Creates an XML string representation of the given bean.
* @throws IllegalArgumentException if the bean class is not known by the
* underlying XMLbinding context.
* @return An XML string representation of the given bean.
**/
public static String toString(Object bean) {
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final Marshaller m = createMarshaller();
m.setProperty(m.JAXB_FRAGMENT,Boolean.TRUE);
m.marshal(bean, baos);
return baos.toString();
} catch (JAXBException x) {
final IllegalArgumentException iae =
new IllegalArgumentException(
"Failed to write SessionConfigBean: "+x,x);
throw iae;
}
}
/**
* Creates an XML clone of the given bean.
* <p>
* In other words, this method XML-serializes the given bean, and
* XML-deserializes a copy of that bean.
* </p>
* @return A deep-clone of the given bean.
* @throws IllegalArgumentException if the bean class is not known by the
* underlying XML binding context.
* @param bean The bean to clone.
*/
public static ScanManagerConfig xmlClone(ScanManagerConfig bean) {
final Object clone = copy(bean);
return (ScanManagerConfig)clone;
}
/**
* Creates an XML clone of the given bean.
* <p>
* In other words, this method XML-serializes the given bean, and
* XML-deserializes a copy of that bean.
* </p>
* @throws IllegalArgumentException if the bean class is not known by the
* underlying XML binding context.
* @return A deep-clone of the given bean.
**/
private static Object copy(Object bean) {
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final Marshaller m = createMarshaller();
m.marshal(bean, baos);
final ByteArrayInputStream bais =
new ByteArrayInputStream(baos.toByteArray());
return createUnmarshaller().unmarshal(bais);
} catch (JAXBException x) {
final IllegalArgumentException iae =
new IllegalArgumentException("Failed to write SessionConfigBean: "+x,x);
throw iae;
}
}
/**
* Creates an XML clone of the given bean.
* <p>
* In other words, this method XML-serializes the given bean, and
* XML-deserializes a copy of that bean.
* </p>
* @return A deep-clone of the given bean.
* @throws IllegalArgumentException if the bean class is not known by the
* underlying XML binding context.
* @param bean The bean to clone.
*/
public static DirectoryScannerConfig xmlClone(DirectoryScannerConfig bean) {
final Object clone = copy(bean);
return (DirectoryScannerConfig)clone;
}
/**
* Reads the configuration from the XML configuration file.
* @throws IOException if it fails to read the configuration.
* @return A {@code ScanManagerConfig} bean read from the
* XML configuration file.
**/
public synchronized ScanManagerConfig readFromFile() throws IOException {
final File f = new File(file);
if (!f.exists())
throw new IOException("No such file: "+file);
if (!f.canRead())
throw new IOException("Can't read file: "+file);
try {
return read(f);
} catch (JAXBException x) {
final IOException io =
new IOException("Failed to read SessionConfigBean from " +
file+": "+x,x);
throw io;
}
}
/**
* Reads the configuration from the given XML configuration file.
* @param f the file to read from.
* @return A {@code ScanManagerConfig} bean read from the
* XML configuration file.
* @throws javax.xml.bind.JAXBException if it fails to read the configuration.
*/
public static ScanManagerConfig read(File f)
throws JAXBException {
final Unmarshaller u = createUnmarshaller();
return (ScanManagerConfig) u.unmarshal(f);
}
/**
* Writes the given bean to the given output stream.
* @param bean the bean to write.
* @param os the output stream to write to.
* @param fragment whether the {@code <?xml ... ?>} header should be
* included. The header is not included if the bean is just an
* XML fragment encapsulated in a higher level XML element.
* @throws JAXBException An XML Binding exception occurred.
**/
public static void write(ScanManagerConfig bean, OutputStream os,
boolean fragment)
throws JAXBException {
writeXml((Object)bean,os,fragment);
}
/**
* Writes the given bean to the given output stream.
* @param bean the bean to write.
* @param os the output stream to write to.
* @param fragment whether the {@code <?xml ... ?>} header should be
* included. The header is not included if the bean is just an
* XML fragment encapsulated in a higher level XML element.
* @throws JAXBException An XML Binding exception occurred.
**/
public static void write(ResultRecord bean, OutputStream os, boolean fragment)
throws JAXBException {
writeXml((Object)bean,os,fragment);
}
/**
* Writes the given bean to the given output stream.
* @param bean the bean to write.
* @param os the output stream to write to.
* @param fragment whether the {@code <?xml ... ?>} header should be
* included. The header is not included if the bean is just an
* XML fragment encapsulated in a higher level XML element.
* @throws JAXBException An XML Binding exception occurred.
**/
private static void writeXml(Object bean, OutputStream os, boolean fragment)
throws JAXBException {
final Marshaller m = createMarshaller();
if (fragment) m.setProperty(m.JAXB_FRAGMENT,Boolean.TRUE);
m.marshal(bean,os);
}
// Creates a JAXB Unmarshaller.
private static Unmarshaller createUnmarshaller() throws JAXBException {
return getContext().createUnmarshaller();
}
// Creates a JAXB Marshaller - for nicely XML formatted output.
private static Marshaller createMarshaller() throws JAXBException {
final Marshaller m = getContext().createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
return m;
}
// Creates a JAXBContext if needed, and returns it.
// The JAXBContext instance we create will be able to handle the
// ScanManagerConfig and ResultRecord classes, plus all the property
// classes they reference (DirectoryScannerBean etc...).
//
private static synchronized JAXBContext getContext() throws JAXBException {
if (context == null)
context = JAXBContext.newInstance(ScanManagerConfig.class,
ResultRecord.class);
return context;
}
// Creates a new XML temporary file called <basename>.new
// This method is used to implement atomic writing to file.
// The usual sequence is:
//
// Final tmp = newXmlTmpFile(basename);
// boolean failed = true;
// try {
// ... write to 'tmp' ...
// // no exception: set failed=false for finaly {} block.
// failed = false;
// } finally
// // failed==true means there was an exception and
// // commit won't be called...
// if (failed==true) tmp.delete();
// }
// commit(tmp,basename)
//
private static File newXmlTmpFile(String basename) throws IOException {
final File f = new File(basename+".new");
if (!f.createNewFile())
throw new IOException("file "+f.getName()+" already exists");
try {
final OutputStream newStream = new FileOutputStream(f);
try {
final String decl =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>";
newStream.write(decl.getBytes("UTF-8"));
newStream.flush();
} finally {
newStream.close();
}
} catch (IOException x) {
f.delete();
throw x;
}
return f;
}
// Commit the temporary file by renaming <basename> to <baesname>~
// and tmpFile to <basename>.
private static File commit(String basename, File tmpFile)
throws IOException {
try {
final String backupName = basename+"~";
final File desired = new File(basename);
final File backup = new File(backupName);
backup.delete();
if (desired.exists()) {
if (!desired.renameTo(new File(backupName)))
throw new IOException("can't rename to "+backupName);
}
if (!tmpFile.renameTo(new File(basename)))
throw new IOException("can't rename to "+basename);
} catch (IOException x) {
tmpFile.delete();
throw x;
}
return new File(basename);
}
/**
* Creates a new committed XML file for {@code <basename>}, containing only
* the {@code <?xml ...?>} header.
* <p>This method will rename {@code <basename>} to {@code <basename>~},
* if it exists.
* </p>
* @return A newly created XML file containing the regular
* {@code <?xml ...?>} header.
* @param basename The name of the new file.
* @throws IOException if the new XML file couldn't be created.
*/
public static File createNewXmlFile(String basename) throws IOException {
return commit(basename,newXmlTmpFile(basename));
}
}