File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\XmlFile.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson;
25
26 import com.thoughtworks.xstream.XStream;
27 import com.thoughtworks.xstream.converters.ConversionException;
28 import com.thoughtworks.xstream.converters.Converter;
29 import com.thoughtworks.xstream.io.StreamException;
30 import com.thoughtworks.xstream.io.xml.XppDriver;
31 import com.thoughtworks.xstream.io.xml.XppReader;
32 import hudson.model.Descriptor;
33 import hudson.util.AtomicFileWriter;
34 import hudson.util.IOException2;
35 import hudson.util.IOUtils;
36 import hudson.util.XStream2;
37 import org.xml.sax.Attributes;
38 import org.xml.sax.InputSource;
39 import org.xml.sax.Locator;
40 import org.xml.sax.SAXException;
41 import org.xml.sax.ext.Locator2;
42 import org.xml.sax.helpers.DefaultHandler;
43
44 import javax.xml.parsers.ParserConfigurationException;
45 import javax.xml.parsers.SAXParserFactory;
46 import java.io.BufferedInputStream;
47 import java.io.File;
48 import java.io.FileInputStream;
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.InputStreamReader;
52 import java.io.Reader;
53 import java.io.Writer;
54 import java.io.StringWriter;
55 import java.util.logging.Level;
56 import java.util.logging.Logger;
57
58 /**
59 * Represents an XML data file that Jenkins uses as a data file.
60 *
61 *
62 * <h2>Evolving data format</h2>
63 * <p>
64 * Changing data format requires a particular care so that users with
65 * the old data format can migrate to the newer data format smoothly.
66 *
67 * <p>
68 * Adding a field is the easiest. When you read an old XML that does
69 * not have any data, the newly added field is left to the VM-default
70 * value (if you let XStream create the object, such as
71 * {@link #read()} &mdash; which is the majority), or to the value initialized by the
72 * constructor (if the object is created via <tt>new</tt> and then its
73 * value filled by XStream, such as {@link #unmarshal(Object)}.)
74 *
75 * <p>
76 * Removing a field requires that you actually leave the field with
77 * <tt>transient</tt> keyword. When you read the old XML, XStream
78 * will set the value to this field. But when the data is saved,
79 * the field will no longer will be written back to XML.
80 * (It might be possible to tweak XStream so that we can simply
81 * remove fields from the class. Any help appreciated.)
82 *
83 * <p>
84 * Changing the data structure is usually a combination of the two
85 * above. You'd leave the old data store with <tt>transient</tt>,
86 * and then add the new data. When you are reading the old XML,
87 * only the old field will be set. When you are reading the new XML,
88 * only the new field will be set. You'll then need to alter the code
89 * so that it will be able to correctly handle both situations,
90 * and that as soon as you see data in the old field, you'll have to convert
91 * that into the new data structure, so that the next <tt>save</tt> operation
92 * will write the new data (otherwise you'll end up losing the data, because
93 * old fields will be never written back.)
94 *
95 * <p>
96 * In some limited cases (specifically when the class is the root object
97 * to be read from XML, such as {@link Descriptor}), it is possible
98 * to completely and drastically change the data format. See
99 * {@link Descriptor#load()} for more about this technique.
100 *
101 * <p>
102 * There's a few other possibilities, such as implementing a custom
103 * {@link Converter} for XStream, or {@link XStream#alias(String, Class) registering an alias}.
104 *
105 * @author Kohsuke Kawaguchi
106 */
107 public final class XmlFile {
108 private final XStream xs;
109 private final File file;
110
111 public XmlFile(File file) {
112 this(DEFAULT_XSTREAM,file);
113 }
114
115 public XmlFile(XStream xs, File file) {
116 this.xs = xs;
117 this.file = file;
118 }
119
120 public File getFile() {
121 return file;
122 }
123
124 public XStream getXStream() {
125 return xs;
126 }
127
128 /**
129 * Loads the contents of this file into a new object.
130 */
131 public Object read() throws IOException {
132 if (LOGGER.isLoggable(Level.FINE)) {
133 LOGGER.fine("Reading "+file);
134 }
135 InputStream in = new BufferedInputStream(new FileInputStream(file));
136 try {
137 return xs.fromXML(in);
138 } catch(StreamException e) {
139 throw new IOException2("Unable to read "+file,e);
140 } catch(ConversionException e) {
141 throw new IOException2("Unable to read "+file,e);
142 } catch(Error e) {// mostly reflection errors
143 throw new IOException2("Unable to read "+file,e);
144 } finally {
145 in.close();
146 }
147 }
148
149 /**
150 * Loads the contents of this file into an existing object.
151 *
152 * @return
153 * The unmarshalled object. Usually the same as <tt>o</tt>, but would be different
154 * if the XML representation is completely new.
155 */
156 public Object unmarshal( Object o ) throws IOException {
157 InputStream in = new BufferedInputStream(new FileInputStream(file));
158 try {
159 // TODO: expose XStream the driver from XStream
160 return xs.unmarshal(DEFAULT_DRIVER.createReader(in), o);
161 } catch (StreamException e) {
162 throw new IOException2("Unable to read "+file,e);
163 } catch(ConversionException e) {
164 throw new IOException2("Unable to read "+file,e);
165 } catch(Error e) {// mostly reflection errors
166 throw new IOException2("Unable to read "+file,e);
167 } finally {
168 in.close();
169 }
170 }
171
172 public void write( Object o ) throws IOException {
173 mkdirs();
174 AtomicFileWriter w = new AtomicFileWriter(file);
175 try {
176 w.write("<?xml version='1.0' encoding='UTF-8'?>\n");
177 xs.toXML(o,w);
178 w.commit();
179 } catch(StreamException e) {
180 throw new IOException2(e);
181 } finally {
182 w.abort();
183 }
184 }
185
186 public boolean exists() {
187 return file.exists();
188 }
189
190 public void delete() {
191 file.delete();
192 }
193
194 public void mkdirs() {
195 file.getParentFile().mkdirs();
196 }
197
198 @Override
199 public String toString() {
200 return file.toString();
201 }
202
203 /**
204 * Opens a {@link Reader} that loads XML.
205 * This method uses {@link #sniffEncoding() the right encoding},
206 * not just the system default encoding.
207 */
208 public Reader readRaw() throws IOException {
209 return new InputStreamReader(new FileInputStream(file),sniffEncoding());
210 }
211
212 /**
213 * Returns the XML file read as a string.
214 */
215 public String asString() throws IOException {
216 StringWriter w = new StringWriter();
217 writeRawTo(w);
218 return w.toString();
219 }
220
221 /**
222 * Writes the raw XML to the given {@link Writer}.
223 * Writer will not be closed by the implementation.
224 */
225 public void writeRawTo(Writer w) throws IOException {
226 Reader r = readRaw();
227 try {
228 Util.copyStream(r,w);
229 } finally {
230 r.close();
231 }
232 }
233
234 /**
235 * Parses the beginning of the file and determines the encoding.
236 *
237 * @throws IOException
238 * if failed to detect encoding.
239 * @return
240 * always non-null.
241 */
242 public String sniffEncoding() throws IOException {
243 class Eureka extends SAXException {
244 final String encoding;
245 public Eureka(String encoding) {
246 this.encoding = encoding;
247 }
248 }
249 InputSource input = new InputSource(file.toURI().toASCIIString());
250 input.setByteStream(new FileInputStream(file));
251
252 try {
253 JAXP.newSAXParser().parse(input,new DefaultHandler() {
254 private Locator loc;
255 @Override
256 public void setDocumentLocator(Locator locator) {
257 this.loc = locator;
258 }
259
260 @Override
261 public void startDocument() throws SAXException {
262 attempt();
263 }
264
265 @Override
266 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
267 attempt();
268 // if we still haven't found it at the first start element, then we are not going to find it.
269 throw new Eureka(null);
270 }
271
272 private void attempt() throws Eureka {
273 if(loc==null) return;
274 if (loc instanceof Locator2) {
275 Locator2 loc2 = (Locator2) loc;
276 String e = loc2.getEncoding();
277 if(e!=null)
278 throw new Eureka(e);
279 }
280 }
281 });
282 // can't reach here
283 throw new AssertionError();
284 } catch (Eureka e) {
285 if(e.encoding!=null)
286 return e.encoding;
287 // the environment can contain old version of Xerces and others that do not support Locator2
288 // in such a case, assume UTF-8 rather than fail, since Jenkins internally always write XML in UTF-8
289 return "UTF-8";
290 } catch (SAXException e) {
291 throw new IOException2("Failed to detect encoding of "+file,e);
292 } catch (ParserConfigurationException e) {
293 throw new AssertionError(e); // impossible
294 } finally {
295 // some JAXP implementations appear to leak the file handle if we just call parse(File,DefaultHandler)
296 input.getByteStream().close();
297 }
298 }
299
300 /**
301 * {@link XStream} instance is supposed to be thread-safe.
302 */
303 private static final XStream DEFAULT_XSTREAM = new XStream2();
304
305 private static final Logger LOGGER = Logger.getLogger(XmlFile.class.getName());
306
307 private static final SAXParserFactory JAXP = SAXParserFactory.newInstance();
308
309 private static final XppDriver DEFAULT_DRIVER = new XppDriver();
310
311 static {
312 JAXP.setNamespaceAware(true);
313 }
314 }
315