/* GASH 2 XMLTransmitter.java The GANYMEDE object storage system. Created: 16 December 2004 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.server; import arlut.csd.ganymede.common.Invid; import arlut.csd.ganymede.common.Query; import arlut.csd.ganymede.common.QueryResult; import arlut.csd.ganymede.rmi.FileTransmitter; import arlut.csd.Util.BigPipedInputStream; import arlut.csd.Util.TranslationService; import java.io.BufferedInputStream; import java.io.IOException; import java.io.PipedOutputStream; import java.rmi.RemoteException; import com.jclark.xml.output.UTF8XMLWriter; /*------------------------------------------------------------------------------ class XMLTransmitter ------------------------------------------------------------------------------*/ /** * This class is used on the server to act as a FileTransmitter, a client * pulling data to do an xmlclient dump can make iterative calls on this object * over RMI in order to receive the file. */ public final class XMLTransmitter implements FileTransmitter { private static final boolean debug = false; /** * TranslationService object for handling string localization in * the Ganymede server. */ static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.server.XMLTransmitter"); /** * How big should the buffer between the XML dumping thread and * the getNextChunk() method in this class? This can be up to 64k, * and the larger it is up to that limit, the fewer RMI calls will * be required to pull a large XML dump from Ganymede. */ static final int bufferSize = 65536; // --- private boolean eof; private PipedOutputStream outpipe; private BigPipedInputStream inpipe; private boolean doSendData; private boolean doSendSchema; private String syncChannel; /** * This constructor creates the XMLTransmitter used to send XML dump * data from the Ganymede server down to the xmlclient. A * XMLTransmitter serves as an RMI exported object that the * xmlclient can continually poll to download chunks of XML. */ public XMLTransmitter(boolean sendData, boolean sendSchema, String syncChannel, boolean includeHistory, boolean includeOid) throws IOException, RemoteException { if (debug) { System.err.println("XMLTransmitter constructed!"); } outpipe = new PipedOutputStream(); inpipe = new BigPipedInputStream(outpipe, bufferSize); doSendData = sendData; doSendSchema = sendSchema; // we need to get a thread to dump the XML schema in the // background to our pipe final String myFinalSyncChannel = syncChannel; final boolean myIncludeHistory = includeHistory; final boolean myIncludeOid = includeOid; Thread dumpThread = new Thread(new Runnable() { public void run() { try { Ganymede.db.dumpXML(outpipe, doSendData, doSendSchema, myFinalSyncChannel, myIncludeHistory, myIncludeOid); } catch (Throwable ex) { // dumpXML will close outpipe on any exception, // nothing we can productively do here, go // ahead and show it for debug purposes // "XML remote data/schema dump hit EOF.. client disconnected" System.err.println(ts.l("init.eof")); } }}, ts.l("init.threadname")); // "Ganymede XMLSession Schema/Data Dump Thread" // and set it running dumpThread.start(); Ganymede.rmi.publishObject(this); } /** * This constructor creates the XMLTransmitter used to send Ganymede * query result data from the Ganymede server down to the xmlclient. * A XMLTransmitter serves as an RMI exported object that the * xmlclient can continually poll to download chunks of XML. */ public XMLTransmitter(GanymedeSession session, Query query, QueryResult rows) throws IOException, RemoteException { if (debug) { System.err.println("XMLTransmitter[query] constructed!"); } outpipe = new PipedOutputStream(); inpipe = new BigPipedInputStream(outpipe, bufferSize); // we need to get a thread to dump the XML objects in the // background to our pipe final GanymedeSession mySession = session; final Query myQuery = query; final QueryResult myRows = rows; Thread dumpThread = new Thread(new Runnable() { public void run() { XMLDumpContext xmlOut = null; try { xmlOut = new XMLDumpContext(new UTF8XMLWriter(outpipe, UTF8XMLWriter.MINIMIZE_EMPTY_ELEMENTS), myQuery); // only supergash gets to see any password hash data when querying xmlOut.setDumpPasswords(mySession.getPermManager().isSuperGash()); xmlOut.markup("<?xml version=\"1.0\"?>\n"); xmlOut.comment("Generated by Ganymede, http://www.arlut.utexas.edu/gash2/"); xmlOut.markup("\n"); xmlOut.startElement("ganymede"); xmlOut.attribute("major", Integer.toString(GanymedeXMLSession.majorVersion)); xmlOut.attribute("minor", Integer.toString(GanymedeXMLSession.minorVersion)); xmlOut.indentOut(); xmlOut.startElementIndent("ganydata"); xmlOut.indentOut(); DBSession dbSession = mySession.getDBSession(); for (Invid invid: myRows.getInvids()) { dbSession.viewDBObject(invid).emitXML(xmlOut); } xmlOut.indentIn(); xmlOut.endElementIndent("ganydata"); xmlOut.indentIn(); xmlOut.endElementIndent("ganymede"); xmlOut.write("\n"); xmlOut.close(); xmlOut = null; } catch (Throwable ex) { if (xmlOut != null) { try { xmlOut.close(); } catch (IOException ioex) { } } // "XML remote data/schema dump hit EOF.. client disconnected" System.err.println(ts.l("init.eof")); } finally { try { outpipe.close(); } catch (IOException ex) { } } }}, ts.l("init.qthreadname")); // "Ganymede XML Query Thread" // and set it running dumpThread.start(); Ganymede.rmi.publishObject(this); } /** * <p>This method pulls down the next sequence of bytes from the * FileTransmitter. This method will block if necessary until the * data is ready to be transmitted.</p> * * <p>This method returns null on end of file.</p> */ public synchronized byte[] getNextChunk() throws RemoteException { if (eof) { return null; } try { // see how much input is ready to be read from the pipe.. if // avail is 0, that means there's nothing to read right now, // but there may be, after we call and block on inpipe.read(). // In that case, we'll go ahead and set up for a 64k block, // but if we get less than 64k when the blocking call actually // returns, we'll create a proper-sized array for transmission int avail = 0; avail = inpipe.available(); if (debug) { System.err.println("getNextChunk: avail was " + avail); } // we don't want to try to send more than 64k at once, as it's // hard (impossible?) to serialize an array longer than that. // this used to be true, anyway... if (avail > bufferSize || avail == 0) { avail = bufferSize; } byte[] data = new byte[avail]; int count = inpipe.read(data); // we may block waiting for the schema dump thread here if (debug) { System.err.println("getNextChunk ahoy [" + avail + "," + count + "]!"); } if (count <= 0) { // -1 is eof, we shouldn't actually get 0, but.. eof = true; return null; } else if (count != data.length) { // we read a smaller chunk, shrink the data down byte[] chunk = new byte[count]; System.arraycopy(data, 0, chunk, 0, count); return chunk; } else { // the data is good to go as is return data; } } catch (IOException ex) { Ganymede.logError(ex); throw new RemoteException(ex.getMessage()); } finally { if (eof) { try { inpipe.close(); } catch (IOException ex) { Ganymede.logError(ex); } } } } /** * Consumes everything sent through the XMLTransmitter until the * writer side has finished. This method will block if necessary * until the data is ready to be consumed.. */ public synchronized void drain() { try { while (getNextChunk() != null); } catch (Exception ex) { } } /** * This method is called to notify the FileTransmitter that no * more of the file will be pulled. */ public synchronized void end() throws RemoteException { eof = true; try { inpipe.close(); } catch (IOException ex) { Ganymede.logError(ex); } } }