All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.XmlFile Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *
 * Copyright (c) 2004-2009 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * 
 *    Kohsuke Kawaguchi
 *
 *
 *******************************************************************************/ 

package hudson;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.io.StreamException;
import com.thoughtworks.xstream.io.xml.XppReader;
import hudson.model.Descriptor;
import hudson.util.AtomicFileWriter;
import hudson.util.IOException2;
import hudson.util.XStream2;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.Locator2;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.io.StringWriter;
import java.util.logging.Logger;

/**
 * Represents an XML data file that Hudson uses as a data file.
 *
 *
 * 

Evolving data format

Changing data format requires a particular * care so that users with the old data format can migrate to the newer data * format smoothly. * *

Adding a field is the easiest. When you read an old XML that does not * have any data, the newly added field is left to the VM-default value (if you * let XStream create the object, such as {@link #read()} — which is the * majority), or to the value initialized by the constructor (if the object is * created via new and then its value filled by XStream, such as * {@link #unmarshal(Object)}.) * *

Removing a field requires that you actually leave the field with * transient keyword. When you read the old XML, XStream will set the * value to this field. But when the data is saved, the field will no longer * will be written back to XML. (It might be possible to tweak XStream so that * we can simply remove fields from the class. Any help appreciated.) * *

Changing the data structure is usually a combination of the two above. * You'd leave the old data store with transient, and then add the new * data. When you are reading the old XML, only the old field will be set. When * you are reading the new XML, only the new field will be set. You'll then need * to alter the code so that it will be able to correctly handle both * situations, and that as soon as you see data in the old field, you'll have to * convert that into the new data structure, so that the next save * operation will write the new data (otherwise you'll end up losing the data, * because old fields will be never written back.) * *

In some limited cases (specifically when the class is the root object to * be read from XML, such as {@link Descriptor}), it is possible to completely * and drastically change the data format. See {@link Descriptor#load()} for * more about this technique. * *

There's a few other possibilities, such as implementing a custom * {@link Converter} for XStream, or * {@link XStream#alias(String, Class) registering an alias}. * * @author Kohsuke Kawaguchi */ public final class XmlFile { private final XStream xs; private final File file; public XmlFile(File file) { this(DEFAULT_XSTREAM, file); } public XmlFile(XStream xs, File file) { this.xs = xs; this.file = file; } public File getFile() { return file; } /** * Loads the contents of this file into a new object. */ public Object read() throws IOException { LOGGER.fine("Reading " + file); Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); try { return xs.fromXML(r); } catch (StreamException e) { throw new IOException2("Unable to read " + file, e); } catch (ConversionException e) { throw new IOException2("Unable to read " + file, e); } catch (Error e) {// mostly reflection errors throw new IOException2("Unable to read " + file, e); } finally { r.close(); } } /** * Loads the contents of this file into an existing object. * * @return The unmarshalled object. Usually the same as o, but * would be different if the XML representation is completely new. */ public Object unmarshal(Object o) throws IOException { Reader r = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); try { return xs.unmarshal(new XppReader(r), o); } catch (StreamException e) { throw new IOException2("Unable to read " + file, e); } catch (ConversionException e) { throw new IOException2("Unable to read " + file, e); } catch (Error e) {// mostly reflection errors throw new IOException2("Unable to read " + file, e); } finally { r.close(); } } public void write(Object o) throws IOException { mkdirs(); AtomicFileWriter w = new AtomicFileWriter(file); try { w.write("\n"); xs.toXML(o, w); w.commit(); } catch (StreamException e) { throw new IOException2(e); } finally { w.abort(); } } public boolean exists() { return file.exists(); } public void delete() { file.delete(); } /** * Delete with return. * @since 3.2.0 * @return true iff file is deleted */ public boolean doDelete() { return file.delete(); } public void mkdirs() { file.getParentFile().mkdirs(); } @Override public String toString() { return file.toString(); } /** * Opens a {@link Reader} that loads XML. This method uses * {@link #sniffEncoding() the right encoding}, not just the system default * encoding. * * @deprecated Should not be loading XML content using a character stream. */ @Deprecated public Reader readRaw() throws IOException { return new InputStreamReader(new FileInputStream(file), sniffEncoding()); } /** * Returns the XML file read as a string. * * @deprecated Should not be loading XML content using a character stream. */ @Deprecated public String asString() throws IOException { StringWriter w = new StringWriter(); writeRawTo(w); return w.toString(); } /** * Writes the raw XML to the given {@link Writer}. Writer will not be closed * by the implementation. * * @deprecated Safer to use {@link #writeRawTo(OutputStream)}. */ @Deprecated public void writeRawTo(Writer w) throws IOException { Reader r = readRaw(); try { Util.copyStream(r, w); } finally { r.close(); } } /** * Writes the raw XML to the given {@link OutputStream}. Stream will not be * closed by the implementation. * * @since 2.1.1 */ public void writeRawTo(OutputStream os) throws IOException { InputStream is = new FileInputStream(file); try { Util.copyStream(is, os); } finally { is.close(); } } /** * Parses the beginning of the file and determines the encoding. * * @throws IOException if failed to detect encoding. * @return always non-null. * @deprecated Should not be loading XML content using a character stream. */ @Deprecated public String sniffEncoding() throws IOException { class Eureka extends SAXException { final String encoding; public Eureka(String encoding) { this.encoding = encoding; } } try { JAXP.newSAXParser().parse(file, new DefaultHandler() { private Locator loc; @Override public void setDocumentLocator(Locator locator) { this.loc = locator; } @Override public void startDocument() throws SAXException { attempt(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { attempt(); // if we still haven't found it at the first start element, // there's something wrong. throw new Eureka(null); } private void attempt() throws Eureka { if (loc == null) { return; } if (loc instanceof Locator2) { Locator2 loc2 = (Locator2) loc; String e = loc2.getEncoding(); if (e != null) { throw new Eureka(e); } } } }); // can't reach here throw new AssertionError(); } catch (Eureka e) { if (e.encoding == null) { throw new IOException("Failed to detect encoding of " + file); } return e.encoding; } catch (SAXException e) { throw new IOException2("Failed to detect encoding of " + file, e); } catch (ParserConfigurationException e) { throw new AssertionError(e); // impossible } } /** * {@link XStream} instance is supposed to be thread-safe. */ public static final XStream DEFAULT_XSTREAM = new XStream2(); private static final Logger LOGGER = Logger.getLogger(XmlFile.class.getName()); private static final SAXParserFactory JAXP = SAXParserFactory.newInstance(); static { JAXP.setNamespaceAware(true); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy