/* Copyright (c) 2001 - 2010 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.ftp; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.HashMap; import java.util.Map; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import org.geoserver.platform.GeoServerResourceLoader; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.HierarchicalStreamDriver; import com.thoughtworks.xstream.io.xml.QNameMap; import com.thoughtworks.xstream.io.xml.StaxDriver; import com.thoughtworks.xstream.io.xml.StaxWriter; /** * Loads and saves the GeoSever FTP Service {@link FTPConfig configuration} from and to the * {@code ftp.xml} file inside the GeoServer data directory. * * @author groldan * */ class FTPConfigLoader { private static final String CONFIG_FILE_NAME = "ftp.xml"; private GeoServerResourceLoader resourceLoader; public FTPConfigLoader(final GeoServerResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } public FTPConfig load() { InputStream in = null; try { File configFile = findConfigFile(); if (!configFile.exists()) { FTPConfig ftpConfig = new FTPConfig(); save(ftpConfig); return ftpConfig; } in = new FileInputStream(configFile); XStream xs = getPersister(); FTPConfig config = (FTPConfig) xs.fromXML(in); return config; } catch (IOException e) { throw new RuntimeException(e); } finally { if (in != null) { try { in.close(); } catch (IOException ignore) { // ignore } } } } private XStream getPersister() { Map<String, String> comments = new HashMap<String, String>(); comments.put("enabled", "true to enable the FTP service, false to disable it"); comments.put("ftpPort", "Port where the FTP Service listens for connections"); comments.put("idleTimeout", "number of seconds during which no network activity " + "is allowed before a session is closed due to inactivity"); comments.put("serverAddress", "IP Address used for binding the local socket. If unset, the server binds " + "to all available network interfaces"); comments.put("passivePorts", "Ports to be used for PASV data connections. Ports can " + "be defined as single ports, closed or open ranges:\n" + " Multiple definitions can be separated by commas, for example:\n" + " 2300 : only use port 2300 as the passive port\n" + " 2300-2399 : use all ports in the range\n" + " 2300- : use all ports larger than 2300\n" + " 2300, 2305, 2400- : use 2300 or 2305 or any port larger than 2400\n"); comments.put("passiveAddress", "Address on which the server will listen to passive data connections, \n" + " if not set defaults to the same address as the control " + "socket for the session."); comments.put( "passiveExternalAddress", "the address the server will claim to be listening on in the PASV reply.\n" + " Useful when the server is behind a NAT firewall and the client sees a different address than the server is using."); HierarchicalStreamDriver streamDriver = new CommentingStaxWriter(comments); XStream xStream = new XStream(streamDriver); xStream.alias("ftp", FTPConfig.class); return xStream; } public void save(FTPConfig config) { File configFile; OutputStream out = null; try { configFile = findConfigFile(); out = new FileOutputStream(configFile); XStream xs = getPersister(); xs.toXML(config, new OutputStreamWriter(out, "UTF-8")); } catch (IOException e) { throw new RuntimeException(e); } finally { if (out != null) { try { out.close(); } catch (IOException e) { throw new RuntimeException(e); } } } } private File findConfigFile() throws IOException { File configFile = resourceLoader.find(CONFIG_FILE_NAME); if (configFile == null) { configFile = new File(resourceLoader.getBaseDirectory(), CONFIG_FILE_NAME); } return configFile; } private static final class CommentingStaxWriter extends StaxDriver { final QNameMap qnameMap = new QNameMap(); private Map<String, String> comments; public CommentingStaxWriter(Map<String, String> elementComments) { this.comments = elementComments; } @Override public StaxWriter createStaxWriter(XMLStreamWriter out, boolean writeStartEndDocument) throws XMLStreamException { return new StaxWriter(qnameMap, out, writeStartEndDocument, isRepairingNamespace(), xmlFriendlyReplacer()) { private int indentLevel = 0; @Override public void startNode(final String elementName) { XMLStreamWriter xmlStreamWriter = super.getXMLStreamWriter(); try { xmlStreamWriter.writeCharacters("\n"); String comment = comments.get(elementName); if (comment != null) { indent(xmlStreamWriter); xmlStreamWriter.writeComment(comment); xmlStreamWriter.writeCharacters("\n"); indent(xmlStreamWriter); } } catch (XMLStreamException ignored) { // shouldn't happen } super.startNode(elementName); indentLevel++; } private void indent(XMLStreamWriter xmlStreamWriter) throws XMLStreamException { for (int i = 0; i < indentLevel; i++) { xmlStreamWriter.writeCharacters(" "); } } @Override public void endNode() { indentLevel--; System.err.println("indentLevel: " + indentLevel); if (indentLevel == 0) { XMLStreamWriter xmlStreamWriter = super.getXMLStreamWriter(); try { xmlStreamWriter.writeCharacters("\n"); super.endNode(); xmlStreamWriter.writeCharacters("\n"); } catch (XMLStreamException ignored) { // shouldn't happen } } else { super.endNode(); } } }; } } }