2 * This file is part of the Vamsas Client version 0.2.
\r
3 * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite,
\r
4 * Andrew Waterhouse and Dominik Lindner.
\r
6 * Earlier versions have also been incorporated into Jalview version 2.4
\r
7 * since 2008, and TOPALi version 2 since 2007.
\r
9 * The Vamsas Client is free software: you can redistribute it and/or modify
\r
10 * it under the terms of the GNU Lesser General Public License as published by
\r
11 * the Free Software Foundation, either version 3 of the License, or
\r
12 * (at your option) any later version.
\r
14 * The Vamsas Client is distributed in the hope that it will be useful,
\r
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
17 * GNU Lesser General Public License for more details.
\r
19 * You should have received a copy of the GNU Lesser General Public License
\r
20 * along with the Vamsas Client. If not, see <http://www.gnu.org/licenses/>.
\r
22 package uk.ac.vamsas.client.simpleclient;
\r
24 import java.io.BufferedInputStream;
\r
25 import java.io.BufferedOutputStream;
\r
26 import java.io.ByteArrayInputStream;
\r
27 import java.io.ByteArrayOutputStream;
\r
28 import java.io.DataInput;
\r
29 import java.io.DataInputStream;
\r
30 import java.io.DataOutput;
\r
31 import java.io.DataOutputStream;
\r
32 import java.io.FileInputStream;
\r
33 import java.io.IOException;
\r
34 import java.io.InputStream;
\r
35 import java.util.Vector;
\r
36 import java.util.jar.JarEntry;
\r
37 import java.util.jar.JarInputStream;
\r
38 import java.util.jar.JarOutputStream;
\r
40 import org.apache.commons.logging.Log;
\r
41 import org.apache.commons.logging.LogFactory;
\r
43 import uk.ac.vamsas.client.AppDataInputStream;
\r
44 import uk.ac.vamsas.client.AppDataOutputStream;
\r
45 import uk.ac.vamsas.client.IClientAppdata;
\r
46 import uk.ac.vamsas.objects.core.AppData;
\r
47 import uk.ac.vamsas.objects.core.ApplicationData;
\r
48 import uk.ac.vamsas.objects.core.User;
\r
49 import uk.ac.vamsas.objects.utils.AppDataReference;
\r
52 * @author jimp Access interface to data chunks read from a VamsasArchiveReader
\r
53 * stream (or byte buffer input stream) or written to a VamsasArchive
\r
54 * stream. // TODO: get VamsasArchiveReader from sclient
\r
56 public class SimpleClientAppdata implements IClientAppdata {
\r
57 private static Log log = LogFactory.getLog(SimpleClientAppdata.class);
\r
60 * has the session's document been accessed to get the AppData entrys?
\r
62 protected boolean accessedDocument = false;
\r
65 * has the user datablock been modified ? temporary file containing new user
\r
66 * specific application data chunk
\r
68 SessionFile newUserData = null;
\r
70 JarOutputStream newUserDataStream = null;
\r
73 * has the apps global datablock been modified ? temporary file containing new
\r
74 * global application data chunk
\r
76 SessionFile newAppData = null;
\r
78 JarOutputStream newAppDataStream = null;
\r
81 * set by extractAppData
\r
83 protected ApplicationData appsGlobal = null;
\r
86 * set by extractAppData
\r
88 protected User usersData = null;
\r
90 ClientDocument clientdoc;
\r
93 * state flags - accessed ClientAppdata - accessed UserAppdata => inputStream
\r
94 * from embedded xml or jar entry of backup has been created - set
\r
95 * ClientAppdata - set UserAppdata => an output stream has been created and
\r
96 * written to - or a data chunk has been written. - need flag for switching
\r
97 * between embedded and jar entry mode ? - always write a jar entry for a
\r
98 * stream. - need code for rewind and overwriting if the set*Appdata methods
\r
99 * are called more than once. - need flags for streams to except a call to
\r
100 * set*Appdata when an output stream exists and is open. - need
\r
103 * The ClientDocument instance that this IClientAppData is accessing
\r
105 protected SimpleClientAppdata(ClientDocument clientdoc) {
\r
106 if (clientdoc == null) {
\r
108 .fatal("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
\r
110 "Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
\r
112 this.clientdoc = clientdoc;
\r
116 * ensures that the appData information for this client instance has been
\r
117 * extracted from the vamsas document provided by clientdoc.
\r
119 private void extractAppData() {
\r
120 if (!accessedDocument)
\r
121 _extractAppData(clientdoc.getVamsasDocument());
\r
125 * gets appropriate app data for the application, if it exists in this dataset
\r
126 * Called by every accessor to ensure data has been retrieved from document.
\r
128 private void _extractAppData(uk.ac.vamsas.objects.core.VamsasDocument doc) {
\r
130 log.debug("extractAppData called for null document object");
\r
133 if (accessedDocument) {
\r
136 Vector apldataset = AppDataReference.getUserandApplicationsData(doc,
\r
137 clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle());
\r
138 accessedDocument = true;
\r
139 if (apldataset != null) {
\r
140 if (apldataset.size() > 0) {
\r
141 AppData clientdat = (AppData) apldataset.get(0);
\r
142 if (clientdat instanceof ApplicationData) {
\r
143 appsGlobal = (ApplicationData) clientdat;
\r
144 if (apldataset.size() > 1) {
\r
145 clientdat = (AppData) apldataset.get(1);
\r
146 if (clientdat instanceof User) {
\r
147 usersData = (User) clientdat;
\r
149 if (apldataset.size() > 2)
\r
150 log.info("Ignoring additional (" + (apldataset.size() - 2)
\r
151 + ") AppDatas returned by document appdata query.");
\r
154 log.warn("Unexpected entry in AppDataReference query: id="
\r
155 + clientdat.getVorbaId() + " type="
\r
156 + clientdat.getClass().getName());
\r
158 apldataset.removeAllElements(); // destroy references.
\r
164 * LATER: generalize this for different low-level session implementations (it
\r
165 * may not always be a Jar)
\r
171 private InputStream getAppDataStream(AppData appdata,
\r
172 VamsasArchiveReader docreader) {
\r
173 String entryRef = appdata.getDataReference();
\r
174 if (entryRef != null) {
\r
175 log.debug("Resolving appData reference +" + entryRef);
\r
176 InputStream entry = docreader.getAppdataStream(entryRef);
\r
177 if (entry != null) {
\r
179 // log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");
\r
183 .debug("GetAppDataStream called for an AppData without a data reference.");
\r
189 * yuk - size of buffer used for slurping appData JarEntry into a byte array.
\r
191 private final int _TRANSFER_BUFFER = 4096 * 4;
\r
194 * Resolve AppData object to a byte array.
\r
197 * @param archiveReader
\r
198 * @return null or the application data as a byte array
\r
200 private byte[] getAppDataAsByteArray(AppData appdata,
\r
201 VamsasArchiveReader docreader) {
\r
202 if (appdata.getData() == null) {
\r
203 if (docreader == null) {
\r
204 log.warn("Silently failing getAppDataAsByteArray with null docreader.",
\r
208 // resolve and load data
\r
209 InputStream entry = getAppDataStream(appdata, docreader);
\r
210 ByteArrayOutputStream bytes = new ByteArrayOutputStream();
\r
212 byte buff[] = new byte[_TRANSFER_BUFFER];
\r
214 while (entry != null && entry.available() > 0) {
\r
215 int len = entry.read(buff, 0, _TRANSFER_BUFFER);
\r
217 bytes.write(buff, 0, len);
\r
222 } catch (Exception e) {
\r
225 "Unexpected exception - probable truncation when accessing VamsasDocument entry "
\r
226 + appdata.getDataReference(), e);
\r
228 if (bytes.size() > 0) {
\r
229 // LATER: deal with probable OutOfMemoryErrors here
\r
230 log.debug("Got " + bytes.size() + " bytes from AppDataReference "
\r
231 + appdata.getDataReference());
\r
232 byte data[] = bytes.toByteArray();
\r
238 log.debug("Returning inline AppData block for " + appdata.getVorbaId());
\r
239 return appdata.getData();
\r
244 * internal method for getting a DataInputStream from an AppData object.
\r
248 * @return data in object or null if no data is accessible
\r
250 private AppDataInputStream getAppDataAsDataInputStream(AppData appdata,
\r
251 VamsasArchiveReader docreader) {
\r
252 if (appdata != null && docreader != null) {
\r
253 String entryRef = appdata.getDataReference();
\r
254 if (entryRef != null) {
\r
255 log.debug("Resolving AppData reference for " + entryRef);
\r
256 InputStream jstrm = docreader.getAppdataStream(entryRef);
\r
258 return new AppDataInputStream(jstrm);
\r
260 log.debug("Returning null input stream for unresolved reference ("
\r
261 + entryRef + ") id=" + appdata.getVorbaId());
\r
265 // return a byteArray input stream
\r
266 byte[] data = appdata.getData();
\r
267 if (data.length > 0) {
\r
268 ByteArrayInputStream stream = new ByteArrayInputStream(data);
\r
269 return new AppDataInputStream(stream);
\r
272 .debug("Returning null input stream for empty Appdata data block in id="
\r
273 + appdata.getVorbaId());
\r
278 log.debug("Returning null DataInputStream for appdata entry:"
\r
279 + appdata.getVorbaId());
\r
285 * internal method for getting ByteArray from AppData object
\r
287 * @param clientOrUser
\r
288 * - true for returning userData, otherwise return Client AppData.
\r
289 * @return null or byte array
\r
291 private byte[] _getappdataByteArray(boolean clientOrUser) {
\r
292 if (clientdoc == null)
\r
294 "Implementation error, Improperly initialized SimpleClientAppdata.");
\r
295 byte[] data = null;
\r
297 if (!clientOrUser) {
\r
298 appdName = "Client's Appdata";
\r
300 appdName = "User's Appdata";
\r
302 log.debug("getting " + appdName + " as a byte array");
\r
305 if (!clientOrUser) {
\r
306 object = appsGlobal;
\r
308 object = usersData;
\r
310 if (object != null) {
\r
311 log.debug("Trying to resolve " + appdName + " object to byte array.");
\r
312 data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
\r
316 .debug("Returning null for " + appdName
\r
317 + "ClientAppdata byte[] array");
\r
323 * common method for Client and User AppData->InputStream accessor
\r
325 * @param clientOrUser
\r
326 * - the appData to resolve - false for client, true for user
\r
328 * @return null or the DataInputStream desired.
\r
330 private AppDataInputStream _getappdataInputStream(boolean clientOrUser) {
\r
331 if (clientdoc == null)
\r
333 "Implementation error, Improperly initialized SimpleClientAppdata.");
\r
335 if (!clientOrUser) {
\r
336 appdName = "Client's Appdata";
\r
338 appdName = "User's Appdata";
\r
340 if (log.isDebugEnabled())
\r
341 log.debug("getting " + appdName + " as an input stream.");
\r
344 if (!clientOrUser) {
\r
345 object = appsGlobal;
\r
347 object = usersData;
\r
349 if (object != null) {
\r
350 log.debug("Trying to resolve ClientAppdata object to an input stream.");
\r
351 return getAppDataAsDataInputStream(object, clientdoc
\r
352 .getVamsasArchiveReader());
\r
354 log.debug("getClientInputStream returning null.");
\r
361 * @see uk.ac.vamsas.client.IClientAppdata#getClientAppdata()
\r
363 public byte[] getClientAppdata() {
\r
364 return _getappdataByteArray(false);
\r
370 * @see uk.ac.vamsas.client.IClientAppdata#getClientInputStream()
\r
372 public AppDataInputStream getClientInputStream() {
\r
373 return _getappdataInputStream(false);
\r
379 * @see uk.ac.vamsas.client.IClientAppdata#getUserAppdata()
\r
381 public byte[] getUserAppdata() {
\r
382 return _getappdataByteArray(true);
\r
388 * @see uk.ac.vamsas.client.IClientAppdata#getUserInputStream()
\r
390 public AppDataInputStream getUserInputStream() {
\r
391 return _getappdataInputStream(true);
\r
395 * methods for writing new AppData entries.
\r
397 private AppDataOutputStream _getAppdataOutputStream(boolean clientOrUser) {
\r
398 // Must access document to get any existing references
\r
401 SessionFile apdfile = null;
\r
402 if (!clientOrUser) {
\r
403 apdname = "clientAppData";
\r
404 apdfile = newAppData;
\r
406 apdname = "userAppData";
\r
407 apdfile = newUserData;
\r
410 if (apdfile == null) {
\r
411 apdfile = clientdoc.sclient._session
\r
412 .getTempSessionFile(apdname, ".jar");
\r
413 log.debug("Successfully made temp appData file for " + apdname);
\r
415 // truncate to remove existing data.
\r
416 apdfile.fileLock.getRaFile().setLength(0);
\r
418 .debug("Successfully truncated existing temp appData for "
\r
421 } catch (Exception e) {
\r
422 log.error("Whilst opening temp file in directory "
\r
423 + clientdoc.sclient._session.sessionDir, e);
\r
425 // we do not make another file for the new entry if one exists already
\r
426 if (!clientOrUser) {
\r
427 newAppData = apdfile;
\r
429 newUserData = apdfile;
\r
432 apdfile.lockFile();
\r
433 // LATER: Refactor these local AppDatastream IO stuff to their own class.
\r
434 JarOutputStream dstrm = new JarOutputStream(apdfile.fileLock
\r
435 .getBufferedOutputStream(true));
\r
436 if (!clientOrUser) {
\r
437 newAppDataStream = dstrm;
\r
439 newUserDataStream = dstrm;
\r
441 dstrm.putNextEntry(new JarEntry("appData_entry.dat"));
\r
442 // LATER: there may be trouble ahead if an AppDataOutputStream is written
\r
443 // to by one thread when another truncates the file. This situation should
\r
444 // be prevented if possible
\r
445 return new AppDataOutputStream(dstrm);
\r
446 } catch (Exception e) {
\r
447 log.error("Whilst opening jar output stream for file "
\r
448 + apdfile.sessionFile);
\r
450 // tidy up and return null
\r
451 apdfile.unlockFile();
\r
456 * copy data from the appData jar file to an appropriately referenced jar or
\r
457 * Data entry for the given ApplicationData Assumes the JarFile is properly
\r
461 * session Document handler
\r
463 * the AppData whose block is being updated
\r
465 * the new data in a Jar written by this class
\r
467 protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd,
\r
468 SessionFile apdjar) throws IOException {
\r
469 if (apdjar == null || apdjar.sessionFile == null
\r
470 || !apdjar.sessionFile.exists()) {
\r
471 throw new IOException("No temporary Appdata to recover and transfer.");
\r
473 if (vdoc == null) {
\r
474 log.fatal("FATAL! NO DOCUMENT TO WRITE TO!");
\r
475 throw new IOException("FATAL! NO DOCUMENT TO WRITE TO!");
\r
477 log.debug("Recovering AppData entry from " + apdjar.sessionFile);
\r
478 JarInputStream istrm = new JarInputStream(apdjar
\r
479 .getBufferedInputStream(true));
\r
480 JarEntry je = null;
\r
481 while (istrm.available() > 0 && (je = istrm.getNextJarEntry()) != null
\r
482 && !je.getName().equals("appData_entry.dat")) {
\r
484 log.debug("Ignoring extraneous entry " + je.getName());
\r
486 if (istrm.available() > 0 && je != null) {
\r
487 log.debug("Found appData_entry.dat in Jar");
\r
488 String ref = appd.getDataReference();
\r
490 throw new IOException("Null AppData.DataReference passed.");
\r
492 log.debug("Writing appData_entry.dat as " + ref);
\r
493 if (vdoc.writeAppdataFromStream(ref, istrm)) {
\r
494 log.debug("Entry updated successfully.");
\r
496 throw new IOException(
\r
497 "writeAppdataFromStream did not return true - expect future badness."); // LATER
\r
506 throw new IOException(
\r
507 "Couldn't find appData_entry.dat in temporary jar file "
\r
508 + apdjar.sessionFile.getAbsolutePath());
\r
516 * @see uk.ac.vamsas.client.IClientAppdata#getClientOutputStream()
\r
518 public AppDataOutputStream getClientOutputStream() {
\r
519 if (clientdoc == null)
\r
521 "Implementation error, Improperly initialized SimpleClientAppdata.");
\r
522 if (log.isDebugEnabled())
\r
523 log.debug("trying to getClientOutputStream for "
\r
524 + clientdoc.sclient.client.getClientUrn());
\r
525 return _getAppdataOutputStream(false);
\r
531 * @see uk.ac.vamsas.client.IClientAppdata#getUserOutputStream()
\r
533 public AppDataOutputStream getUserOutputStream() {
\r
534 if (clientdoc == null)
\r
536 "Implementation error, Improperly initialized SimpleClientAppdata.");
\r
537 if (log.isDebugEnabled())
\r
538 log.debug("trying to getUserOutputStream for ("
\r
539 + clientdoc.sclient.getUserHandle().getFullName() + ")"
\r
540 + clientdoc.sclient.client.getClientUrn());
\r
541 return _getAppdataOutputStream(true);
\r
547 * @see uk.ac.vamsas.client.IClientAppdata#hasClientAppdata()
\r
549 public boolean hasClientAppdata() {
\r
550 if (clientdoc == null)
\r
552 "Implementation error, Improperly initialized SimpleClientAppdata.");
\r
554 // LATER - check validity of a DataReference before we return true
\r
555 // TODO: return true if usersData is null but we have already written a new
\r
557 if ((appsGlobal != null)
\r
558 && (appsGlobal.getDataReference() != null || appsGlobal.getData() != null))
\r
566 * @see uk.ac.vamsas.client.IClientAppdata#hasUserAppdata()
\r
568 public boolean hasUserAppdata() {
\r
569 if (clientdoc == null)
\r
571 "Implementation error, Improperly initialized SimpleClientAppdata.");
\r
573 // LATER - check validity of a DataReference before we return true
\r
574 // TODO: return true if usersData is null but we have already written a new
\r
576 if ((usersData != null)
\r
577 && (usersData.getDataReference() != null || usersData.getData() != null))
\r
582 private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
\r
584 if (data != null && data.length > 0)
\r
586 ostrm.closeEntry();
\r
588 } catch (Exception e) {
\r
589 log.error("Serious! - IO error when writing AppDataStream to file "
\r
590 + newAppData.sessionFile, e);
\r
598 * @see uk.ac.vamsas.client.IClientAppdata#setClientAppdata(byte[])
\r
600 public void setClientAppdata(byte[] data) {
\r
601 if (clientdoc == null)
\r
603 "Implementation error, Improperly initialized SimpleClientAppdata.");
\r
604 _getAppdataOutputStream(false);
\r
605 if (newAppDataStream == null) {
\r
606 // LATER: define an exception for this ? - operation may fail even if file
\r
607 // i/o not involved
\r
609 .error("Serious! - couldn't open new AppDataStream in session directory "
\r
610 + clientdoc.sclient._session.sessionDir);
\r
612 _writeAppDataStream(newAppDataStream, data);
\r
613 // LATER: deal with error case - do we make session read only, or what ?
\r
620 * @see uk.ac.vamsas.client.IClientAppdata#setUserAppdata(byte[])
\r
622 public void setUserAppdata(byte[] data) {
\r
623 if (clientdoc == null)
\r
625 "Implementation error, Improperly initialized SimpleClientAppdata.");
\r
626 _getAppdataOutputStream(true);
\r
627 if (newUserDataStream == null) {
\r
628 // LATER: define an exception for this ? - operation may fail even if file
\r
629 // i/o not involved
\r
631 .error("Serious! - couldn't open new UserDataStream in session directory "
\r
632 + clientdoc.sclient._session.sessionDir);
\r
634 _writeAppDataStream(newUserDataStream, data);
\r
635 // LATER: deal with error case - do we make session read only, or what ?
\r
640 * flush and close outstanding output streams. - do this before checking data
\r
643 * @throws IOException
\r
645 protected void closeForWriting() throws IOException {
\r
646 if (newAppDataStream != null) {
\r
647 newAppDataStream.flush();
\r
648 newAppDataStream.closeEntry();
\r
649 newAppDataStream.close();
\r
651 if (newUserDataStream != null) {
\r
652 newUserDataStream.flush();
\r
653 newUserDataStream.closeEntry();
\r
654 newUserDataStream.close();
\r
660 * @return true if any AppData blocks have to be updated in session Jar
\r
662 protected boolean isModified() {
\r
663 // LATER differentiate between core xml modification and Jar Entry
\r
665 if ((newAppData != null && newAppData.sessionFile.exists())
\r
666 || (newUserData != null && newUserData.sessionFile.exists()))
\r
674 * @see java.lang.Object#finalize()
\r
676 protected void finalize() throws Throwable {
\r
677 if (newAppDataStream != null) {
\r
678 newAppDataStream = null;
\r
680 if (newAppDataStream != null) {
\r
681 newUserDataStream = null;
\r
683 if (newAppData != null) {
\r
684 newAppData.eraseExistence();
\r
687 if (newUserData != null) {
\r
688 newUserData.eraseExistence();
\r
689 newUserData = null;
\r