001: /*
002: * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
003: *
004: * http://izpack.org/
005: * http://izpack.codehaus.org/
006: *
007: * Copyright 2007 Vladimir Ralev
008: *
009: * Licensed under the Apache License, Version 2.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.apache.org/licenses/LICENSE-2.0
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: */
021:
022: package com.izforge.izpack.installer;
023:
024: import java.net.MalformedURLException;
025: import java.net.URL;
026: import java.util.*;
027: import java.io.*;
028: import com.izforge.izpack.compiler.*;
029: import com.izforge.izpack.*;
030: import com.izforge.izpack.util.*;
031:
032: import com.izforge.izpack.compiler.CompilerException;
033:
034: import net.n3.nanoxml.*;
035:
036: /**
037: *
038: * This class enumerates the availabe packs at the web repository. Parses the config files
039: * - install.xml, packsinfo.xml, langpacks and is used to override the static configuration
040: * in the installer jar.
041: *
042: * @author <a href="vralev@redhat.com">Vladimir Ralev</a>
043: * @version $Revision: 1.1 $
044: */
045: public class WebRepositoryAccessor {
046: /** URL to remote install.xml */
047: private String installXmlUrl;
048:
049: /** Base repository URL */
050: private String baseUrl;
051:
052: /** install.xml */
053: private String installXmlString;
054:
055: /** packsinfo.xml contains nbytes, pack name and pack id */
056: private String packsInfo;
057:
058: /** list of PackInfo entries */
059: private ArrayList<PackInfo> packs;
060:
061: /** Constant for checking attributes. */
062: private static boolean YES = true;
063:
064: /** Constant for checking attributes. */
065: private static boolean NO = false;
066:
067: /** Files to be looked for at the repository base url */
068: private static final String installFilename = "install.xml";
069:
070: private static final String packsinfoFilename = "packsinfo.xml";
071:
072: /** Files being downloaded in the buffer, 1MB max */
073: private static final int BUFFER_SIZE = 1000000;
074:
075: /**
076: *
077: * Create a new WebRepositoryAccessor.
078: *
079: * @param urlbase
080: */
081: public WebRepositoryAccessor(String urlbase) {
082: this .installXmlUrl = urlbase + "/" + installFilename;
083: this .baseUrl = urlbase;
084: }
085:
086: /**
087: * Get the list of the packs from the remore install.xml
088: *
089: * @return the packs list
090: */
091: public ArrayList<PackInfo> getOnlinePacks() {
092: readConfig();
093: packs = parsePacks();
094: readPacksInfo();
095: parsePacksInfo();
096: return packs;
097: }
098:
099: /**
100: * Returns the contents of a file at url as a string (must be a text file)
101: *
102: * @param url
103: * @return the content
104: */
105: private String stringFromURL(String url) {
106: int max = BUFFER_SIZE;
107: byte[] raw = new byte[max];
108: InputStream in = null;
109: try {
110: WebAccessor w = new WebAccessor(null);
111: in = w.openInputStream(new URL(url));
112: if (in == null)
113: throw new RuntimeException(
114: "Unable to open network stream");
115: int r = in.read(raw);
116: int off = r;
117: while (r > 0) {
118: r = in.read(raw, off, max - off);
119: off += r;
120: }
121: return new String(raw);
122: } catch (Exception e) {
123: System.out.println(e + " while trying to download " + url);
124: return null;
125: } finally {
126: try {
127: if (in != null)
128: in.close();
129: } catch (Exception e) {
130: }
131: }
132: }
133:
134: /**
135: * Reads the install.xml into confgiString
136: *
137: */
138: private void readConfig() {
139: installXmlString = stringFromURL(installXmlUrl);
140: }
141:
142: /**
143: * Reads packsinfo.xml
144: *
145: */
146: private void readPacksInfo() {
147: String url = this .baseUrl + "/" + packsinfoFilename;
148: packsInfo = stringFromURL(url);
149: }
150:
151: /**
152: * Parse install.xml and return the list of packs
153: *
154: * @return the list of packs
155: */
156: private ArrayList<PackInfo> parsePacks() {
157: try {
158: IXMLParser parser = XMLParserFactory
159: .createDefaultXMLParser();
160: IXMLReader reader = StdXMLReader
161: .stringReader(installXmlString);
162: parser.setReader(reader);
163: XMLElement xml = (XMLElement) parser.parse();
164: return loadPacksList(xml);
165: } catch (Exception e) {
166: System.out.println("WARN: Unable to parse install.xml");
167: return null;
168: }
169: }
170:
171: /**
172: * Parse packsinfo.xml, fill the nbytes field, which is not available at runtime
173: * otherwise.
174: *
175: */
176: private void parsePacksInfo() {
177: try {
178: IXMLParser parser = XMLParserFactory
179: .createDefaultXMLParser();
180: IXMLReader reader = StdXMLReader.stringReader(packsInfo);
181: parser.setReader(reader);
182: XMLElement xml = (XMLElement) parser.parse();
183: XMLElement root = xml; //requireChildNamed(xml, "packs");
184: for (int q = 0; q < root.getChildrenCount(); q++) {
185: XMLElement ch = root.getChildAtIndex(q);
186: PackInfo pi = packs.get(q);
187: Pack p = pi.getPack();
188: p.nbytes = Long.parseLong(ch.getAttribute("nbytes"));
189: }
190: } catch (Exception e) {
191: System.out.println("WARN: Unable to parse packsinfo.xml");
192: }
193: }
194:
195: /**
196: * First download the jar file. The create the input stream from the
197: * downloaded file. This is because the Jar connection's openInputStream
198: * will blocks until the whole jar in order to unzip it (there is no way
199: * to see the download progress there).
200: *
201: * @param url
202: * @return the url
203: */
204: public static String getCachedUrl(String url, String tempFolder)
205: throws Exception {
206: int max = BUFFER_SIZE;
207: byte[] raw = new byte[max];
208: try {
209: WebAccessor w = new WebAccessor(null);
210: InputStream in = w.openInputStream(new URL(url));
211: int r = in.read(raw);
212: File tempDir = new File(tempFolder);
213:
214: tempDir.mkdirs();
215:
216: File temp = File.createTempFile("izpacktempfile", "jar",
217: new File(tempFolder));
218: FileOutputStream fos = new FileOutputStream(temp);
219: String path = "file:///" + temp.getAbsolutePath();
220: while (r > 0) {
221: fos.write(raw, 0, r);
222: r = in.read(raw);
223: }
224: in.close();
225: fos.close();
226:
227: return path;
228: } catch (SecurityException e) {
229: System.out.println(e + " while trying to write temp file: "
230: + tempFolder);
231: throw e;
232: } catch (Exception e) {
233: System.out.println(e + " while trying to download " + url);
234: throw e;
235: }
236: }
237:
238: protected ArrayList<PackInfo> loadPacksList(XMLElement data)
239: throws CompilerException {
240: ArrayList<PackInfo> result = new ArrayList<PackInfo>();
241:
242: // Initialisation
243: XMLElement root = requireChildNamed(data, "packs");
244:
245: // at least one pack is required
246: Vector<XMLElement> packElements = root.getChildrenNamed("pack");
247: if (packElements.isEmpty())
248: parseError(root, "<packs> requires a <pack>");
249:
250: Iterator<XMLElement> packIter = packElements.iterator();
251: while (packIter.hasNext()) {
252: XMLElement el = packIter.next();
253:
254: // Trivial initialisations
255: String name = requireAttribute(el, "name");
256: String id = el.getAttribute("id");
257:
258: boolean loose = "true".equalsIgnoreCase(el.getAttribute(
259: "loose", "false"));
260: String description = requireChildNamed(el, "description")
261: .getContent();
262: boolean required = requireYesNoAttribute(el, "required");
263: String group = el.getAttribute("group");
264: String installGroups = el.getAttribute("installGroups");
265: String excludeGroup = el.getAttribute("excludeGroup");
266: boolean uninstall = "yes".equalsIgnoreCase(el.getAttribute(
267: "uninstall", "yes"));
268: String parent = el.getAttribute("parent");
269:
270: if (required && excludeGroup != null) {
271: parseError(
272: el,
273: "Pack, which has excludeGroup can not be required.",
274: new Exception(
275: "Pack, which has excludeGroup can not be required."));
276: }
277:
278: PackInfo pack = new PackInfo(name, id, description,
279: required, loose, excludeGroup, uninstall);
280: pack.setOsConstraints(OsConstraint.getOsList(el)); // TODO:
281: pack.setParent(parent);
282:
283: // unverified
284: // if the pack belongs to an excludeGroup it's not preselected by default
285: if (excludeGroup == null)
286: pack.setPreselected(validateYesNoAttribute(el,
287: "preselected", YES));
288: else
289: pack.setPreselected(validateYesNoAttribute(el,
290: "preselected", NO));
291:
292: // Set the pack group if specified
293: if (group != null)
294: pack.setGroup(group);
295: // Set the pack install groups if specified
296: if (installGroups != null) {
297: StringTokenizer st = new StringTokenizer(installGroups,
298: ",");
299: while (st.hasMoreTokens()) {
300: String igroup = st.nextToken();
301: pack.addInstallGroup(igroup);
302: }
303: }
304:
305: // We get the parsables list
306: Iterator<XMLElement> iter = el.getChildrenNamed("parsable")
307: .iterator();
308: while (iter.hasNext()) {
309: XMLElement p = iter.next();
310: String target = requireAttribute(p, "targetfile");
311: String type = p.getAttribute("type", "plain");
312: String encoding = p.getAttribute("encoding", null);
313: List<OsConstraint> osList = OsConstraint.getOsList(p); // TODO: unverified
314:
315: pack.addParsable(new ParsableFile(target, type,
316: encoding, osList));
317: }
318:
319: // We get the executables list
320: iter = el.getChildrenNamed("executable").iterator();
321: while (iter.hasNext()) {
322: XMLElement e = iter.next();
323: ExecutableFile executable = new ExecutableFile();
324: String val; // temp value
325:
326: executable.path = requireAttribute(e, "targetfile");
327:
328: // when to execute this executable
329: val = e.getAttribute("stage", "never");
330: if ("postinstall".equalsIgnoreCase(val))
331: executable.executionStage = ExecutableFile.POSTINSTALL;
332: else if ("uninstall".equalsIgnoreCase(val))
333: executable.executionStage = ExecutableFile.UNINSTALL;
334:
335: // type of this executable
336: val = e.getAttribute("type", "bin");
337: if ("jar".equalsIgnoreCase(val)) {
338: executable.type = ExecutableFile.JAR;
339: executable.mainClass = e.getAttribute("class"); // executable
340: // class
341: }
342:
343: // what to do if execution fails
344: val = e.getAttribute("failure", "ask");
345: if ("abort".equalsIgnoreCase(val))
346: executable.onFailure = ExecutableFile.ABORT;
347: else if ("warn".equalsIgnoreCase(val))
348: executable.onFailure = ExecutableFile.WARN;
349:
350: // whether to keep the executable after executing it
351: val = e.getAttribute("keep");
352: executable.keepFile = "true".equalsIgnoreCase(val);
353:
354: // get arguments for this executable
355: XMLElement args = e.getFirstChildNamed("args");
356: if (null != args) {
357: Iterator<XMLElement> argIterator = args
358: .getChildrenNamed("arg").iterator();
359: while (argIterator.hasNext()) {
360: XMLElement arg = argIterator.next();
361: executable.argList.add(requireAttribute(arg,
362: "value"));
363: }
364: }
365:
366: executable.osList = OsConstraint.getOsList(e); // TODO:
367: // unverified
368:
369: pack.addExecutable(executable);
370: }
371:
372: // get the updatechecks list
373: iter = el.getChildrenNamed("updatecheck").iterator();
374: while (iter.hasNext()) {
375: XMLElement f = iter.next();
376:
377: String casesensitive = f.getAttribute("casesensitive");
378:
379: // get includes and excludes
380: ArrayList<String> includesList = new ArrayList<String>();
381: ArrayList<String> excludesList = new ArrayList<String>();
382:
383: // get includes and excludes
384: Iterator<XMLElement> include_it = f.getChildrenNamed(
385: "include").iterator();
386: while (include_it.hasNext()) {
387: XMLElement inc_el = include_it.next();
388: includesList.add(requireAttribute(inc_el, "name"));
389: }
390:
391: Iterator<XMLElement> exclude_it = f.getChildrenNamed(
392: "exclude").iterator();
393: while (exclude_it.hasNext()) {
394: XMLElement excl_el = exclude_it.next();
395: excludesList.add(requireAttribute(excl_el, "name"));
396: }
397:
398: pack.addUpdateCheck(new UpdateCheck(includesList,
399: excludesList, casesensitive));
400: }
401: // We get the dependencies
402: iter = el.getChildrenNamed("depends").iterator();
403: while (iter.hasNext()) {
404: XMLElement dep = iter.next();
405: String depName = requireAttribute(dep, "packname");
406: pack.addDependency(depName);
407:
408: }
409: result.add(pack);
410: }
411: return result;
412: }
413:
414: /**
415: * Create parse error with consistent messages. Includes file name. For use When parent is
416: * unknown.
417: *
418: * @param message Brief message explaining error
419: */
420: protected void parseError(String message) throws CompilerException {
421: throw new CompilerException(installFilename + ":" + message);
422: }
423:
424: /**
425: * Create parse error with consistent messages. Includes file name and line # of parent. It is
426: * an error for 'parent' to be null.
427: *
428: * @param parent The element in which the error occured
429: * @param message Brief message explaining error
430: */
431: protected void parseError(XMLElement parent, String message)
432: throws CompilerException {
433: throw new CompilerException(installFilename + ":"
434: + parent.getLineNr() + ": " + message);
435: }
436:
437: /**
438: * Create a chained parse error with consistent messages. Includes file name and line # of
439: * parent. It is an error for 'parent' to be null.
440: *
441: * @param parent The element in which the error occured
442: * @param message Brief message explaining error
443: */
444: protected void parseError(XMLElement parent, String message,
445: Throwable cause) throws CompilerException {
446: throw new CompilerException(installFilename + ":"
447: + parent.getLineNr() + ": " + message, cause);
448: }
449:
450: /**
451: * Create a parse warning with consistent messages. Includes file name and line # of parent. It
452: * is an error for 'parent' to be null.
453: *
454: * @param parent The element in which the warning occured
455: * @param message Warning message
456: */
457: protected void parseWarn(XMLElement parent, String message) {
458: System.out.println(installFilename + ":" + parent.getLineNr()
459: + ": " + message);
460: }
461:
462: /**
463: * Call getFirstChildNamed on the parent, producing a meaningful error message on failure. It is
464: * an error for 'parent' to be null.
465: *
466: * @param parent The element to search for a child
467: * @param name Name of the child element to get
468: */
469: protected XMLElement requireChildNamed(XMLElement parent,
470: String name) throws CompilerException {
471: XMLElement child = parent.getFirstChildNamed(name);
472: if (child == null)
473: parseError(parent, "<" + parent.getName()
474: + "> requires child <" + name + ">");
475: return child;
476: }
477:
478: /**
479: * Call getContent on an element, producing a meaningful error message if not present, or empty,
480: * or a valid URL. It is an error for 'element' to be null.
481: *
482: * @param element The element to get content of
483: */
484: protected URL requireURLContent(XMLElement element)
485: throws CompilerException {
486: URL url = null;
487: try {
488: url = new URL(requireContent(element));
489: } catch (MalformedURLException x) {
490: parseError(element, "<" + element.getName()
491: + "> requires valid URL", x);
492: }
493: return url;
494: }
495:
496: /**
497: * Call getContent on an element, producing a meaningful error message if not present, or empty.
498: * It is an error for 'element' to be null.
499: *
500: * @param element The element to get content of
501: */
502: protected String requireContent(XMLElement element)
503: throws CompilerException {
504: String content = element.getContent();
505: if (content == null || content.length() == 0)
506: parseError(element, "<" + element.getName()
507: + "> requires content");
508: return content;
509: }
510:
511: /**
512: * Call getAttribute on an element, producing a meaningful error message if not present, or
513: * empty. It is an error for 'element' or 'attribute' to be null.
514: *
515: * @param element The element to get the attribute value of
516: * @param attribute The name of the attribute to get
517: */
518: protected String requireAttribute(XMLElement element,
519: String attribute) throws CompilerException {
520: String value = element.getAttribute(attribute);
521: if (value == null)
522: parseError(element, "<" + element.getName()
523: + "> requires attribute '" + attribute + "'");
524: return value;
525: }
526:
527: /**
528: * Get a required attribute of an element, ensuring it is an integer. A meaningful error message
529: * is generated as a CompilerException if not present or parseable as an int. It is an error for
530: * 'element' or 'attribute' to be null.
531: *
532: * @param element The element to get the attribute value of
533: * @param attribute The name of the attribute to get
534: */
535: protected int requireIntAttribute(XMLElement element,
536: String attribute) throws CompilerException {
537: String value = element.getAttribute(attribute);
538: if (value == null || value.length() == 0)
539: parseError(element, "<" + element.getName()
540: + "> requires attribute '" + attribute + "'");
541: try {
542: return Integer.parseInt(value);
543: } catch (NumberFormatException x) {
544: parseError(element, "'" + attribute
545: + "' must be an integer");
546: }
547: return 0; // never happens
548: }
549:
550: /**
551: * Call getAttribute on an element, producing a meaningful error message if not present, or one
552: * of "yes" or "no". It is an error for 'element' or 'attribute' to be null.
553: *
554: * @param element The element to get the attribute value of
555: * @param attribute The name of the attribute to get
556: */
557: protected boolean requireYesNoAttribute(XMLElement element,
558: String attribute) throws CompilerException {
559: String value = requireAttribute(element, attribute);
560: if ("yes".equalsIgnoreCase(value))
561: return true;
562: if ("no".equalsIgnoreCase(value))
563: return false;
564:
565: parseError(element, "<" + element.getName()
566: + "> invalid attribute '" + attribute
567: + "': Expected (yes|no)");
568:
569: return false; // never happens
570: }
571:
572: /**
573: * Call getAttribute on an element, producing a meaningful warning if not "yes" or "no". If the
574: * 'element' or 'attribute' are null, the default value is returned.
575: *
576: * @param element The element to get the attribute value of
577: * @param attribute The name of the attribute to get
578: * @param defaultValue Value returned if attribute not present or invalid
579: */
580: protected boolean validateYesNoAttribute(XMLElement element,
581: String attribute, boolean defaultValue) {
582: if (element == null)
583: return defaultValue;
584:
585: String value = element.getAttribute(attribute,
586: (defaultValue ? "yes" : "no"));
587: if ("yes".equalsIgnoreCase(value))
588: return true;
589: if ("no".equalsIgnoreCase(value))
590: return false;
591:
592: // TODO: should this be an error if it's present but "none of the
593: // above"?
594: parseWarn(element, "<" + element.getName()
595: + "> invalid attribute '" + attribute
596: + "': Expected (yes|no) if present");
597:
598: return defaultValue;
599: }
600:
601: }
|