From fab6bab770b548a0c99ed6f5dfb46f5aa99f67c7 Mon Sep 17 00:00:00 2001 From: pvtroshin Date: Mon, 22 Nov 2010 15:09:13 +0000 Subject: [PATCH] Adding AAConWS git-svn-id: link to svn.lifesci.dundee.ac.uk/svn/barton/ptroshin/JABA2@3343 e3abac25-378b-4346-85de-24260fe3988d --- .classpath | 3 +- TODO.txt | 1 + .../{compbio-util-1.2.jar => compbio-util-1.3.jar} | Bin 16690 -> 17632 bytes binaries/aaconservation.jar | Bin 0 -> 136881 bytes binaries/manual.txt | 90 +++ build.xml | 9 +- conf/Executable.properties | 13 +- conf/settings/AAConLimits.xml | 13 + conf/settings/AAConParameters.xml | 36 + conf/settings/AAConPresets.xml | 30 + .../data/sequence/ClustalAlignmentUtil.java | 481 ++++++------ .../compbio/data/sequence/DisemblResultAnnot.java | 5 + datamodel/compbio/data/sequence/FastaSequence.java | 308 ++++---- .../compbio/data/sequence/JalviewAnnotation.java | 7 + .../data/sequence/MultiAnnotatedSequence.java | 73 +- datamodel/compbio/data/sequence/SequenceUtil.java | 771 +++++++++++--------- runner/compbio/runner/conservation/AACon.java | 198 +++++ runner/compbio/runner/disorder/Disembl.java | 196 +++-- .../compbio/data/sequence/SequenceUtilTester.java | 277 ++++--- testsrc/compbio/metadata/AllTestSuit.java | 6 +- .../compbio/runner/conservation/AAConTester.java | 381 ++++++++++ testsrc/compbio/runner/disorder/DisemblTester.java | 352 +++++++++ testsrc/testdata/TO1381.fasta.aln | 35 + webservices/compbio/data/msa/Annotation.java | 175 +++++ webservices/compbio/data/msa/JManagement.java | 54 ++ webservices/compbio/data/msa/Metadata.java | 50 ++ webservices/compbio/data/msa/MsaWS.java | 394 ++++------ webservices/compbio/ws/server/AAConWS.java | 140 ++++ webservices/compbio/ws/server/WSUtil.java | 112 +-- 29 files changed, 2910 insertions(+), 1300 deletions(-) rename WEB-INF/lib/{compbio-util-1.2.jar => compbio-util-1.3.jar} (80%) create mode 100644 binaries/aaconservation.jar create mode 100644 binaries/manual.txt create mode 100644 conf/settings/AAConLimits.xml create mode 100644 conf/settings/AAConParameters.xml create mode 100644 conf/settings/AAConPresets.xml create mode 100644 datamodel/compbio/data/sequence/DisemblResultAnnot.java create mode 100644 datamodel/compbio/data/sequence/JalviewAnnotation.java create mode 100644 runner/compbio/runner/conservation/AACon.java create mode 100644 testsrc/compbio/runner/conservation/AAConTester.java create mode 100644 testsrc/compbio/runner/disorder/DisemblTester.java create mode 100644 testsrc/testdata/TO1381.fasta.aln create mode 100644 webservices/compbio/data/msa/Annotation.java create mode 100644 webservices/compbio/data/msa/JManagement.java create mode 100644 webservices/compbio/data/msa/Metadata.java create mode 100644 webservices/compbio/ws/server/AAConWS.java diff --git a/.classpath b/.classpath index 9107de9..cff7667 100644 --- a/.classpath +++ b/.classpath @@ -11,6 +11,7 @@ - + + diff --git a/TODO.txt b/TODO.txt index 79f6ca1..6e70077 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,5 +1,6 @@ TODO: +Add iupred ws http://iupred.enzim.hu/ Add globprot ws Add ronn ws diff --git a/WEB-INF/lib/compbio-util-1.2.jar b/WEB-INF/lib/compbio-util-1.3.jar similarity index 80% rename from WEB-INF/lib/compbio-util-1.2.jar rename to WEB-INF/lib/compbio-util-1.3.jar index a8e20becf80845baf64bc25e8de50eb58d308a2c..9042b5a2760afcd3af10996bc37f848df24f2c8a 100644 GIT binary patch delta 2144 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 WoaN+=E{c47415e%fC@ZpL9zgS%m0r6 diff --git a/binaries/aaconservation.jar b/binaries/aaconservation.jar new file mode 100644 index 0000000000000000000000000000000000000000..375b8e9c38acd810f9ed42dfdea3c09c5ad0b275 GIT binary patch literal 136881 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

1lKgc^YLQ!QJd|g(7^o;3C9J{F``Bd9Q}3 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 z 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%0AKCd-_@YKBTPy0QF;FbBk)1Z^}onx!?!o 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 literal 0 HcmV?d00001 diff --git a/binaries/manual.txt b/binaries/manual.txt new file mode 100644 index 0000000..ff7d66f --- /dev/null +++ b/binaries/manual.txt @@ -0,0 +1,90 @@ + +AA Conservation version 1.0b (2 September 2010) + +This program allows calculation of conservation of amino acids in +multiple sequence alignments. +It implements 17 different conservation scores as described by Valdar in +his paper (Scoring Residue Conservation, PROTEINS: Structure, Function +and Bioinformatics 48:227-241 (2002)) and SMERFS scoring algorithm as described +by Manning, Jefferson and Barton (The contrasting properties of conservation +and correlated phylogeny in protein functional residue prediction, +BMC Bioinformatics (2008)). + +The conservation algorithms supported are: + +KABAT, JORES, SCHNEIDER, SHENKIN, GERSTEIN, TAYLOR_GAPS, TAYLOR_NO_GAPS, +ZVELIBIL, KARLIN, ARMON, THOMPSON, NOT_LANCET, MIRNY, WILLIAMSON, +LANDGRAF, SANDER, VALDAR, SMERFS + +Input format is either a FASTA formatted file containing aligned sequences with +gaps or a Clustal alignment. The valid gap characters are *, -, space character, +X and . (a dot). By default program prints the results to the command window. +If the output file is provided the results are printed to the file in two +possible formats with or without an alignment. +If format is not specified, the program outputs conservation scores without +alignment. The scores are not normalized by default but they can be (see below). +SMERFS default parameters are window width of 7, column score is set to +the middle column, gap% cutoff of 0.1. If different values for SMERFS parameters +are required than all three parameters must be provided. Details of the program +execution can be recorded to a separate file if an appropriate file path is +provided. + +List of command line arguments: + +-m= precedes a comma separated list of method names + EXAMPLE: -m=KABAT,JORES,GERSTEIN + Optional, if no method is specified request for all is assumed. + +-i= precedes a full path to the input FASTA file, required + +-o= precedes a full path to the output file, optional, if no output file is + provided the program will output to the standard out. + +-t= precedes the number of CPUs (CPU cores more precisely) to use. Optional, + defaults to all processors available on the machine. + +-f= precedes the format of the results in the output file + two different formats are possible: + RESULT_WITH_ALIGNMENT + RESULT_NO_ALIGNMENT + Optional, if not specified RESULT_NO_ALIGNMENT is assumed + +-s= precedes a list of three comma separated parameters for SMERFS + the order of parameters is as following: + 1. window width - an integer and an odd number + 2. how to allocate window scores to columns, two ways are possible: + MID_SCORE - gives the window score to the middle column + MAX_SCORE - gives the column the highest score of all the windows it + belongs to + 3. gap percentage cutoff - a float greater than 0 and smaller or equal 1 + EXAMPLE: -s=5,MID_SCORE,0.1 + Optional, default values are 7,MID_SCORE,0.1 + +-d= precedes a full path to a file where program execution details are to be + listed. Optional, if not provided, no execution statistics is produced. + +-g= precedes comma separated list of gap characters provided by the user, if + you're using an unusual gap character (not a -,., ,*,X) you have to + provide it. If you you provide this list you have to list all the gaps + accepted. Including those that were previously treated as a default. + Optional. + +-n using this key causes the results to be normalized. + Normalized results have values between 0 and 1. Please note however, that + some results cannot be normalized. In such a case, the system returns not + normalized value, and log the issue to the standard error stream. + The following formula is used for normalization + n = (d - dmin)/(dmax - dmin) + Negative results first converted to positive by adding an absolute value of + the most negative result. Optional. + +EXAMPLE HOW TO RUN THE PROGRAM: +java -jar -m=KABAT,SMERFS -i=prot1 -o=prot1_results -n + +As a result of the execution KABAT and SMERFS scores will be calculated. +Input comes form prot1 file and an output without an alignment is recorded to +prot1_results file. + +Authors: Peter Troshin, Agnieszka Golicz, David Martin and Geoff Barton. +Please visit http://www.compbio.dundee.ac.uk for further information. + \ No newline at end of file diff --git a/build.xml b/build.xml index 724776f..05137d6 100644 --- a/build.xml +++ b/build.xml @@ -153,7 +153,7 @@ - + @@ -233,7 +233,7 @@ - + @@ -248,7 +248,8 @@ - + Packing binaries, and configuration files @@ -264,7 +265,7 @@ - + diff --git a/conf/Executable.properties b/conf/Executable.properties index 0a9b5cd..247c9f5 100644 --- a/conf/Executable.properties +++ b/conf/Executable.properties @@ -43,6 +43,8 @@ cluster.tcoffee.bin=/homes/pvtroshin/workspace/jaba2/binaries/src/tcoffee/t_coff #/sw/bin/t_coffee # Sub matrix support does not work #tcoffee.-matrix.path=binaries/matrices +# This variable is required by tcoffee +tcoffee.bin.env=HOME_4_TCOFFEE#jobsout; tcoffee.presets.file=conf/settings/TcoffeePresets.xml tcoffee.parameters.file=conf/settings/TcoffeeParameters.xml tcoffee.limits.file=conf/settings/TcoffeeLimits.xml @@ -75,5 +77,14 @@ jronn.cluster.settings=-q 64bit-pri.q -pe smp 4 -l h_vmem=1700M -l ram=1700M -l local.disembl.bin=/homes/pvtroshin/soft/DisEMBL-1.4raw/DisEMBL.py cluster.disembl.bin=/homes/pvtroshin/soft/DisEMBL-1.4raw/DisEMBL.py #disembl.parameters.file=conf/settings/JronnParameters.xml -disembl.limits.file=conf/settings/JronnLimits.xml +disembl.limits.file=conf/settings/DisemblLimits.xml disembl.cluster.settings=-l h_cpu=24:00:00 -l h_vmem=6000M -l ram=6000M + +local.aacon.bin.windows=D:\\Java\\jdk1.6.0_14\\bin\\java.exe +local.aacon.bin=/sw/java/latest/bin/java +cluster.aacon.bin=/sw/java/latest/bin/java +aacon.jar.file=binaries/aaconservation.jar +aacon.parameters.file=conf/settings/AAConParameters.xml +aacon.limits.file=conf/settings/AAConLimits.xml +#TODO jronn.jvm.options=-Xms32M -Xmx512M +aacon.cluster.cpunum=4 diff --git a/conf/settings/AAConLimits.xml b/conf/settings/AAConLimits.xml new file mode 100644 index 0000000..a3e4935 --- /dev/null +++ b/conf/settings/AAConLimits.xml @@ -0,0 +1,13 @@ + + + compbio.runner.conservation.AACon + + 100000 + 100000 + + + # LocalEngineExecutionLimit # + 1000 + 1000 + + diff --git a/conf/settings/AAConParameters.xml b/conf/settings/AAConParameters.xml new file mode 100644 index 0000000..9f5da9f --- /dev/null +++ b/conf/settings/AAConParameters.xml @@ -0,0 +1,36 @@ + + + compbio.runner.conservation.AACon + + Normalize + Normalize the results. The results of the calculation by different methods will all be scaled to the range between 0 and 1, so that they are comparable + -n + http://www.compbio.dundee.ac.uk/jabaws/prog_docs/aacon.txt + + = + + Calculation method + The method of the calculation to use + -m + http://www.compbio.dundee.ac.uk/jabaws/prog_docs/aacon.txt + SHENKIN + KABAT + JORES + SCHNEIDER + SHENKIN + GERSTEIN + TAYLOR_GAPS + TAYLOR_NO_GAPS + ZVELIBIL + KARLIN + ARMON + THOMPSON + NOT_LANCET + MIRNY + WILLIAMSON + LANDGRAF + SANDER + VALDAR + SMERFS + + diff --git a/conf/settings/AAConPresets.xml b/conf/settings/AAConPresets.xml new file mode 100644 index 0000000..cdf8d37 --- /dev/null +++ b/conf/settings/AAConPresets.xml @@ -0,0 +1,30 @@ + + + compbio.runner.conservation.AACon + + Quick conservation + Collection of fast conservation methods + + + + + + Slow conservation + Collection of most expensive (slow) conservation methods + + + + + + Complete conservation + Calculate conservation with all supported methods + + + + + + + + + + diff --git a/datamodel/compbio/data/sequence/ClustalAlignmentUtil.java b/datamodel/compbio/data/sequence/ClustalAlignmentUtil.java index 5fce997..b6076a4 100644 --- a/datamodel/compbio/data/sequence/ClustalAlignmentUtil.java +++ b/datamodel/compbio/data/sequence/ClustalAlignmentUtil.java @@ -1,19 +1,15 @@ -/* Copyright (c) 2009 Peter Troshin - * - * JAva Bioinformatics Analysis Web Services (JABAWS) @version: 1.0 - * - * This library is free software; you can redistribute it and/or modify it under the terms of the - * Apache License version 2 as published by the Apache Software Foundation - * - * This library 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 Apache - * License for more details. - * - * A copy of the license is in apache_license.txt. It is also available here: - * @see: http://www.apache.org/licenses/LICENSE-2.0.txt - * - * Any republication or derived work distributed in source code form - * must include this copyright and license notice. +/* + * Copyright (c) 2009 Peter Troshin JAva Bioinformatics Analysis Web Services + * (JABAWS) @version: 1.0 This library is free software; you can redistribute it + * and/or modify it under the terms of the Apache License version 2 as published + * by the Apache Software Foundation This library 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 + * Apache License for more details. A copy of the license is in + * apache_license.txt. It is also available here: + * @see: http://www.apache.org/licenses/LICENSE-2.0.txt Any republication or + * derived work distributed in source code form must include this copyright and + * license notice. */ package compbio.data.sequence; @@ -45,246 +41,247 @@ import java.util.logging.Logger; */ public final class ClustalAlignmentUtil { - private static final Logger log = Logger - .getLogger(ClustalAlignmentUtil.class.getCanonicalName()); - - /** - * Dash char to be used as gap char in the alignments - */ - public static final char gapchar = '-'; - - /* - * Number of spaces separating the name and the sequence - */ - private static final String spacer = " "; // 6 space characters - /* - * name length limit is 30 characters! 2.0.7 - 2.0.12 clustalw /* if name is - * longer than that it gets trimmed in the end - */ - private static final int maxNameLength = 30; // Maximum name length - /* - * If all sequences names in the alignment is shorter than - * minNameHolderLength than spaces are added to complete the name up to - * minNameHolderLength - */ - private static final int minNameHolderLength = 10; // Minimum number of - - // TODO check whether clustal still loads data if length is 60! - private static final int oneLineAlignmentLength = 60; // this could in fact - - // be 50 - - // for long names ~30 chars - - /** - * Read Clustal formatted alignment. Limitations: Does not read consensus - * - * Sequence names as well as the sequences are not guaranteed to be unique! - * - * @throws {@link IOException} - * @throws {@link UnknownFileFormatException} - */ - public static Alignment readClustalFile(InputStream instream) - throws IOException, UnknownFileFormatException { - - boolean flag = false; - - List headers = new ArrayList(); - Map seqhash = new HashMap(); - FastaSequence[] seqs = null; - - String line; - - BufferedReader breader = new BufferedReader(new InputStreamReader( - instream)); - while ((line = breader.readLine()) != null) { - if (line.indexOf(" ") != 0) { - java.util.StringTokenizer str = new StringTokenizer(line, " "); - String id = ""; - - if (str.hasMoreTokens()) { - id = str.nextToken(); - // PROBCONS output clustal formatted file with not mention - // of CLUSTAL (:-)) - if (id.equals("CLUSTAL") || id.equals("PROBCONS")) { - flag = true; - } else { - if (flag) { - StringBuffer tempseq; - if (seqhash.containsKey(id)) { - tempseq = seqhash.get(id); - } else { - tempseq = new StringBuffer(); - seqhash.put(id, tempseq); - } - - if (!(headers.contains(id))) { - headers.add(id); - } - - tempseq.append(str.nextToken()); + private static final Logger log = Logger + .getLogger(ClustalAlignmentUtil.class.getCanonicalName()); + + /** + * Dash char to be used as gap char in the alignments + */ + public static final char gapchar = '-'; + + /* + * Number of spaces separating the name and the sequence + */ + private static final String spacer = " "; // 6 space characters + /* + * name length limit is 30 characters! 2.0.7 - 2.0.12 clustalw /* if name is + * longer than that it gets trimmed in the end + */ + private static final int maxNameLength = 30; // Maximum name length + /* + * If all sequences names in the alignment is shorter than + * minNameHolderLength than spaces are added to complete the name up to + * minNameHolderLength + */ + private static final int minNameHolderLength = 10; // Minimum number of + + // TODO check whether clustal still loads data if length is 60! + private static final int oneLineAlignmentLength = 60; // this could in fact + + // be 50 + + // for long names ~30 chars + + /** + * Read Clustal formatted alignment. Limitations: Does not read consensus + * + * Sequence names as well as the sequences are not guaranteed to be unique! + * + * @throws {@link IOException} + * @throws {@link UnknownFileFormatException} + */ + public static Alignment readClustalFile(InputStream instream) + throws IOException, UnknownFileFormatException { + + boolean flag = false; + + List headers = new ArrayList(); + Map seqhash = new HashMap(); + FastaSequence[] seqs = null; + + String line; + + BufferedReader breader = new BufferedReader(new InputStreamReader( + instream)); + while ((line = breader.readLine()) != null) { + if (line.indexOf(" ") != 0) { + java.util.StringTokenizer str = new StringTokenizer(line, " "); + String id = ""; + + if (str.hasMoreTokens()) { + id = str.nextToken(); + // PROBCONS output clustal formatted file with not mention + // of CLUSTAL (:-)) + if (id.equals("CLUSTAL") || id.equals("PROBCONS")) { + flag = true; + } else { + if (flag) { + StringBuffer tempseq; + if (seqhash.containsKey(id)) { + tempseq = seqhash.get(id); + } else { + tempseq = new StringBuffer(); + seqhash.put(id, tempseq); + } + + if (!(headers.contains(id))) { + headers.add(id); + } + + tempseq.append(str.nextToken()); + } + } + } } - } } - } - } - breader.close(); + breader.close(); - // TODO improve this bit - if (flag) { + // TODO improve this bit + if (flag) { - // Add sequences to the hash - seqs = new FastaSequence[headers.size()]; - for (int i = 0; i < headers.size(); i++) { - if (seqhash.get(headers.get(i)) != null) { + // Add sequences to the hash + seqs = new FastaSequence[headers.size()]; + for (int i = 0; i < headers.size(); i++) { + if (seqhash.get(headers.get(i)) != null) { - FastaSequence newSeq = new FastaSequence(headers.get(i), - seqhash.get(headers.get(i)).toString()); + FastaSequence newSeq = new FastaSequence(headers.get(i), + seqhash.get(headers.get(i)).toString()); - seqs[i] = newSeq; + seqs[i] = newSeq; - } else { - // should not happened - throw new AssertionError( - "Bizarreness! Can't find sequence for " - + headers.get(i)); + } else { + // should not happened + throw new AssertionError( + "Bizarreness! Can't find sequence for " + + headers.get(i)); + } + } } - } - } - if (seqs == null || seqs.length == 0) { - throw new UnknownFileFormatException( - "Input does not appear to be a clustal file! "); - } - return new Alignment(Arrays.asList(seqs), new AlignmentMetadata( - Program.CLUSTAL, gapchar)); - } - - /** - * - * @param input - * @return true if the file is recognised as Clustal formatted alignment, - * false otherwise - */ - public static boolean isValidClustalFile(InputStream input) { - if (input == null) { - throw new NullPointerException("Input is expected!"); - } - BufferedReader breader = new BufferedReader( - new InputStreamReader(input)); - try { - if (input.available() < 10) { - return false; - } - // read first 10 lines to find "Clustal" - for (int i = 0; i < 10; i++) { - String line = breader.readLine(); - if (line != null) { - line = line.toUpperCase().trim(); - if (line.contains("CLUSTAL") || line.contains("PROBCONS")) { - return true; - } + if (seqs == null || seqs.length == 0) { + throw new UnknownFileFormatException( + "Input does not appear to be a clustal file! "); } - } - - breader.close(); - } catch (IOException e) { - log.severe("Could not read from the stream! " - + e.getLocalizedMessage() + e.getCause()); - } finally { - SequenceUtil.closeSilently(log, breader); - } - return false; - } - - /** - * Write Clustal formatted alignment Limitations: does not record the - * consensus. Potential bug - records 60 chars length alignment where - * Clustal would have recorded 50 chars. - * - * @param outStream - * - * @param alignment - * @throws IOException - */ - public static void writeClustalAlignment(final OutputStream outStream, - final Alignment alignment) throws IOException { - List seqs = alignment.getSequences(); - - PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream)); - - out.write("CLUSTAL\n\n\n"); - - int max = 0; - int maxidLength = 0; - - int i = 0; - // Find the longest sequence name - for (FastaSequence fs : seqs) { - String tmp = fs.getId(); - - if (fs.getSequence().length() > max) { - max = fs.getSequence().length(); - } - if (tmp.length() > maxidLength) { - maxidLength = tmp.length(); - } - i++; - } - if (maxidLength < minNameHolderLength) { - maxidLength = minNameHolderLength; - } - if (maxidLength > maxNameLength) { - maxidLength = 30; // the rest will be trimmed + return new Alignment(Arrays.asList(seqs), new AlignmentMetadata( + Program.CLUSTAL, gapchar)); } - int oneLineAlignmentLength = 60; - int nochunks = max / oneLineAlignmentLength + 1; - - for (i = 0; i < nochunks; i++) { - int j = 0; - for (FastaSequence fs : seqs) { - - String name = fs.getId(); - // display at most 30 characters in the name, keep the names - // 6 spaces away from the alignment for longest sequence names, - // and more than this for shorter names - out.format("%-" + maxidLength + "s" + spacer, - (name.length() > maxNameLength ? name.substring(0, - maxidLength) : name)); - int start = i * oneLineAlignmentLength; - int end = start + oneLineAlignmentLength; - - if (end < fs.getSequence().length() - && start < fs.getSequence().length()) { - out.write(fs.getSequence().substring(start, end) + "\n"); - } else { - if (start < fs.getSequence().length()) { - out.write(fs.getSequence().substring(start) + "\n"); - } + /** + * Please note this method closes the input stream provided as a parameter + * + * @param input + * @return true if the file is recognised as Clustal formatted alignment, + * false otherwise + */ + public static boolean isValidClustalFile(InputStream input) { + if (input == null) { + throw new NullPointerException("Input is expected!"); } - j++; - } - out.write("\n"); - } - try { - out.close(); - } finally { - SequenceUtil.closeSilently(log, out); + BufferedReader breader = new BufferedReader( + new InputStreamReader(input)); + try { + if (input.available() < 10) { + return false; + } + // read first 10 lines to find "Clustal" + for (int i = 0; i < 10; i++) { + String line = breader.readLine(); + if (line != null) { + line = line.toUpperCase().trim(); + if (line.contains("CLUSTAL") || line.contains("PROBCONS")) { + return true; + } + } + } + + breader.close(); + } catch (IOException e) { + log.severe("Could not read from the stream! " + + e.getLocalizedMessage() + e.getCause()); + } finally { + SequenceUtil.closeSilently(log, breader); + } + return false; } - } - public static Alignment readClustalFile(File file) - throws UnknownFileFormatException, IOException { - if (file == null) { - throw new NullPointerException("File is expected!"); + /** + * Write Clustal formatted alignment Limitations: does not record the + * consensus. Potential bug - records 60 chars length alignment where + * Clustal would have recorded 50 chars. + * + * @param outStream + * + * @param alignment + * @throws IOException + */ + public static void writeClustalAlignment(final OutputStream outStream, + final Alignment alignment) throws IOException { + List seqs = alignment.getSequences(); + + PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream)); + + out.write("CLUSTAL\n\n\n"); + + int max = 0; + int maxidLength = 0; + + int i = 0; + // Find the longest sequence name + for (FastaSequence fs : seqs) { + String tmp = fs.getId(); + + if (fs.getSequence().length() > max) { + max = fs.getSequence().length(); + } + if (tmp.length() > maxidLength) { + maxidLength = tmp.length(); + } + i++; + } + if (maxidLength < minNameHolderLength) { + maxidLength = minNameHolderLength; + } + if (maxidLength > maxNameLength) { + maxidLength = 30; // the rest will be trimmed + } + + int oneLineAlignmentLength = 60; + int nochunks = max / oneLineAlignmentLength + 1; + + for (i = 0; i < nochunks; i++) { + int j = 0; + for (FastaSequence fs : seqs) { + + String name = fs.getId(); + // display at most 30 characters in the name, keep the names + // 6 spaces away from the alignment for longest sequence names, + // and more than this for shorter names + out.format("%-" + maxidLength + "s" + spacer, + (name.length() > maxNameLength ? name.substring(0, + maxidLength) : name)); + int start = i * oneLineAlignmentLength; + int end = start + oneLineAlignmentLength; + + if (end < fs.getSequence().length() + && start < fs.getSequence().length()) { + out.write(fs.getSequence().substring(start, end) + "\n"); + } else { + if (start < fs.getSequence().length()) { + out.write(fs.getSequence().substring(start) + "\n"); + } + } + j++; + } + out.write("\n"); + } + try { + out.close(); + } finally { + SequenceUtil.closeSilently(log, out); + } } - FileInputStream fio = new FileInputStream(file); - Alignment seqAl = ClustalAlignmentUtil.readClustalFile(fio); - try { - fio.close(); - } finally { - SequenceUtil.closeSilently(log, fio); + + public static Alignment readClustalFile(File file) + throws UnknownFileFormatException, IOException { + if (file == null) { + throw new NullPointerException("File is expected!"); + } + FileInputStream fio = new FileInputStream(file); + Alignment seqAl = ClustalAlignmentUtil.readClustalFile(fio); + try { + fio.close(); + } finally { + SequenceUtil.closeSilently(log, fio); + } + return seqAl; } - return seqAl; - } } diff --git a/datamodel/compbio/data/sequence/DisemblResultAnnot.java b/datamodel/compbio/data/sequence/DisemblResultAnnot.java new file mode 100644 index 0000000..c5f026c --- /dev/null +++ b/datamodel/compbio/data/sequence/DisemblResultAnnot.java @@ -0,0 +1,5 @@ +package compbio.data.sequence; + +public enum DisemblResultAnnot { + COILS, REM465, HOTLOOPS +} diff --git a/datamodel/compbio/data/sequence/FastaSequence.java b/datamodel/compbio/data/sequence/FastaSequence.java index 6072d29..2032fec 100644 --- a/datamodel/compbio/data/sequence/FastaSequence.java +++ b/datamodel/compbio/data/sequence/FastaSequence.java @@ -1,19 +1,15 @@ -/* Copyright (c) 2009 Peter Troshin - * - * JAva Bioinformatics Analysis Web Services (JABAWS) @version: 1.0 - * - * This library is free software; you can redistribute it and/or modify it under the terms of the - * Apache License version 2 as published by the Apache Software Foundation - * - * This library 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 Apache - * License for more details. - * - * A copy of the license is in apache_license.txt. It is also available here: - * @see: http://www.apache.org/licenses/LICENSE-2.0.txt - * - * Any republication or derived work distributed in source code form - * must include this copyright and license notice. +/* + * Copyright (c) 2009 Peter Troshin JAva Bioinformatics Analysis Web Services + * (JABAWS) @version: 1.0 This library is free software; you can redistribute it + * and/or modify it under the terms of the Apache License version 2 as published + * by the Apache Software Foundation This library 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 + * Apache License for more details. A copy of the license is in + * apache_license.txt. It is also available here: + * @see: http://www.apache.org/licenses/LICENSE-2.0.txt Any republication or + * derived work distributed in source code form must include this copyright and + * license notice. */ package compbio.data.sequence; @@ -42,151 +38,155 @@ import compbio.util.annotation.Immutable; @Immutable public class FastaSequence { - /** - * Sequence id - */ - private String id; - - // TODO what about gapped sequence here! should be indicated - /** - * Returns the string representation of sequence - */ - private String sequence; - - private FastaSequence() { - // Default constructor for JaxB - } - - /** - * Upon construction the any whitespace characters are removed from the - * sequence - * - * @param id - * @param sequence - */ - public FastaSequence(String id, String sequence) { - this.id = id; - this.sequence = SequenceUtil.cleanSequence(sequence); - } - - /** - * Gets the value of id - * - * @return the value of id - */ - public String getId() { - return this.id; - } - - /** - * Gets the value of sequence - * - * @return the value of sequence - */ - public String getSequence() { - return this.sequence; - } - - public static int countMatchesInSequence(final String theString, - final String theRegExp) { - final Pattern p = Pattern.compile(theRegExp); - final Matcher m = p.matcher(theString); - int cnt = 0; - while (m.find()) { - cnt++; + /** + * Sequence id + */ + private String id; + + // TODO what about gapped sequence here! should be indicated + /** + * Returns the string representation of sequence + */ + private String sequence; + + private FastaSequence() { + // Default constructor for JaxB } - return cnt; - } - - public String getFormattedFasta() { - return getFormatedSequence(80); - } - - /** - * - * @return one line name, next line sequence, no matter what the sequence - * length is - */ - public String getOnelineFasta() { - String fasta = ">" + getId() + SysPrefs.newlinechar; - fasta += getSequence() + SysPrefs.newlinechar; - return fasta; - } - - /** - * Format sequence per width letter in one string. Without spaces. - * - * @return multiple line formated sequence, one line width letters length - * - */ - public String getFormatedSequence(final int width) { - if (sequence == null) { - return ""; + + /** + * Upon construction the any whitespace characters are removed from the + * sequence + * + * @param id + * @param sequence + */ + public FastaSequence(String id, String sequence) { + this.id = id; + this.sequence = SequenceUtil.cleanSequence(sequence); } - assert width >= 0 : "Wrong width parameter "; - - final StringBuilder sb = new StringBuilder(sequence); - int nchunks = sequence.length() / width; - // add up inserted new line chars - nchunks = (nchunks + sequence.length()) / width; - int nlineCharcounter = 0; - for (int i = 1; i <= nchunks; i++) { - int insPos = width * i + nlineCharcounter; - // to prevent inserting new line in the very end of a sequence then - // it would have failed. - // Also covers the case when the sequences shorter than width - if (sb.length() <= insPos) { - break; - } - sb.insert(insPos, "\n"); - nlineCharcounter++; + /** + * Gets the value of id + * + * @return the value of id + */ + public String getId() { + return this.id; } - return sb.toString(); - } - - /** - * - * @return sequence length - */ - public int getLength() { - return this.sequence.length(); - } - - /** - * Same as oneLineFasta - */ - @Override - public String toString() { - return this.getOnelineFasta(); - } - - @Override - public int hashCode() { - final int prime = 17; - int result = 1; - result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result - + ((sequence == null) ? 0 : sequence.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; + + /** + * Gets the value of sequence + * + * @return the value of sequence + */ + public String getSequence() { + return this.sequence; } - if (!(obj instanceof FastaSequence)) { - return false; + + public static int countMatchesInSequence(final String theString, + final String theRegExp) { + final Pattern p = Pattern.compile(theRegExp); + final Matcher m = p.matcher(theString); + int cnt = 0; + while (m.find()) { + cnt++; + } + return cnt; } - FastaSequence fs = (FastaSequence) obj; - if (!fs.getId().equals(this.getId())) { - return false; + + public String getFormattedFasta() { + return getFormatedSequence(80); } - if (!fs.getSequence().equalsIgnoreCase(this.getSequence())) { - return false; + + /** + * + * @return one line name, next line sequence, no matter what the sequence + * length is + */ + public String getOnelineFasta() { + String fasta = ">" + getId() + SysPrefs.newlinechar; + fasta += getSequence() + SysPrefs.newlinechar; + return fasta; + } + + /** + * Format sequence per width letter in one string. Without spaces. + * + * @return multiple line formated sequence, one line width letters length + * + */ + public String getFormatedSequence(final int width) { + if (sequence == null) { + return ""; + } + + assert width >= 0 : "Wrong width parameter "; + + final StringBuilder sb = new StringBuilder(sequence); + // int tail = nrOfWindows % WIN_SIZE; + // final int turns = (nrOfWindows - tail) / WIN_SIZE; + + int tailLen = sequence.length() % width; + // add up inserted new line chars + int nchunks = (sequence.length() - tailLen) / width; + int nlineCharcounter = 0; + int insPos = 0; + for (int i = 1; i <= nchunks; i++) { + insPos = width * i + nlineCharcounter; + // to prevent inserting new line in the very end of a sequence then + // it would have failed. + if (sb.length() <= insPos) { + break; + } + sb.insert(insPos, "\n"); + nlineCharcounter++; + } + // sb.insert(insPos + tailLen, "\n"); + return sb.toString(); + } + + /** + * + * @return sequence length + */ + public int getLength() { + return this.sequence.length(); + } + + /** + * Same as oneLineFasta + */ + @Override + public String toString() { + return this.getOnelineFasta(); + } + + @Override + public int hashCode() { + final int prime = 17; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + + ((sequence == null) ? 0 : sequence.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof FastaSequence)) { + return false; + } + FastaSequence fs = (FastaSequence) obj; + if (!fs.getId().equals(this.getId())) { + return false; + } + if (!fs.getSequence().equalsIgnoreCase(this.getSequence())) { + return false; + } + return true; } - return true; - } } diff --git a/datamodel/compbio/data/sequence/JalviewAnnotation.java b/datamodel/compbio/data/sequence/JalviewAnnotation.java new file mode 100644 index 0000000..cf19937 --- /dev/null +++ b/datamodel/compbio/data/sequence/JalviewAnnotation.java @@ -0,0 +1,7 @@ +package compbio.data.sequence; + +public class JalviewAnnotation { + + String annotation; + +} diff --git a/datamodel/compbio/data/sequence/MultiAnnotatedSequence.java b/datamodel/compbio/data/sequence/MultiAnnotatedSequence.java index 580a22e..1a889e3 100644 --- a/datamodel/compbio/data/sequence/MultiAnnotatedSequence.java +++ b/datamodel/compbio/data/sequence/MultiAnnotatedSequence.java @@ -2,6 +2,9 @@ package compbio.data.sequence; import java.util.EnumMap; import java.util.List; +import java.util.Map; + +import compbio.util.annotation.NotThreadSafe; /** * TODO complete @@ -11,23 +14,67 @@ import java.util.List; * @param * enum type */ +@NotThreadSafe public class MultiAnnotatedSequence> { - private final EnumMap> annotation; + private final Map> annotations; + + public MultiAnnotatedSequence(Class enumeration) { + this.annotations = new EnumMap>(enumeration); + } + + public void addAnnotation(T type, List annotation) { + assert type != null : "Type is expected"; + assert annotation != null : "Not empty value is expected!"; + if (!annotations.isEmpty()) { + assert annotations.values().iterator().next().size() == annotation + .size() : "Annotations must contain the same number of elements!"; + } + this.annotations.put(type, annotation); + } + + public Map> getAnnotations() { + return new EnumMap>(this.annotations); + } - private MultiAnnotatedSequence(Class type) { - this.annotation = new EnumMap>(type); - } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((annotations == null) ? 0 : annotations.hashCode()); + return result; + } - // public MultiAnnotatedSequence getFloatInstance(FastaSequence fsequence) { - // return null; - //} + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + MultiAnnotatedSequence other = (MultiAnnotatedSequence) obj; + if (annotations == null) { + if (other.annotations != null) + return false; + } else if (!annotations.equals(other.annotations)) + return false; + return true; + } - public EnumMap> getIntegerInstance(Class enumeration) { - return new EnumMap>(enumeration); - } + @Override + public String toString() { + String value = ""; + for (Map.Entry> annt : annotations.entrySet()) { + value += annt.getKey() + " "; + value += annt.getValue() + "\n"; + } + return value; + } - public EnumMap> getFloatInstance(Class enumeration) { - return new EnumMap>(enumeration); - } + public JalviewAnnotation toJalviewAnnotation() { + // TODO Auto-generated method stub + return null; + } } diff --git a/datamodel/compbio/data/sequence/SequenceUtil.java b/datamodel/compbio/data/sequence/SequenceUtil.java index 99a8147..149e0e0 100644 --- a/datamodel/compbio/data/sequence/SequenceUtil.java +++ b/datamodel/compbio/data/sequence/SequenceUtil.java @@ -1,22 +1,15 @@ -/* - * @(#)SequenceUtil.java 1.0 September 2009 - * - * Copyright (c) 2009 Peter Troshin - * - * Jalview Web Services version: 2.0 - * - * This library is free software; you can redistribute it and/or modify it under the terms of the - * Apache License version 2 as published by the Apache Software Foundation - * - * This library 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 Apache - * License for more details. - * - * A copy of the license is in apache_license.txt. It is also available here: - * see: http://www.apache.org/licenses/LICENSE-2.0.txt - * - * Any republication or derived work distributed in source code form - * must include this copyright and license notice. +/* + * @(#)SequenceUtil.java 1.0 September 2009 Copyright (c) 2009 Peter Troshin + * Jalview Web Services version: 2.0 This library is free software; you can + * redistribute it and/or modify it under the terms of the Apache License + * version 2 as published by the Apache Software Foundation This library 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 Apache License for more details. A copy of the + * license is in apache_license.txt. It is also available here: see: + * http://www.apache.org/licenses/LICENSE-2.0.txt Any republication or derived + * work distributed in source code form must include this copyright and license + * notice. */ package compbio.data.sequence; @@ -33,6 +26,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; +import java.util.Scanner; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -45,362 +39,415 @@ import java.util.regex.Pattern; */ public final class SequenceUtil { - /** - * A whitespace character: [\t\n\x0B\f\r] - */ - public static final Pattern WHITE_SPACE = Pattern.compile("\\s"); - - /** - * A digit - */ - public static final Pattern DIGIT = Pattern.compile("\\d"); - - /** - * Non word - */ - public static final Pattern NONWORD = Pattern.compile("\\W"); - - /** - * Valid Amino acids - */ - public static final Pattern AA = Pattern.compile("[ARNDCQEGHILKMFPSTWYV]+", - Pattern.CASE_INSENSITIVE); - - /** - * inversion of AA pattern - */ - public static final Pattern NON_AA = Pattern.compile( - "[^ARNDCQEGHILKMFPSTWYV]+", Pattern.CASE_INSENSITIVE); - - /** - * Same as AA pattern but with two additional letters - XU - */ - public static final Pattern AMBIGUOUS_AA = Pattern.compile( - "[ARNDCQEGHILKMFPSTWYVXU]+", Pattern.CASE_INSENSITIVE); - - /** - * Nucleotides a, t, g, c, u - */ - public static final Pattern NUCLEOTIDE = Pattern.compile("[AGTCU]+", - Pattern.CASE_INSENSITIVE); - - /** - * Ambiguous nucleotide - */ - public static final Pattern AMBIGUOUS_NUCLEOTIDE = Pattern.compile( - "[AGTCRYMKSWHBVDNU]+", Pattern.CASE_INSENSITIVE); // see IUPAC - /** - * Non nucleotide - */ - public static final Pattern NON_NUCLEOTIDE = Pattern.compile("[^AGTCU]+", - Pattern.CASE_INSENSITIVE); - - private SequenceUtil() { - } // utility class, no instantiation - - /* - * public static void write_PirSeq(OutputStream os, FastaSequence seq) - * throws IOException { BufferedWriter pir_out = new BufferedWriter(new - * OutputStreamWriter(os)); pir_out.write(">P1;" + seq.getId() + - * SysPrefs.newlinechar); pir_out.write(seq.getSequence() + - * SysPrefs.newlinechar); pir_out.close(); } - * - * public static void write_FastaSeq(OutputStream os, FastaSequence seq) - * throws IOException { BufferedWriter fasta_out = new BufferedWriter( new - * OutputStreamWriter(os)); fasta_out.write(">" + seq.getId() + - * SysPrefs.newlinechar); fasta_out.write(seq.getSequence() + - * SysPrefs.newlinechar); fasta_out.close(); } - */ - - /** - * @return true is the sequence contains only letters a,c, t, g, u - */ - public static boolean isNucleotideSequence(final FastaSequence s) { - return SequenceUtil.isNonAmbNucleotideSequence(s.getSequence()); - } - - /** - * Ambiguous DNA chars : AGTCRYMKSWHBVDN // differs from protein in only one - * (!) - B char - */ - public static boolean isNonAmbNucleotideSequence(String sequence) { - sequence = SequenceUtil.cleanSequence(sequence); - if (SequenceUtil.DIGIT.matcher(sequence).find()) { - return false; + /** + * A whitespace character: [\t\n\x0B\f\r] + */ + public static final Pattern WHITE_SPACE = Pattern.compile("\\s"); + + /** + * A digit + */ + public static final Pattern DIGIT = Pattern.compile("\\d"); + + /** + * Non word + */ + public static final Pattern NONWORD = Pattern.compile("\\W"); + + /** + * Valid Amino acids + */ + public static final Pattern AA = Pattern.compile("[ARNDCQEGHILKMFPSTWYV]+", + Pattern.CASE_INSENSITIVE); + + /** + * inversion of AA pattern + */ + public static final Pattern NON_AA = Pattern.compile( + "[^ARNDCQEGHILKMFPSTWYV]+", Pattern.CASE_INSENSITIVE); + + /** + * Same as AA pattern but with two additional letters - XU + */ + public static final Pattern AMBIGUOUS_AA = Pattern.compile( + "[ARNDCQEGHILKMFPSTWYVXU]+", Pattern.CASE_INSENSITIVE); + + /** + * Nucleotides a, t, g, c, u + */ + public static final Pattern NUCLEOTIDE = Pattern.compile("[AGTCU]+", + Pattern.CASE_INSENSITIVE); + + /** + * Ambiguous nucleotide + */ + public static final Pattern AMBIGUOUS_NUCLEOTIDE = Pattern.compile( + "[AGTCRYMKSWHBVDNU]+", Pattern.CASE_INSENSITIVE); // see IUPAC + /** + * Non nucleotide + */ + public static final Pattern NON_NUCLEOTIDE = Pattern.compile("[^AGTCU]+", + Pattern.CASE_INSENSITIVE); + + private SequenceUtil() { + } // utility class, no instantiation + + /* + * public static void write_PirSeq(OutputStream os, FastaSequence seq) + * throws IOException { BufferedWriter pir_out = new BufferedWriter(new + * OutputStreamWriter(os)); pir_out.write(">P1;" + seq.getId() + + * SysPrefs.newlinechar); pir_out.write(seq.getSequence() + + * SysPrefs.newlinechar); pir_out.close(); } public static void + * write_FastaSeq(OutputStream os, FastaSequence seq) throws IOException { + * BufferedWriter fasta_out = new BufferedWriter( new + * OutputStreamWriter(os)); fasta_out.write(">" + seq.getId() + + * SysPrefs.newlinechar); fasta_out.write(seq.getSequence() + + * SysPrefs.newlinechar); fasta_out.close(); } + */ + + /** + * @return true is the sequence contains only letters a,c, t, g, u + */ + public static boolean isNucleotideSequence(final FastaSequence s) { + return SequenceUtil.isNonAmbNucleotideSequence(s.getSequence()); } - if (SequenceUtil.NON_NUCLEOTIDE.matcher(sequence).find()) { - return false; - /* - * System.out.format("I found the text starting at " + - * "index %d and ending at index %d.%n", nonDNAmatcher .start(), - * nonDNAmatcher.end()); - */ + + /** + * Ambiguous DNA chars : AGTCRYMKSWHBVDN // differs from protein in only one + * (!) - B char + */ + public static boolean isNonAmbNucleotideSequence(String sequence) { + sequence = SequenceUtil.cleanSequence(sequence); + if (SequenceUtil.DIGIT.matcher(sequence).find()) { + return false; + } + if (SequenceUtil.NON_NUCLEOTIDE.matcher(sequence).find()) { + return false; + /* + * System.out.format("I found the text starting at " + + * "index %d and ending at index %d.%n", nonDNAmatcher .start(), + * nonDNAmatcher.end()); + */ + } + final Matcher DNAmatcher = SequenceUtil.NUCLEOTIDE.matcher(sequence); + return DNAmatcher.find(); } - final Matcher DNAmatcher = SequenceUtil.NUCLEOTIDE.matcher(sequence); - return DNAmatcher.find(); - } - - /** - * Removes all whitespace chars in the sequence string - * - * @param sequence - * @return cleaned up sequence - */ - public static String cleanSequence(String sequence) { - assert sequence != null; - final Matcher m = SequenceUtil.WHITE_SPACE.matcher(sequence); - sequence = m.replaceAll("").toUpperCase(); - return sequence; - } - - /** - * Removes all special characters and digits as well as whitespace chars - * from the sequence - * - * @param sequence - * @return cleaned up sequence - */ - public static String deepCleanSequence(String sequence) { - sequence = SequenceUtil.cleanSequence(sequence); - sequence = SequenceUtil.DIGIT.matcher(sequence).replaceAll(""); - sequence = SequenceUtil.NONWORD.matcher(sequence).replaceAll(""); - final Pattern othernonSeqChars = Pattern.compile("[_-]+"); - sequence = othernonSeqChars.matcher(sequence).replaceAll(""); - return sequence; - } - - /** - * - * @param sequence - * @return true is the sequence is a protein sequence, false overwise - */ - public static boolean isProteinSequence(String sequence) { - sequence = SequenceUtil.cleanSequence(sequence); - if (SequenceUtil.isNonAmbNucleotideSequence(sequence)) { - return false; + + /** + * Removes all whitespace chars in the sequence string + * + * @param sequence + * @return cleaned up sequence + */ + public static String cleanSequence(String sequence) { + assert sequence != null; + final Matcher m = SequenceUtil.WHITE_SPACE.matcher(sequence); + sequence = m.replaceAll("").toUpperCase(); + return sequence; } - if (SequenceUtil.DIGIT.matcher(sequence).find()) { - return false; + + /** + * Removes all special characters and digits as well as whitespace chars + * from the sequence + * + * @param sequence + * @return cleaned up sequence + */ + public static String deepCleanSequence(String sequence) { + sequence = SequenceUtil.cleanSequence(sequence); + sequence = SequenceUtil.DIGIT.matcher(sequence).replaceAll(""); + sequence = SequenceUtil.NONWORD.matcher(sequence).replaceAll(""); + final Pattern othernonSeqChars = Pattern.compile("[_-]+"); + sequence = othernonSeqChars.matcher(sequence).replaceAll(""); + return sequence; } - if (SequenceUtil.NON_AA.matcher(sequence).find()) { - return false; + + /** + * @param sequence + * @return true is the sequence is a protein sequence, false overwise + */ + public static boolean isProteinSequence(String sequence) { + sequence = SequenceUtil.cleanSequence(sequence); + if (SequenceUtil.isNonAmbNucleotideSequence(sequence)) { + return false; + } + if (SequenceUtil.DIGIT.matcher(sequence).find()) { + return false; + } + if (SequenceUtil.NON_AA.matcher(sequence).find()) { + return false; + } + final Matcher protmatcher = SequenceUtil.AA.matcher(sequence); + return protmatcher.find(); } - final Matcher protmatcher = SequenceUtil.AA.matcher(sequence); - return protmatcher.find(); - } - - /** - * Check whether the sequence confirms to amboguous protein sequence - * - * @param sequence - * @return return true only if the sequence if ambiguous protein sequence - * Return false otherwise. e.g. if the sequence is non-ambiguous - * protein or DNA - */ - public static boolean isAmbiguosProtein(String sequence) { - sequence = SequenceUtil.cleanSequence(sequence); - if (SequenceUtil.isNonAmbNucleotideSequence(sequence)) { - return false; + + /** + * Check whether the sequence confirms to amboguous protein sequence + * + * @param sequence + * @return return true only if the sequence if ambiguous protein sequence + * Return false otherwise. e.g. if the sequence is non-ambiguous + * protein or DNA + */ + public static boolean isAmbiguosProtein(String sequence) { + sequence = SequenceUtil.cleanSequence(sequence); + if (SequenceUtil.isNonAmbNucleotideSequence(sequence)) { + return false; + } + if (SequenceUtil.DIGIT.matcher(sequence).find()) { + return false; + } + if (SequenceUtil.NON_AA.matcher(sequence).find()) { + return false; + } + if (SequenceUtil.AA.matcher(sequence).find()) { + return false; + } + final Matcher amb_prot = SequenceUtil.AMBIGUOUS_AA.matcher(sequence); + return amb_prot.find(); } - if (SequenceUtil.DIGIT.matcher(sequence).find()) { - return false; + + /** + * Writes list of FastaSequeces into the outstream formatting the sequence + * so that it contains width chars on each line + * + * @param outstream + * @param sequences + * @param width + * - the maximum number of characters to write in one line + * @throws IOException + */ + public static void writeFasta(final OutputStream outstream, + final List sequences, final int width) + throws IOException { + writeFastaKeepTheStream(outstream, sequences, width); + outstream.close(); } - if (SequenceUtil.NON_AA.matcher(sequence).find()) { - return false; + + public static void writeFastaKeepTheStream(final OutputStream outstream, + final List sequences, final int width) + throws IOException { + final OutputStreamWriter writer = new OutputStreamWriter(outstream); + final BufferedWriter fastawriter = new BufferedWriter(writer); + for (final FastaSequence fs : sequences) { + fastawriter.write(">" + fs.getId() + "\n"); + fastawriter.write(fs.getFormatedSequence(width)); + fastawriter.write("\n"); + } + fastawriter.flush(); + writer.flush(); + } + + /** + * Reads fasta sequences from inStream into the list of FastaSequence + * objects + * + * @param inStream + * from + * @return list of FastaSequence objects + * @throws IOException + */ + public static List readFasta(final InputStream inStream) + throws IOException { + final List seqs = new ArrayList(); + + final BufferedReader infasta = new BufferedReader( + new InputStreamReader(inStream, "UTF8"), 16000); + final Pattern pattern = Pattern.compile("//s+"); + + String line; + String sname = "", seqstr = null; + do { + line = infasta.readLine(); + if ((line == null) || line.startsWith(">")) { + if (seqstr != null) { + seqs.add(new FastaSequence(sname.substring(1), seqstr)); + } + sname = line; // remove > + seqstr = ""; + } else { + final String subseq = pattern.matcher(line).replaceAll(""); + seqstr += subseq; + } + } while (line != null); + + infasta.close(); + return seqs; } - if (SequenceUtil.AA.matcher(sequence).find()) { - return false; + + /** + * Writes FastaSequence in the file, each sequence will take one line only + * + * @param os + * @param sequences + * @throws IOException + */ + public static void writeFasta(final OutputStream os, + final List sequences) throws IOException { + final OutputStreamWriter outWriter = new OutputStreamWriter(os); + final BufferedWriter fasta_out = new BufferedWriter(outWriter); + for (final FastaSequence fs : sequences) { + fasta_out.write(fs.getOnelineFasta()); + } + fasta_out.close(); + outWriter.close(); + } + + public static List readJRonn(final File result) + throws IOException, UnknownFileFormatException { + InputStream input = new FileInputStream(result); + List sequences = readJRonn(input); + input.close(); + return sequences; } - final Matcher amb_prot = SequenceUtil.AMBIGUOUS_AA.matcher(sequence); - return amb_prot.find(); - } - - /** - * Writes list of FastaSequeces into the outstream formatting the sequence - * so that it contains width chars on each line - * - * @param outstream - * @param sequences - * @param width - * - the maximum number of characters to write in one line - * @throws IOException - */ - public static void writeFasta(final OutputStream outstream, - final List sequences, final int width) - throws IOException { - final OutputStreamWriter writer = new OutputStreamWriter(outstream); - final BufferedWriter fastawriter = new BufferedWriter(writer); - for (final FastaSequence fs : sequences) { - fastawriter.write(fs.getFormatedSequence(width)); + + /** + * Reader for JRonn horizontal file format >Foobar M G D T T A G 0.48 0.42 + * 0.42 0.48 0.52 0.53 0.54 All values are tab delimited + * + * @param inStream + * @return + * @throws IOException + * @throws UnknownFileFormatException + */ + public static List readJRonn(final InputStream inStream) + throws IOException, UnknownFileFormatException { + final List seqs = new ArrayList(); + + final BufferedReader infasta = new BufferedReader( + new InputStreamReader(inStream, "UTF8"), 16000); + + String line; + String sname = ""; + do { + line = infasta.readLine(); + if (line == null || line.isEmpty()) { + // skip empty lines + continue; + } + if (line.startsWith(">")) { + // read name + sname = line.trim().substring(1); + // read sequence line + line = infasta.readLine(); + final String sequence = line.replace("\t", ""); + // read annotation line + line = infasta.readLine(); + String[] annotValues = line.split("\t"); + float[] annotation = convertToNumber(annotValues); + if (annotation.length != sequence.length()) { + throw new UnknownFileFormatException( + "File does not look like Jronn horizontally formatted output file!\n" + + JRONN_WRONG_FORMAT_MESSAGE); + } + seqs.add(new AnnotatedSequence(sname, sequence, annotation)); + } + } while (line != null); + + infasta.close(); + return seqs; } - outstream.flush(); - fastawriter.close(); - writer.close(); - } - - /** - * Reads fasta sequences from inStream into the list of FastaSequence - * objects - * - * @param inStream - * from - * @return list of FastaSequence objects - * @throws IOException - */ - public static List readFasta(final InputStream inStream) - throws IOException { - final List seqs = new ArrayList(); - - final BufferedReader infasta = new BufferedReader( - new InputStreamReader(inStream, "UTF8"), 16000); - final Pattern pattern = Pattern.compile("//s+"); - - String line; - String sname = "", seqstr = null; - do { - line = infasta.readLine(); - if ((line == null) || line.startsWith(">")) { - if (seqstr != null) { - seqs.add(new FastaSequence(sname.substring(1), seqstr)); + + private static float[] convertToNumber(String[] annotValues) + throws UnknownFileFormatException { + float[] annotation = new float[annotValues.length]; + try { + for (int i = 0; i < annotation.length; i++) { + annotation[i] = Float.parseFloat(annotValues[i]); + } + } catch (NumberFormatException e) { + throw new UnknownFileFormatException(JRONN_WRONG_FORMAT_MESSAGE, + e.getCause()); } - sname = line; // remove > - seqstr = ""; - } else { - final String subseq = pattern.matcher(line).replaceAll(""); - seqstr += subseq; - } - } while (line != null); - - infasta.close(); - return seqs; - } - - /** - * Writes FastaSequence in the file, each sequence will take one line only - * - * @param os - * @param sequences - * @throws IOException - */ - public static void writeFasta(final OutputStream os, - final List sequences) throws IOException { - final OutputStreamWriter outWriter = new OutputStreamWriter(os); - final BufferedWriter fasta_out = new BufferedWriter(outWriter); - for (final FastaSequence fs : sequences) { - fasta_out.write(fs.getOnelineFasta()); + return annotation; } - fasta_out.close(); - outWriter.close(); - } - - public static List readJRonn(final File result) - throws IOException, UnknownFileFormatException { - InputStream input = new FileInputStream(result); - List sequences = readJRonn(input); - input.close(); - return sequences; - } - - /** - * Reader for JRonn horizontal file format - * - * >Foobar - * - * M G D T T A G - * - * 0.48 0.42 0.42 0.48 0.52 0.53 0.54 - * - * All values are tab delimited - * - * @param inStream - * @return - * @throws IOException - * @throws UnknownFileFormatException - */ - public static List readJRonn(final InputStream inStream) - throws IOException, UnknownFileFormatException { - final List seqs = new ArrayList(); - - final BufferedReader infasta = new BufferedReader( - new InputStreamReader(inStream, "UTF8"), 16000); - - String line; - String sname = ""; - do { - line = infasta.readLine(); - if (line == null || line.isEmpty()) { - // skip empty lines - continue; - } - if (line.startsWith(">")) { - // read name - sname = line.trim().substring(1); - // read sequence line - line = infasta.readLine(); - final String sequence = line.replace("\t", ""); - // read annotation line - line = infasta.readLine(); - String[] annotValues = line.split("\t"); - float[] annotation = convertToNumber(annotValues); - if (annotation.length != sequence.length()) { - throw new UnknownFileFormatException( - "File does not look like Jronn horizontally formatted output file!\n" - + JRONN_WRONG_FORMAT_MESSAGE); + + private static final String JRONN_WRONG_FORMAT_MESSAGE = "Jronn file must be in the following format:\n" + + ">sequence_name\n " + + "M V S\n" + + "0.43 0.22 0.65\n" + + "Where first line is the sequence name,\n" + + "second line is the tab delimited sequence,\n" + + "third line contains tab delimited disorder prediction values.\n" + + "No lines are allowed between these three. Additionally, the number of " + + "sequence residues must be equal to the number of the disorder values."; + + /** + * Closes the Closable and logs the exception if any + * + * @param log + * @param stream + */ + public final static void closeSilently(java.util.logging.Logger log, + Closeable stream) { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + log.log(Level.WARNING, e.getLocalizedMessage(), e.getCause()); + } } - seqs.add(new AnnotatedSequence(sname, sequence, annotation)); - } - } while (line != null); - - infasta.close(); - return seqs; - } - - private static float[] convertToNumber(String[] annotValues) - throws UnknownFileFormatException { - float[] annotation = new float[annotValues.length]; - try { - for (int i = 0; i < annotation.length; i++) { - annotation[i] = Float.parseFloat(annotValues[i]); - } - } catch (NumberFormatException e) { - throw new UnknownFileFormatException(JRONN_WRONG_FORMAT_MESSAGE, e - .getCause()); } - return annotation; - } - - private static final String JRONN_WRONG_FORMAT_MESSAGE = "Jronn file must be in the following format:\n" - + ">sequence_name\n " - + "M V S\n" - + "0.43 0.22 0.65\n" - + "Where first line is the sequence name,\n" - + "second line is the tab delimited sequence,\n" - + "third line contains tab delimited disorder prediction values.\n" - + "No lines are allowed between these three. Additionally, the number of " - + "sequence residues must be equal to the number of the disorder values."; - - /** - * Closes the Closable and logs the exception if any - * - * @param log - * @param stream - */ - public final static void closeSilently(java.util.logging.Logger log, - Closeable stream) { - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - log.log(Level.WARNING, e.getLocalizedMessage(), e.getCause()); - } + + /** + * + * TODO complete! + * + * # RESIDUE COILS REM465 HOTLOOPS M 0.86010 0.88512 0.37094 T 0.79983 + * 0.85864 0.44331 .... # RESIDUE COILS REM465 HOTLOOPS M 0.86010 0.88512 + * 0.37094 + * + * @param input + * @return + * @throws IOException + * @throws UnknownFileFormatException + */ + public static List> readDisembl( + final InputStream input) throws IOException, + UnknownFileFormatException { + Scanner scan = new Scanner(input); + scan.useDelimiter("# RESIDUE COILS REM465 HOTLOOPS\n"); + if (!scan.hasNext()) { + throw new UnknownFileFormatException( + "In Disembl score format each seqeunce score is expected to start from the line: " + + "'# RESIDUE COILS REM465 HOTLOOPS\\n'." + + " No such line was found!"); + } + + List> results = new ArrayList>(); + int seqCounter = 0; + while (scan.hasNext()) { + seqCounter++; + String singleSeq = scan.next(); + Scanner scansingle = new Scanner(singleSeq); + StringBuffer seqbuffer = new StringBuffer(); + List coils = new ArrayList(); + List rem = new ArrayList(); + List hotloops = new ArrayList(); + + MultiAnnotatedSequence disemblRes = new MultiAnnotatedSequence( + DisemblResultAnnot.class); + + while (scansingle.hasNextLine()) { + String valueLine = scansingle.nextLine(); + Scanner values = new Scanner(valueLine); + seqbuffer.append(values.next()); + coils.add(values.nextFloat()); + rem.add(values.nextFloat()); + hotloops.add(values.nextFloat()); + values.close(); + } + disemblRes.addAnnotation(DisemblResultAnnot.COILS, coils); + disemblRes.addAnnotation(DisemblResultAnnot.REM465, rem); + disemblRes.addAnnotation(DisemblResultAnnot.HOTLOOPS, hotloops); + // TODO + // disemblRes.sequence = seqbuffer.toString(); + scansingle.close(); + results.add(disemblRes); + } + + input.close(); + return results; } - } - - public static List readDisembl(final File result) - throws IOException, UnknownFileFormatException { - InputStream input = new FileInputStream(result); - List sequences = readJRonn(input); - input.close(); - return sequences; - } + } diff --git a/runner/compbio/runner/conservation/AACon.java b/runner/compbio/runner/conservation/AACon.java new file mode 100644 index 0000000..5d20d5d --- /dev/null +++ b/runner/compbio/runner/conservation/AACon.java @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2009 Peter Troshin JAva Bioinformatics Analysis Web Services + * (JABAWS) @version: 1.0 This library is free software; you can redistribute it + * and/or modify it under the terms of the Apache License version 2 as published + * by the Apache Software Foundation This library 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 + * Apache License for more details. A copy of the license is in + * apache_license.txt. It is also available here: + * @see: http://www.apache.org/licenses/LICENSE-2.0.txt Any republication or + * derived work distributed in source code form must include this copyright and + * license notice. + */ + +package compbio.runner.conservation; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + +import org.apache.log4j.Logger; + +import compbio.conservation.Method; +import compbio.conservation.ResultReader; +import compbio.data.sequence.MultiAnnotatedSequence; +import compbio.engine.client.CommandBuilder; +import compbio.engine.client.Executable; +import compbio.engine.client.SkeletalExecutable; +import compbio.metadata.Limit; +import compbio.metadata.LimitsManager; +import compbio.metadata.ResultNotAvailableException; +import compbio.runner.Util; + +/** + * Command line + * + * java -Xmx512 -jar jronn_v3.jar -i=test_seq.txt -n=1 -o=out.txt -s=stat.out + * + * @author pvtroshin + * + */ +public class AACon extends SkeletalExecutable { + + private static Logger log = Logger.getLogger(AACon.class); + + /** + * Number of cores to use, defaults to 1 for local execution or the value of + * "jronn.cluster.cpunum" property for cluster execution + */ + private int ncoreNumber = 0; + + private final String ncorePrm = "-n="; + + // Cache for Limits information + private static LimitsManager limits; + + public static final String KEY_VALUE_SEPARATOR = Util.SPACE; + public static final String STAT_FILE = "stat.txt"; + + public AACon() { + addParameters(Arrays.asList("-jar", getLibPath(), "-d=" + STAT_FILE, + "-f=RESULT_NO_ALIGNMENT")); + } + + @SuppressWarnings("unchecked") + @Override + public MultiAnnotatedSequence getResults(String workDirectory) + throws ResultNotAvailableException { + MultiAnnotatedSequence annotations = null; + try { + InputStream inStream = new FileInputStream(new File(workDirectory, + getOutput())); + annotations = ResultReader.readResults(inStream); + inStream.close(); + } catch (FileNotFoundException e) { + log.error(e.getMessage(), e.getCause()); + throw new ResultNotAvailableException(e); + } catch (IOException e) { + log.error(e.getMessage(), e.getCause()); + throw new ResultNotAvailableException(e); + } catch (NullPointerException e) { + log.error(e.getMessage(), e.getCause()); + throw new ResultNotAvailableException(e); + } + return annotations; + } + + private static String getLibPath() { + + String settings = ph.getProperty("aacon.jar.file"); + if (compbio.util.Util.isEmpty(settings)) { + throw new NullPointerException( + "Please define aacon.jar.file property in Executable.properties file" + + "and initialize it with the location of jronn jar file"); + } + if (new File(settings).isAbsolute()) { + // Jronn jar can be found so no actions necessary + // no further actions is necessary + return settings; + } + return compbio.engine.client.Util.convertToAbsolute(settings); + } + + @Override + public List getCreatedFiles() { + return Arrays.asList(getOutput(), getError()); + } + + @Override + public AACon setInput(String inFile) { + super.setInput(inFile); + cbuilder.setParam("-i=" + inFile); + return this; + } + + @Override + public AACon setOutput(String outFile) { + super.setOutput(outFile); + cbuilder.setParam("-o=" + outFile); + return this; + } + + @Override + public Limit getLimit(String presetName) { + if (limits == null) { + limits = getLimits(); + } + Limit limit = null; + if (limits != null) { + // this returns default limit if preset is undefined! + limit = limits.getLimitByName(presetName); + } + // If limit is not defined for a particular preset, then return default + // limit + if (limit == null) { + log.debug("Limit for the preset " + presetName + + " is not found. Using default"); + limit = limits.getDefaultLimit(); + } + return limit; + } + + @Override + public LimitsManager getLimits() { + // synchronise on static field + synchronized (log) { + if (limits == null) { + limits = Util.getLimits(this.getClass()); + } + } + return limits; + } + + @Override + public Class> getType() { + return this.getClass(); + } + + public static String getStatFile() { + return STAT_FILE; + } + + public void setNCore(int ncoreNumber) { + if (ncoreNumber < 1 || ncoreNumber > 100) { + throw new IndexOutOfBoundsException( + "Number of cores must be within 1 and 100 "); + } + this.ncoreNumber = ncoreNumber; + cbuilder.setParam(ncorePrm + Integer.toString(getNCore())); + } + + int getNCore() { + return ncoreNumber; + } + + @Override + public CommandBuilder getParameters(ExecProvider provider) { + // If number of cores is provided, set it for the cluster execution + // only! + if (provider == Executable.ExecProvider.Cluster) { + int cpunum = SkeletalExecutable.getClusterCpuNum(getType()); + cpunum = (cpunum == 0) ? 1 : cpunum; + setNCore(cpunum); + } else { + // Limit number of cores to 1 for ANY execution which does not set + // Ncores explicitly using setNCore method or is run on local VM + if (ncoreNumber == 0) { + setNCore(1); + } + } + return super.getParameters(provider); + } + +} diff --git a/runner/compbio/runner/disorder/Disembl.java b/runner/compbio/runner/disorder/Disembl.java index 4c76854..c6b278f 100644 --- a/runner/compbio/runner/disorder/Disembl.java +++ b/runner/compbio/runner/disorder/Disembl.java @@ -1,19 +1,15 @@ -/* Copyright (c) 2009 Peter Troshin - * - * JAva Bioinformatics Analysis Web Services (JABAWS) @version: 1.0 - * - * This library is free software; you can redistribute it and/or modify it under the terms of the - * Apache License version 2 as published by the Apache Software Foundation - * - * This library 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 Apache - * License for more details. - * - * A copy of the license is in apache_license.txt. It is also available here: - * @see: http://www.apache.org/licenses/LICENSE-2.0.txt - * - * Any republication or derived work distributed in source code form - * must include this copyright and license notice. +/* + * Copyright (c) 2009 Peter Troshin JAva Bioinformatics Analysis Web Services + * (JABAWS) @version: 1.0 This library is free software; you can redistribute it + * and/or modify it under the terms of the Apache License version 2 as published + * by the Apache Software Foundation This library 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 + * Apache License for more details. A copy of the license is in + * apache_license.txt. It is also available here: + * @see: http://www.apache.org/licenses/LICENSE-2.0.txt Any republication or + * derived work distributed in source code form must include this copyright and + * license notice. */ package compbio.runner.disorder; @@ -24,7 +20,6 @@ import java.util.Arrays; import org.apache.log4j.Logger; -import com.sun.xml.internal.bind.api.impl.NameConverter.Standard; import compbio.data.sequence.Alignment; import compbio.data.sequence.UnknownFileFormatException; import compbio.engine.client.Executable; @@ -36,98 +31,101 @@ import compbio.metadata.ResultNotAvailableException; import compbio.runner.Util; /** - * @see Standard DisEMBL DisEMBL.py smooth_frame peak_frame join_frame - * fold_coils fold_hotloops fold_rem465 sequence_file print 'A default run - * would be: ./DisEMBL.py 8 8 4 1.2 1.4 1.2 fasta_file > out' + * @see DisEMBL * - * new DisEMBL is at /homes/pvtroshin/soft/DisEMBL-1.4raw This is not a - * standard DisEMBL! The script has been modified! DisEMBL.py smooth_frame - * peak_frame join_frame fold_coils fold_hotloops fold_rem465 [mode] < - * fasta_file > out print 'A default run would be: ./DisEMBL.py 8 8 4 1.2 - * 1.4 1.2 < fasta_file' print 'Mode: "default"(nothing) or "scores" which - * will give scores per residue in TAB separated format' + * DisEMBL.py smooth_frame peak_frame join_frame fold_coils fold_hotloops + * fold_rem465 sequence_file print 'A default run would be: ./DisEMBL.py 8 + * 8 4 1.2 1.4 1.2 fasta_file > out' new DisEMBL is at + * /homes/pvtroshin/soft/DisEMBL-1.4raw * + * This is not a standard DisEMBL! The script has been modified! DisEMBL.py + * smooth_frame peak_frame join_frame fold_coils fold_hotloops fold_rem465 + * [mode] < fasta_file > out print + * + * 'A default run would be: ./DisEMBL.py 8 8 4 1.2 1.4 1.2 < fasta_file' + * print 'Mode: "default"(nothing) or "scores" which will give scores per + * residue in TAB separated format' */ public class Disembl extends SkeletalExecutable implements - PipedExecutable { - - private static Logger log = Logger.getLogger(Disembl.class); - - // Cache for Limits information - private static LimitsManager limits; - - public static final String KEY_VALUE_SEPARATOR = Util.SPACE; - - public Disembl() { - // remove default input to prevent it to appear in the parameters list - // that could happen if the parameters are set first - // super.setInput(""); - addParameters(Arrays.asList("8", "8", "4", "1.2", "1.4", "1.2", - "scores")); - } - - @SuppressWarnings("unchecked") - public Alignment getResults(String workDirectory) - throws ResultNotAvailableException { - try { - return Util.readClustalFile(workDirectory, getOutput()); - } catch (FileNotFoundException e) { - log.error(e.getMessage(), e.getCause()); - throw new ResultNotAvailableException(e); - } catch (IOException e) { - log.error(e.getMessage(), e.getCause()); - throw new ResultNotAvailableException(e); - } catch (UnknownFileFormatException e) { - log.error(e.getMessage(), e.getCause()); - throw new ResultNotAvailableException(e); - } catch (NullPointerException e) { - log.error(e.getMessage(), e.getCause()); - throw new ResultNotAvailableException(e); + PipedExecutable { + + private static Logger log = Logger.getLogger(Disembl.class); + + // Cache for Limits information + private static LimitsManager limits; + + public static final String KEY_VALUE_SEPARATOR = Util.SPACE; + + public Disembl() { + // remove default input to prevent it to appear in the parameters list + // that could happen if the parameters are set first + // super.setInput(""); + addParameters(Arrays.asList("8", "8", "4", "1.2", "1.4", "1.2", + "scores")); } - } - - @Override - public Disembl setInput(String inFile) { - super.setInput(inFile); - cbuilder.setLast(inFile); - return this; - } - - @Override - public Limit getLimit(String presetName) { - if (limits == null) { - limits = getLimits(); + + @SuppressWarnings("unchecked") + public Alignment getResults(String workDirectory) + throws ResultNotAvailableException { + try { + return Util.readClustalFile(workDirectory, getOutput()); + } catch (FileNotFoundException e) { + log.error(e.getMessage(), e.getCause()); + throw new ResultNotAvailableException(e); + } catch (IOException e) { + log.error(e.getMessage(), e.getCause()); + throw new ResultNotAvailableException(e); + } catch (UnknownFileFormatException e) { + log.error(e.getMessage(), e.getCause()); + throw new ResultNotAvailableException(e); + } catch (NullPointerException e) { + log.error(e.getMessage(), e.getCause()); + throw new ResultNotAvailableException(e); + } } - Limit limit = null; - if (limits != null) { - // this returns default limit if preset is undefined! - limit = limits.getLimitByName(presetName); + @Override + public Disembl setInput(String inFile) { + super.setInput(inFile); + cbuilder.setLast(inFile); + return this; } - // If limit is not defined for a particular preset, then return default - // limit - if (limit == null) { - log.debug("Limit for the preset " + presetName - + " is not found. Using default"); - limit = limits.getDefaultLimit(); + + @Override + public Limit getLimit(String presetName) { + if (limits == null) { + limits = getLimits(); + } + + Limit limit = null; + if (limits != null) { + // this returns default limit if preset is undefined! + limit = limits.getLimitByName(presetName); + } + // If limit is not defined for a particular preset, then return default + // limit + if (limit == null) { + log.debug("Limit for the preset " + presetName + + " is not found. Using default"); + limit = limits.getDefaultLimit(); + } + return limit; } - return limit; - } - - @Override - public LimitsManager getLimits() { - // synchronise on static field - synchronized (log) { - if (limits == null) { - limits = Util.getLimits(this.getClass()); - } + + @Override + public LimitsManager getLimits() { + // synchronise on static field + synchronized (log) { + if (limits == null) { + limits = Util.getLimits(this.getClass()); + } + } + return limits; } - return limits; - } - @Override - public Class> getType() { - return this.getClass(); - } + @Override + public Class> getType() { + return this.getClass(); + } } diff --git a/testsrc/compbio/data/sequence/SequenceUtilTester.java b/testsrc/compbio/data/sequence/SequenceUtilTester.java index f2af670..720037b 100644 --- a/testsrc/compbio/data/sequence/SequenceUtilTester.java +++ b/testsrc/compbio/data/sequence/SequenceUtilTester.java @@ -1,21 +1,16 @@ -/* Copyright (c) 2009 Peter Troshin - * - * JAva Bioinformatics Analysis Web Services (JABAWS) @version: 1.0 - * - * This library is free software; you can redistribute it and/or modify it under the terms of the - * Apache License version 2 as published by the Apache Software Foundation - * - * This library 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 Apache - * License for more details. - * - * A copy of the license is in apache_license.txt. It is also available here: - * @see: http://www.apache.org/licenses/LICENSE-2.0.txt - * - * Any republication or derived work distributed in source code form - * must include this copyright and license notice. +/* + * Copyright (c) 2009 Peter Troshin JAva Bioinformatics Analysis Web Services + * (JABAWS) @version: 1.0 This library is free software; you can redistribute it + * and/or modify it under the terms of the Apache License version 2 as published + * by the Apache Software Foundation This library 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 + * Apache License for more details. A copy of the license is in + * apache_license.txt. It is also available here: + * @see: http://www.apache.org/licenses/LICENSE-2.0.txt Any republication or + * derived work distributed in source code form must include this copyright and + * license notice. */ - package compbio.data.sequence; import static org.testng.AssertJUnit.assertEquals; @@ -36,112 +31,152 @@ import compbio.metadata.AllTestSuit; public class SequenceUtilTester { - @Test() - public void testisNonAmbNucleotideSequence() { - String dnaseq = "atgatTGACGCTGCTGatgtcgtgagtgga"; - assertTrue(SequenceUtil.isNonAmbNucleotideSequence(dnaseq)); - String dirtyDnaseq = "atgAGTggt\taGGTgc\ncgcACTgc gACtcgcGAt cgA "; - assertTrue(SequenceUtil.isNonAmbNucleotideSequence(dirtyDnaseq)); - String nonDna = "atgfctgatgcatgcatgatgctga"; - assertFalse(SequenceUtil.isNonAmbNucleotideSequence(nonDna)); - - nonDna = "atgc1tgatgcatgcatgatgctga"; - assertFalse(SequenceUtil.isNonAmbNucleotideSequence(nonDna)); - - nonDna = "ARLGRVRWTQQRHAEAAVLLQQASDAAPEHPGIALWLGHALEDAGQAEAAAAAYTRAHQL"; - assertFalse(SequenceUtil.isNonAmbNucleotideSequence(nonDna)); - // String ambDna = "AGTCRYMKSWHBVDN"; // see IUPAC Nucleotide Code - assertFalse(SequenceUtil.isNonAmbNucleotideSequence(nonDna)); - - } - - @Test() - public void testCleanSequence() { - String dirtySeq = "atgAGTggt\taGGTgc\ncgcAC\rTgc gACtcgcGAt cgA "; - assertEquals("atgAGTggtaGGTgccgcACTgcgACtcgcGAtcgA".toUpperCase(), - SequenceUtil.cleanSequence(dirtySeq)); - } - - @Test() - public void testDeepCleanSequence() { - String dirtySeq = "a!t?g.A;GTggt\ta12GGTgc\ncgc23AC\rTgc gAC<>.,?!|\\|/t@cg-c¬GA=_+(0){]}[:£$&^*\"t cgA "; - assertEquals("atgAGTggtaGGTgccgcACTgcgACtcgcGAtcgA".toUpperCase(), - SequenceUtil.deepCleanSequence(dirtySeq)); - } - - @Test() - public void testisProteinSequence() { - String dirtySeq = "atgAGTggt\taGGTgc\ncgcAC\rTgc gACtcgcGAt cgA "; - assertFalse(SequenceUtil.isProteinSequence(dirtySeq)); - String notaSeq = "atgc1tgatgcatgcatgatgctga"; - assertFalse(SequenceUtil.isProteinSequence(notaSeq)); - String AAseq = "ARLGRVRWTQQRHAEAAVLLQQASDAAPEHPGIALWLGHALEDAGQAEAAAAAYTRAHQL"; - assertTrue(SequenceUtil.isProteinSequence(AAseq)); - AAseq += "XU"; - assertFalse(SequenceUtil.isProteinSequence(AAseq)); - - } - - @Test() - public void testReadWriteFasta() { - - try { - FileInputStream fio = new FileInputStream( - AllTestSuit.TEST_DATA_PATH + "TO1381.fasta"); - assertNotNull(fio); - List fseqs = SequenceUtil.readFasta(fio); - assertNotNull(fseqs); - assertEquals(3, fseqs.size()); - assertEquals(3, fseqs.size()); - fio.close(); - FileOutputStream fou = new FileOutputStream( - AllTestSuit.TEST_DATA_PATH + "TO1381.fasta.written"); - SequenceUtil.writeFasta(fou, fseqs); - fou.close(); - FileOutputStream fou20 = new FileOutputStream( - AllTestSuit.TEST_DATA_PATH + "TO1381.fasta20.written"); - SequenceUtil.writeFasta(fou20, fseqs, 20); - fou20.close(); - - } catch (FileNotFoundException e) { - e.printStackTrace(); - fail(e.getLocalizedMessage()); - } catch (IOException e) { - e.printStackTrace(); - fail(e.getLocalizedMessage()); + @Test() + public void testisNonAmbNucleotideSequence() { + String dnaseq = "atgatTGACGCTGCTGatgtcgtgagtgga"; + assertTrue(SequenceUtil.isNonAmbNucleotideSequence(dnaseq)); + String dirtyDnaseq = "atgAGTggt\taGGTgc\ncgcACTgc gACtcgcGAt cgA "; + assertTrue(SequenceUtil.isNonAmbNucleotideSequence(dirtyDnaseq)); + String nonDna = "atgfctgatgcatgcatgatgctga"; + assertFalse(SequenceUtil.isNonAmbNucleotideSequence(nonDna)); + + nonDna = "atgc1tgatgcatgcatgatgctga"; + assertFalse(SequenceUtil.isNonAmbNucleotideSequence(nonDna)); + + nonDna = "ARLGRVRWTQQRHAEAAVLLQQASDAAPEHPGIALWLGHALEDAGQAEAAAAAYTRAHQL"; + assertFalse(SequenceUtil.isNonAmbNucleotideSequence(nonDna)); + // String ambDna = "AGTCRYMKSWHBVDN"; // see IUPAC Nucleotide Code + assertFalse(SequenceUtil.isNonAmbNucleotideSequence(nonDna)); + + } + + @Test() + public void testCleanSequence() { + String dirtySeq = "atgAGTggt\taGGTgc\ncgcAC\rTgc gACtcgcGAt cgA "; + assertEquals("atgAGTggtaGGTgccgcACTgcgACtcgcGAtcgA".toUpperCase(), + SequenceUtil.cleanSequence(dirtySeq)); + } + + @Test() + public void testDeepCleanSequence() { + String dirtySeq = "a!t?g.A;GTggt\ta12GGTgc\ncgc23AC\rTgc gAC<>.,?!|\\|/t@cg-c¬GA=_+(0){]}[:£$&^*\"t cgA "; + assertEquals("atgAGTggtaGGTgccgcACTgcgACtcgcGAtcgA".toUpperCase(), + SequenceUtil.deepCleanSequence(dirtySeq)); } - } - - /** - * This test tests the loading of horizontally formatted Jronn output file - */ - @Test - public void loadJronnFile() { - - FileInputStream fio; - try { - fio = new FileInputStream(AllTestSuit.TEST_DATA_PATH + "jronn.out"); - List aseqs = SequenceUtil.readJRonn(fio); - assertNotNull(aseqs); - assertEquals(aseqs.size(), 3); - AnnotatedSequence aseq = aseqs.get(0); - assertNotNull(aseq); - assertNotNull(aseq.getAnnotation()); - //System.out.println(aseq); - assertEquals(aseq.getAnnotation().length, aseq.getSequence() - .length()); - fio.close(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - fail(e.getLocalizedMessage()); - } catch (IOException e) { - e.printStackTrace(); - fail(e.getLocalizedMessage()); - } catch (UnknownFileFormatException e) { - e.printStackTrace(); - fail(e.getLocalizedMessage()); + + @Test() + public void testisProteinSequence() { + String dirtySeq = "atgAGTggt\taGGTgc\ncgcAC\rTgc gACtcgcGAt cgA "; + assertFalse(SequenceUtil.isProteinSequence(dirtySeq)); + String notaSeq = "atgc1tgatgcatgcatgatgctga"; + assertFalse(SequenceUtil.isProteinSequence(notaSeq)); + String AAseq = "ARLGRVRWTQQRHAEAAVLLQQASDAAPEHPGIALWLGHALEDAGQAEAAAAAYTRAHQL"; + assertTrue(SequenceUtil.isProteinSequence(AAseq)); + AAseq += "XU"; + assertFalse(SequenceUtil.isProteinSequence(AAseq)); + + } + + @Test() + public void testReadWriteFasta() { + + try { + FileInputStream fio = new FileInputStream( + AllTestSuit.TEST_DATA_PATH + "TO1381.fasta"); + assertNotNull(fio); + List fseqs = SequenceUtil.readFasta(fio); + assertNotNull(fseqs); + assertEquals(3, fseqs.size()); + assertEquals(3, fseqs.size()); + fio.close(); + FileOutputStream fou = new FileOutputStream( + AllTestSuit.TEST_DATA_PATH + "TO1381.fasta.written"); + SequenceUtil.writeFasta(fou, fseqs); + fou.close(); + FileOutputStream fou20 = new FileOutputStream( + AllTestSuit.TEST_DATA_PATH + "TO1381.fasta20.written"); + SequenceUtil.writeFasta(fou20, fseqs, 21); + fou20.close(); + + } catch (FileNotFoundException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } } - } + /** + * This test tests the loading of horizontally formatted Jronn output file + */ + @Test + public void loadJronnFile() { + + FileInputStream fio; + try { + fio = new FileInputStream(AllTestSuit.TEST_DATA_PATH + "jronn.out"); + List aseqs = SequenceUtil.readJRonn(fio); + assertNotNull(aseqs); + assertEquals(aseqs.size(), 3); + AnnotatedSequence aseq = aseqs.get(0); + assertNotNull(aseq); + assertNotNull(aseq.getAnnotation()); + // System.out.println(aseq); + assertEquals(aseq.getAnnotation().length, aseq.getSequence() + .length()); + fio.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (UnknownFileFormatException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } + } + + enum Trial { + one, two, three + }; + + /** + * This test tests the loading of horizontally formatted Jronn output file + */ + @SuppressWarnings("unchecked") + @Test + public void testMultiAnnotatedSequence() { + + FileInputStream fio; + try { + fio = new FileInputStream(AllTestSuit.TEST_DATA_PATH + + "disembl.out"); + List> aseqs = SequenceUtil + .readDisembl(fio); + assertNotNull(aseqs); + + /* + * MultiAnnotatedSequence ma = new MultiAnnotatedSequence(); + * Map> val = ma.getInstance(Trial.class); + * List list = new ArrayList(); list.add(new + * Float(1.2)); list.add(new Double(5.662)); val.put(Trial.one, + * list); val.put(Trial.two, Arrays.asList(6.22f, 1, 37.6f)); + * System.out.println(val); AnnotatedSequence aseq = aseqs.get(0); + */ + fio.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (UnknownFileFormatException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } + + } } diff --git a/testsrc/compbio/metadata/AllTestSuit.java b/testsrc/compbio/metadata/AllTestSuit.java index 2c045cc..633e30b 100644 --- a/testsrc/compbio/metadata/AllTestSuit.java +++ b/testsrc/compbio/metadata/AllTestSuit.java @@ -59,8 +59,7 @@ public class AllTestSuit { * For this to work execution must start from the project directory! */ public static final String CURRENT_DIRECTORY = SysPrefs - .getCurrentDirectory() - + File.separator; + .getCurrentDirectory() + File.separator; public static final String TEST_DATA_PATH = "testsrc" + File.separator + "testdata" + File.separator; @@ -78,6 +77,9 @@ public class AllTestSuit { public static final String test_input = AllTestSuit.TEST_DATA_PATH_ABSOLUTE + "TO1381.fasta"; + public static final String test_alignment_input = AllTestSuit.TEST_DATA_PATH_ABSOLUTE + + "TO1381.fasta.aln"; + public static final String test_input_real = AllTestSuit.TEST_DATA_PATH_ABSOLUTE + "50x500Protein.fasta"; diff --git a/testsrc/compbio/runner/conservation/AAConTester.java b/testsrc/compbio/runner/conservation/AAConTester.java new file mode 100644 index 0000000..033555a --- /dev/null +++ b/testsrc/compbio/runner/conservation/AAConTester.java @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2010 Peter Troshin JAva Bioinformatics Analysis Web Services + * (JABAWS) @version: 2.0 + * + * This library is free software; you can redistribute it and/or modify it under + * the terms of the Apache License version 2 as published + * by the Apache Software Foundation This library 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 + * Apache License for more details. A copy of the license is in + * apache_license.txt. It is also available here: + * + * @see: http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Any republication or derived work distributed in source code form must include + * this copyright and license notice. + */ +package compbio.runner.conservation; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.ParseException; + +import javax.xml.bind.ValidationException; + +import org.ggf.drmaa.DrmaaException; +import org.ggf.drmaa.JobInfo; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import compbio.conservation.Method; +import compbio.data.sequence.MultiAnnotatedSequence; +import compbio.engine.AsyncExecutor; +import compbio.engine.Configurator; +import compbio.engine.FilePuller; +import compbio.engine.SyncExecutor; +import compbio.engine.client.ConfExecutable; +import compbio.engine.client.ConfiguredExecutable; +import compbio.engine.client.Executable; +import compbio.engine.client.RunConfiguration; +import compbio.engine.cluster.drmaa.ClusterUtil; +import compbio.engine.cluster.drmaa.JobRunner; +import compbio.engine.cluster.drmaa.StatisticManager; +import compbio.engine.local.LocalRunner; +import compbio.metadata.ChunkHolder; +import compbio.metadata.JobExecutionException; +import compbio.metadata.JobStatus; +import compbio.metadata.JobSubmissionException; +import compbio.metadata.LimitsManager; +import compbio.metadata.PresetManager; +import compbio.metadata.ResultNotAvailableException; +import compbio.metadata.RunnerConfig; +import compbio.util.FileWatcher; +import compbio.util.SysPrefs; + +public class AAConTester { + + public static final String CURRENT_DIRECTORY = SysPrefs + .getCurrentDirectory() + File.separator; + + public static String test_outfile = "TO1381.aacon.out"; // "/homes/pvtroshin/TO1381.clustal.cluster.out + public static String test_alignment_input = CURRENT_DIRECTORY + "testsrc" + + File.separator + "testdata" + File.separator + "TO1381.fasta.aln"; + private AACon aacon; + + @BeforeMethod(alwaysRun = true) + void init() { + aacon = new AACon(); + aacon.setInput(test_alignment_input).setOutput(test_outfile); + } + + @Test() + public void testRunOnCluster() { + assertFalse(SysPrefs.isWindows, + "Cluster execution can only be in unix environment"); + try { + ConfiguredExecutable confAAcon = Configurator + .configureExecutable(aacon, Executable.ExecProvider.Cluster); + JobRunner runner = JobRunner.getInstance(confAAcon); + + assertNotNull(runner, "Runner is NULL"); + runner.executeJob(); + // assertNotNull("JobId is null", jobId1); + JobStatus status = runner.getJobStatus(); + assertTrue(status == JobStatus.PENDING + || status == JobStatus.RUNNING, + "Status of the process is wrong!"); + JobInfo info = runner.getJobInfo(); + assertNotNull(info, "JobInfo is null"); + StatisticManager sm = new StatisticManager(info); + assertNotNull(sm, "Statictic manager is null"); + try { + + String exits = sm.getExitStatus(); + assertNotNull("Exit status is null", exits); + // cut 4 trailing zeros from the number + int exitsInt = ClusterUtil.CLUSTER_STAT_IN_SEC.parse(exits) + .intValue(); + assertEquals(0, exitsInt); + System.out.println(sm.getAllStats()); + + } catch (ParseException e) { + e.printStackTrace(); + fail("Parse Exception: " + e.getMessage()); + } + // assertFalse(runner.cleanup()); + assertTrue(sm.hasExited()); + assertFalse(sm.wasAborted()); + assertFalse(sm.hasDump()); + assertFalse(sm.hasSignaled()); + + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail("DrmaaException caught:" + e.getMessage()); + } catch (JobExecutionException e) { + e.printStackTrace(); + fail("DrmaaException caught:" + e.getMessage()); + } catch (DrmaaException e) { + e.printStackTrace(); + fail("DrmaaException caught:" + e.getMessage()); + } + } + + /** + * This tests fails from time to time depending on the cluster load or some + * other factors. Any client code has to adjust for this issue + */ + @Test() + public void testRunOnClusterAsync() { + assertFalse(SysPrefs.isWindows, + "Cluster execution can only be in unix environment"); + try { + ConfiguredExecutable confAAcon = Configurator + .configureExecutable(aacon, Executable.ExecProvider.Cluster); + AsyncExecutor aengine = Configurator.getAsyncEngine(confAAcon); + String jobId = aengine.submitJob(confAAcon); + assertNotNull(jobId, "Runner is NULL"); + // let drmaa to start + Thread.sleep(500); + JobStatus status = aengine.getJobStatus(jobId); + while (status != JobStatus.FINISHED) { + System.out.println("Job Status: " + status); + Thread.sleep(1000); + status = aengine.getJobStatus(jobId); + ConfiguredExecutable result = (ConfiguredExecutable) aengine + .getResults(jobId); + assertNotNull(result); + System.out.println("RES:" + result); + // Some times the job could be removed from the cluster + // accounting + // before it has been reported to finish. Make sure + // to stop waiting in such case + if (status == JobStatus.UNDEFINED) { + break; + } + } + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail("DrmaaException caught:" + e.getMessage()); + } catch (InterruptedException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + @Test() + public void testRunLocally() { + try { + ConfiguredExecutable confAAcon = Configurator + .configureExecutable(aacon, Executable.ExecProvider.Local); + + // For local execution use relative + LocalRunner lr = new LocalRunner(confAAcon); + lr.executeJob(); + ConfiguredExecutable al1 = lr.waitForResult(); + assertNotNull(al1.getResults()); + MultiAnnotatedSequence annotations = confAAcon.getResults(); + assertNotNull(annotations); + assertEquals(annotations.getAnnotations().size(), 18); + assertEquals(al1.getResults(), annotations); + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (JobExecutionException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } + } + + @Test() + public void testRunLocallyOnTwoCpu() { + try { + aacon.setNCore(2); + ConfiguredExecutable confAAcon = Configurator + .configureExecutable(aacon, Executable.ExecProvider.Local); + + // For local execution use relative + LocalRunner lr = new LocalRunner(confAAcon); + lr.executeJob(); + ConfiguredExecutable al1 = lr.waitForResult(); + assertNotNull(al1.getResults()); + MultiAnnotatedSequence annotations = confAAcon.getResults(); + assertNotNull(annotations); + assertEquals(annotations.getAnnotations().size(), 18); + assertEquals(al1.getResults(), annotations); + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (JobExecutionException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } + } + + @Test() + public void readStatistics() { + try { + ConfiguredExecutable confAAcon = Configurator + .configureExecutable(aacon, Executable.ExecProvider.Local); + // For local execution use relative + + AsyncExecutor sexec = Configurator.getAsyncEngine(confAAcon); + String jobId = sexec.submitJob(confAAcon); + FilePuller fw = FilePuller.newFilePuller( + confAAcon.getWorkDirectory() + File.separator + + AACon.getStatFile(), + FileWatcher.MIN_CHUNK_SIZE_BYTES); + int count = 0; + long position = 0; + fw.waitForFile(2); + JobStatus status = sexec.getJobStatus(jobId); + do { + ChunkHolder ch = fw.pull(position); + String chunk = ch.getChunk(); + position = ch.getNextPosition(); + // System.out.println(chunk); + count++; + // Make sure the loop is terminated if the job fails + if ((status == JobStatus.UNDEFINED || status == JobStatus.FAILED)) { + fail("job failed!"); + break; + } + Thread.sleep(300); + status = sexec.getJobStatus(jobId); + } while (status != JobStatus.FINISHED || fw.hasMoreData()); + + assertTrue(count >= 1); + ConfiguredExecutable al = sexec.getResults(jobId); + assertNotNull(al.getResults()); + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (InterruptedException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + @Test() + public void testPersistance() { + try { + AACon aacon = new AACon(); + aacon.setError("errrr.txt").setInput(test_alignment_input) + .setOutput("outtt.txt"); + assertEquals(aacon.getInput(), test_alignment_input); + assertEquals(aacon.getError(), "errrr.txt"); + assertEquals(aacon.getOutput(), "outtt.txt"); + ConfiguredExecutable cAAcon = Configurator + .configureExecutable(aacon, Executable.ExecProvider.Local); + + SyncExecutor sexec = Configurator.getSyncEngine(cAAcon); + sexec.executeJob(); + ConfiguredExecutable al = sexec.waitForResult(); + assertNotNull(al.getResults()); + // Save run configuration + assertTrue(cAAcon.saveRunConfiguration()); + + // See if loaded configuration is the same as saved + RunConfiguration loadedRun = RunConfiguration + .load(new FileInputStream(new File(cAAcon + .getWorkDirectory(), RunConfiguration.rconfigFile))); + assertEquals( + ((ConfExecutable) cAAcon).getRunConfiguration(), + loadedRun); + // Load run configuration as ConfExecutable + ConfiguredExecutable resurrectedCAAcon = (ConfiguredExecutable) cAAcon + .loadRunConfiguration(new FileInputStream(new File(cAAcon + .getWorkDirectory(), RunConfiguration.rconfigFile))); + assertNotNull(resurrectedCAAcon); + assertEquals(resurrectedCAAcon.getExecutable().getInput(), + test_alignment_input); + assertEquals(resurrectedCAAcon.getExecutable().getError(), + "errrr.txt"); + assertEquals(resurrectedCAAcon.getExecutable().getOutput(), + "outtt.txt"); + // See in details whether executables are the same + assertEquals(resurrectedCAAcon.getExecutable(), aacon); + + ConfiguredExecutable resAAcon = Configurator + .configureExecutable(resurrectedCAAcon.getExecutable(), + Executable.ExecProvider.Local); + + sexec = Configurator.getSyncEngine(resAAcon, + Executable.ExecProvider.Local); + sexec.executeJob(); + al = sexec.waitForResult(); + assertNotNull(al); + + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (JobExecutionException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (FileNotFoundException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + @Test() + public void testConfigurationLoading() { + try { + RunnerConfig aaconConfig = ConfExecutable + .getRunnerOptions(AACon.class); + assertNotNull(aaconConfig); + assertTrue(aaconConfig.getArguments().size() > 0); + + PresetManager aaconPresets = ConfExecutable + .getRunnerPresets(AACon.class); + assertNull(aaconPresets); // there is no presets + + LimitsManager jronnLimits = ConfExecutable + .getRunnerLimits(AACon.class); + assertNotNull(jronnLimits); + assertTrue(jronnLimits.getLimits().size() > 0); + jronnLimits.validate(aaconPresets); + + } catch (FileNotFoundException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (ValidationException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } + } + +} diff --git a/testsrc/compbio/runner/disorder/DisemblTester.java b/testsrc/compbio/runner/disorder/DisemblTester.java new file mode 100644 index 0000000..85d5e91 --- /dev/null +++ b/testsrc/compbio/runner/disorder/DisemblTester.java @@ -0,0 +1,352 @@ +/* Copyright (c) 2009 Peter Troshin + * + * JAva Bioinformatics Analysis Web Services (JABAWS) @version: 1.0 + * + * This library is free software; you can redistribute it and/or modify it under the terms of the + * Apache License version 2 as published by the Apache Software Foundation + * + * This library 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 Apache + * License for more details. + * + * A copy of the license is in apache_license.txt. It is also available here: + * @see: http://www.apache.org/licenses/LICENSE-2.0.txt + * + * Any republication or derived work distributed in source code form + * must include this copyright and license notice. + */ + +package compbio.runner.disorder; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +import javax.xml.bind.ValidationException; + +import org.ggf.drmaa.DrmaaException; +import org.ggf.drmaa.JobInfo; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import compbio.data.sequence.AnnotatedSequence; +import compbio.engine.AsyncExecutor; +import compbio.engine.Configurator; +import compbio.engine.FilePuller; +import compbio.engine.SyncExecutor; +import compbio.engine.client.ConfExecutable; +import compbio.engine.client.ConfiguredExecutable; +import compbio.engine.client.Executable; +import compbio.engine.client.RunConfiguration; +import compbio.engine.cluster.drmaa.ClusterUtil; +import compbio.engine.cluster.drmaa.JobRunner; +import compbio.engine.cluster.drmaa.StatisticManager; +import compbio.engine.local.LocalRunner; +import compbio.metadata.AllTestSuit; +import compbio.metadata.ChunkHolder; +import compbio.metadata.JobExecutionException; +import compbio.metadata.JobStatus; +import compbio.metadata.JobSubmissionException; +import compbio.metadata.LimitsManager; +import compbio.metadata.PresetManager; +import compbio.metadata.ResultNotAvailableException; +import compbio.metadata.RunnerConfig; +import compbio.util.FileWatcher; +import compbio.util.SysPrefs; + +public class DisemblTester { + + public static String test_outfile = "TO1381.disembl.out"; + + private Disembl disembl; + + @BeforeMethod(alwaysRun = true) + void init() { + disembl = new Disembl(); + disembl.setInput(AllTestSuit.test_input).setOutput(test_outfile); + } + + @Test(groups = { AllTestSuit.test_group_cluster, + AllTestSuit.test_group_runner }) + public void testRunOnCluster() { + assertFalse(SysPrefs.isWindows, + "Cluster execution can only be in unix environment"); + try { + ConfiguredExecutable confDisembl = Configurator + .configureExecutable(disembl, + Executable.ExecProvider.Cluster); + JobRunner runner = JobRunner.getInstance(confDisembl); + + assertNotNull(runner, "Runner is NULL"); + runner.executeJob(); + // assertNotNull("JobId is null", jobId1); + JobStatus status = runner.getJobStatus(); + assertTrue(status == JobStatus.PENDING + || status == JobStatus.RUNNING, + "Status of the process is wrong!"); + JobInfo info = runner.getJobInfo(); + assertNotNull(info, "JobInfo is null"); + StatisticManager sm = new StatisticManager(info); + assertNotNull(sm, "Statictic manager is null"); + try { + + String exits = sm.getExitStatus(); + assertNotNull("Exit status is null", exits); + // cut 4 trailing zeros from the number + int exitsInt = ClusterUtil.CLUSTER_STAT_IN_SEC.parse(exits) + .intValue(); + assertEquals(0, exitsInt); + System.out.println(sm.getAllStats()); + + } catch (ParseException e) { + e.printStackTrace(); + fail("Parse Exception: " + e.getMessage()); + } + //assertFalse(runner.cleanup()); + assertTrue(sm.hasExited()); + assertFalse(sm.wasAborted()); + assertFalse(sm.hasDump()); + assertFalse(sm.hasSignaled()); + + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail("DrmaaException caught:" + e.getMessage()); + } catch (JobExecutionException e) { + e.printStackTrace(); + fail("DrmaaException caught:" + e.getMessage()); + } catch (DrmaaException e) { + e.printStackTrace(); + fail("DrmaaException caught:" + e.getMessage()); + } + } + + /** + * This tests fails from time to time depending on the cluster load or some + * other factors. Any client code has to adjust for this issue + */ + @Test(groups = { AllTestSuit.test_group_cluster, + AllTestSuit.test_group_runner }) + public void testRunOnClusterAsync() { + assertFalse(SysPrefs.isWindows, + "Cluster execution can only be in unix environment"); + try { + ConfiguredExecutable confDisembl = Configurator + .configureExecutable(disembl, + Executable.ExecProvider.Cluster); + AsyncExecutor aengine = Configurator.getAsyncEngine(confDisembl); + String jobId = aengine.submitJob(confDisembl); + assertNotNull(jobId, "Runner is NULL"); + // let drmaa to start + Thread.sleep(500); + JobStatus status = aengine.getJobStatus(jobId); + while (status != JobStatus.FINISHED) { + System.out.println("Job Status: " + status); + Thread.sleep(1000); + status = aengine.getJobStatus(jobId); + ConfiguredExecutable result = (ConfiguredExecutable) aengine + .getResults(jobId); + assertNotNull(result); + System.out.println("RES:" + result); + // Some times the job could be removed from the cluster accounting + // before it has been reported to finish. Make sure + // to stop waiting in such case + if (status == JobStatus.UNDEFINED) { + break; + } + } + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail("DrmaaException caught:" + e.getMessage()); + } catch (InterruptedException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + @Test(groups = { AllTestSuit.test_group_runner }) + public void testRunLocally() { + try { + ConfiguredExecutable confDisembl = Configurator + .configureExecutable(disembl, Executable.ExecProvider.Local); + + // For local execution use relative + LocalRunner lr = new LocalRunner(confDisembl); + lr.executeJob(); + ConfiguredExecutable al1 = lr.waitForResult(); + assertNotNull(al1.getResults()); + List al2 = confDisembl.getResults(); + assertNotNull(al2); + assertEquals(al2.size(), 3); + assertEquals(al1.getResults(), al2); + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (JobExecutionException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } + } + + @Test(groups = { AllTestSuit.test_group_runner }) + public void readStatistics() { + try { + ConfiguredExecutable confDisembl = Configurator + .configureExecutable(disembl, Executable.ExecProvider.Local); + // For local execution use relavive + + AsyncExecutor sexec = Configurator.getAsyncEngine(confDisembl); + String jobId = sexec.submitJob(confDisembl); + FilePuller fw = FilePuller.newFilePuller(confDisembl + .getWorkDirectory() + + File.separator + Jronn.getStatFile(), + FileWatcher.MIN_CHUNK_SIZE_BYTES); + int count = 0; + long position = 0; + fw.waitForFile(4); + JobStatus status = sexec.getJobStatus(jobId); + while (status != JobStatus.FINISHED) { + if (fw.hasMoreData()) { + ChunkHolder ch = fw.pull(position); + String chunk = ch.getChunk(); + position = ch.getNextPosition(); + } + count++; + // Make sure the loop is terminated if the job fails + if ((status == JobStatus.UNDEFINED || status == JobStatus.FAILED)) { + break; + } + Thread.sleep(300); + status = sexec.getJobStatus(jobId); + } + assertTrue(count > 1); + ConfiguredExecutable al = sexec.getResults(jobId); + assertNotNull(al.getResults()); + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (InterruptedException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + @Test(groups = { AllTestSuit.test_group_runner }) + public void testPersistance() { + try { + Disembl disembl = new Disembl(); + disembl.setError("errrr.txt").setInput(AllTestSuit.test_input) + .setOutput("outtt.txt"); + assertEquals(disembl.getInput(), AllTestSuit.test_input); + assertEquals(disembl.getError(), "errrr.txt"); + assertEquals(disembl.getOutput(), "outtt.txt"); + ConfiguredExecutable cDisembl = Configurator + .configureExecutable(disembl, Executable.ExecProvider.Local); + + SyncExecutor sexec = Configurator.getSyncEngine(cDisembl); + sexec.executeJob(); + ConfiguredExecutable al = sexec.waitForResult(); + assertNotNull(al.getResults()); + // Save run configuration + assertTrue(cDisembl.saveRunConfiguration()); + + // See if loaded configuration is the same as saved + RunConfiguration loadedRun = RunConfiguration + .load(new FileInputStream(new File(cDisembl + .getWorkDirectory(), RunConfiguration.rconfigFile))); + assertEquals(((ConfExecutable) cDisembl) + .getRunConfiguration(), loadedRun); + // Load run configuration as ConfExecutable + ConfiguredExecutable resurrectedCDisembl = (ConfiguredExecutable) cDisembl + .loadRunConfiguration(new FileInputStream(new File(cDisembl + .getWorkDirectory(), RunConfiguration.rconfigFile))); + assertNotNull(resurrectedCDisembl); + assertEquals(resurrectedCDisembl.getExecutable().getInput(), + AllTestSuit.test_input); + assertEquals(resurrectedCDisembl.getExecutable().getError(), + "errrr.txt"); + assertEquals(resurrectedCDisembl.getExecutable().getOutput(), + "outtt.txt"); + // See in details whether executables are the same + assertEquals(resurrectedCDisembl.getExecutable(), disembl); + + ConfiguredExecutable resJronn = Configurator + .configureExecutable(resurrectedCDisembl.getExecutable(), + Executable.ExecProvider.Local); + + sexec = Configurator.getSyncEngine(resJronn, + Executable.ExecProvider.Local); + sexec.executeJob(); + al = sexec.waitForResult(); + assertNotNull(al); + + } catch (JobSubmissionException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (JobExecutionException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (FileNotFoundException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getMessage()); + } catch (ResultNotAvailableException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + @Test(groups = { AllTestSuit.test_group_runner }) + public void testConfigurationLoading() { + try { + RunnerConfig disemblConfig = ConfExecutable + .getRunnerOptions(Disembl.class); + assertNotNull(disemblConfig); + assertTrue(disemblConfig.getArguments().size() > 0); + + PresetManager disemblPresets = ConfExecutable + .getRunnerPresets(Disembl.class); + assertNull(disemblPresets); // there is no presets + + LimitsManager disemblLimits = ConfExecutable + .getRunnerLimits(Disembl.class); + assertNotNull(disemblLimits); + assertTrue(disemblLimits.getLimits().size() > 0); + disemblLimits.validate(disemblPresets); + + } catch (FileNotFoundException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (IOException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } catch (ValidationException e) { + e.printStackTrace(); + fail(e.getLocalizedMessage()); + } + } + +} diff --git a/testsrc/testdata/TO1381.fasta.aln b/testsrc/testdata/TO1381.fasta.aln new file mode 100644 index 0000000..f6da7b8 --- /dev/null +++ b/testsrc/testdata/TO1381.fasta.aln @@ -0,0 +1,35 @@ +>Foobar_dundeefriends +MTADGPRELLQLRAAVRHRPQDFVAWLMLADAELGMGDTTAGEMAVQRGLALHPGHPEAV +ARLGRVRWTQQRHAEAAVLLQQASDAAPEHPGIALWLGHALEDAGQAEAAAAAYTRAHQL +LPEEPYITAQLLNWRRRLCDWRALDVLSAQVRAAVAQGVGAVEPFAFLSEDASAAEQLAC +ARTRAQAIAASVRPLAPTRVRSKGPLRVGFVSNGFGAHPTGLLTVALFEALQRRQPDLQM +HLFATSGDDGSTLRTRLAQASTLHDVTALGHLATAKHIRHHGIDLLFDLRGWGGGGRPEV +FALRPAPVQVNWLAYPGTSGAPWMDYVLGDAFALPPALEPFYSEHVLRLQGAFQPSDTSR +VVAEPPSRTQCGLPEQGVVLCCFNNSYKLNPQSMARMLAVLREVPDSVLWLLSGPGEADA +RLRAFAHAQGVDAQRLVFMPKLPHPQYLARYRHADLFLDTHPYNAHTTASDALWTGCPVL +TTPGETFAARVAGSLNHHLGLDEMNVADDAAFVAKAVALASDPAALTALHARVDVLRRES +GVFEMDGFADDFGALLQALARRHGWLGI + +>Foobar +-----------------------------------MGDTTAGEMAVQRGLALH------- +---------QQRHAEAAVLLQQASDAAPEHPGIALWL-HALEDAGQAEAAAA-YTRAHQL +LPEEPYITAQLLN--------------------AVAQGVGAVEPFAFLSEDASAAE---- +----------SVRPLAPTRVRSKGPLRVGFVSNGFGAHPTGLLTVALFEALQRRQPDLQM +HLFATSGDDGSTLRTRLAQASTLHDVTALGHLATAKHIRHHGIDLLFDLRGWGGGGRPEV +FALRPAPVQVNWLAYPGTSGAPWMDYVLGDAFALPPALEPFYSEHVLRLQGAFQPSDTSR +VVAEPPSRTQCGLPEQGVVLCCFNNSYKLNPQSMARMLAVLREVPDSVLWLLSGPGEADA +RLRAFAHAQGVDAQRLVFMPKLPHPQYLARYRHADLFLDTHPYNAHTTASDALWTGCPVL +TTPGETFAARVAGSLNHHLGLDEMNVADDAAFVAKAVALASDPAALTALHARVDVLRRES +GVFEMDGFADDFGALLQALARRHGWLGI + +>dundeefriends +-MTADGPRELLQLRAAVRHRPQDVAWLMLADAELGMGDTTAGEMAVQRGLALHPGHPEAV +ARLGRVRWTQQRHAEAAVLLQQASDAAPEHPGIALWLGHALED--------------HQL +LPEEPYITAQLDVLSAQVR-------------AAVAQGVGAVEPFAFLSEDASAAEQLAC +ARTRAQAIAASVRPLAPTRVRSKGPLRVGFVSNGFGAHPTGLLTVALFEALQRRQPDLQM +HLFATSGDDGSTLRTRLAQASTLHDVTALGHLATAKHIRHHGIDLLFDLRGWGGGGRPEV +FALRPAPVQVNWLAYPGTSGAPWMDYVLGDAFALPPALEPFYSEHVLRLQGAFQPSDTSR +VVAEPPSRTQCGLPEQGVVLCCFNNSYKLNPQSMARMLAVLREVPDSVLWLLSGPGEADA +RLRAFAHAQGVDAQRLVFMPKLPHPQYLARYRHADLFLDTHPYNAHTTASDALWTGCPVL +TTPGETFAARVAGSLNHHLGLDEMNVADDAAFVAKAVALASDPAALTALHARVDVLRRES +I--------------------------- \ No newline at end of file diff --git a/webservices/compbio/data/msa/Annotation.java b/webservices/compbio/data/msa/Annotation.java new file mode 100644 index 0000000..c73c0bc --- /dev/null +++ b/webservices/compbio/data/msa/Annotation.java @@ -0,0 +1,175 @@ +package compbio.data.msa; + +import java.security.InvalidParameterException; +import java.util.List; + +import javax.jws.WebParam; +import javax.jws.WebService; + +import compbio.data.sequence.FastaSequence; +import compbio.data.sequence.MultiAnnotatedSequence; +import compbio.metadata.JobSubmissionException; +import compbio.metadata.LimitExceededException; +import compbio.metadata.Option; +import compbio.metadata.Preset; +import compbio.metadata.ResultNotAvailableException; +import compbio.metadata.UnsupportedRuntimeException; +import compbio.metadata.WrongParameterException; + +/** + * Interface for tools that results to one or more annotation to sequence(s) + * + * @author pvtroshin + * + * Date November 2010 + * + * @param + * executable type / web service type + */ +@WebService(targetNamespace = "http://a.data.compbio/01/12/2010/") +public interface Annotation extends JManagement, Metadata { + + /** + * + * Any dataset containing a greater number of sequences or the average + * length of the sequences are greater then defined in the default Limit + * will not be accepted for an alignment operation and + * JobSubmissionException will be thrown. + * + * @param sequences + * List of FastaSequence objects. The programme does not perform + * any sequence validity checks. Nor does it checks whether the + * sequences names are unique. It is responsibility of the caller + * to validate this information + * @return jobId - unique identifier for the job + * @throws JobSubmissionException + * is thrown when the job could not be submitted due to the + * following reasons: 1) The number of sequences in the + * submission or their average length is greater then defined by + * the default Limit. 2) Any problems on the server side e.g. it + * is misconfigured or malfunction, is reported via this + * exception. In the first case the information on the limit + * could be obtained from an exception. + * @throws InvalidParameterException + * thrown if input list of fasta sequence is null or empty + * @throws UnsupportedRuntimeException + * thrown if server OS does not support native executables for a + * given web service, e.g. JWS2 is deployed on Windows and Mafft + * service is called + * @throws LimitExceededException + * is throw if the input sequences number or average length + * exceeds what is defined by the limit + */ + String analize( + @WebParam(name = "fastaSequences") List sequences) + throws UnsupportedRuntimeException, LimitExceededException, + JobSubmissionException; + + /** + * + * @see Option + * + * Default Limit is used to decide whether the calculation will be + * permitted or denied + * + * @param sequences + * List of FastaSequence objects. The programme does not perform + * any sequence validity checks. Nor does it checks whether the + * sequences names are unique. It is responsibility of the caller + * to validate this information + * @param options + * A list of Options + * @return jobId - unique identifier for the job + * @throws JobSubmissionException. This + * exception is thrown when the job could not be submitted due + * to the following reasons: 1) The number of sequences in the + * submission or their average length is greater then defined by + * the default Limit. 2) Any problems on the server side e.g. it + * is misconfigured or malfunction, is reported via this + * exception. In the first case the information on the limit + * could be obtained from an exception. + * @throws WrongParameterException + * is throws when 1) One of the Options provided is not + * supported, 2) The value of the option is defined outside the + * boundaries. In both cases exception object contain the + * information on the violating Option. + * @throws InvalidParameterException + * thrown if input list of fasta sequence is null or empty + * @throws UnsupportedRuntimeException + * thrown if server OS does not support native executables for a + * given web service, e.g. JWS2 is deployed on Windows and Mafft + * service is called + * @throws LimitExceededException + * is throw if the input sequences number or average length + * exceeds what is defined by the limit + */ + String customAnalize( + @WebParam(name = "fastaSequences") List sequences, + @WebParam(name = "options") List> options) + throws UnsupportedRuntimeException, LimitExceededException, + JobSubmissionException, WrongParameterException; + + /** + * + * + * Limit for a presetName is used whether the calculation will be permitted + * or denied. If no Limit was defined for a presetName, than default limit + * is used. + * + * @param sequences + * List of FastaSequence objects. The programme does not perform + * any sequence validity checks. Nor does it checks whether the + * sequences names are unique. It is responsibility of the caller + * to validate this information + * @param preset + * A list of Options + * @return String - jobId - unique identifier for the job + * @throws JobSubmissionException. This + * exception is thrown when the job could not be submitted due + * to the following reasons: 1) The number of sequences in the + * submission or their average length is greater then defined by + * the default Limit. 2) Any problems on the server side e.g. it + * is misconfigured or malfunction, is reported via this + * exception. In the first case the information on the limit + * could be obtained from an exception. + * @throws WrongParameterException + * is throws when 1) One of the Options provided is not + * supported, 2) The value of the option is defined outside the + * boundaries. In both cases exception object contain the + * information on the violating Option. + * @throws InvalidParameterException + * thrown if input list of fasta sequence is null or empty + * @throws UnsupportedRuntimeException + * thrown if server OS does not support native executables for a + * given web service, e.g. JWS2 is deployed on Windows and Mafft + * service is called + * @throws LimitExceededException + * is throw if the input sequences number or average length + * exceeds what is defined by the limit + */ + String presetAnalize( + @WebParam(name = "fastaSequences") List sequences, + @WebParam(name = "preset") Preset preset) + throws UnsupportedRuntimeException, LimitExceededException, + JobSubmissionException, WrongParameterException; + + /** + * Return the result of the job. + * + * @param jobId + * a unique job identifier + * @return + * @throws ResultNotAvailableException + * this exception is throw if the job execution was not + * successful or the result of the execution could not be found. + * (e.g. removed). Exception could also be thrown is dues to the + * lower level problems on the server i.e. IOException, + * FileNotFoundException problems as well as + * UnknownFileFormatException. + * @throws InvalidParameterException + * thrown if jobId is empty or cannot be recognised e.g. in + * invalid format + */ + MultiAnnotatedSequence getResult(@WebParam(name = "jobId") String jobId) + throws ResultNotAvailableException; +} diff --git a/webservices/compbio/data/msa/JManagement.java b/webservices/compbio/data/msa/JManagement.java new file mode 100644 index 0000000..b9b2f01 --- /dev/null +++ b/webservices/compbio/data/msa/JManagement.java @@ -0,0 +1,54 @@ +package compbio.data.msa; + +import java.security.InvalidParameterException; + +import javax.jws.WebParam; + +import compbio.metadata.ChunkHolder; +import compbio.metadata.JobStatus; + +public interface JManagement { + + /** + * Stop running job but leave its output untouched + * + * @return true if job was cancelled successfully, false otherwise + * @throws InvalidParameterException + * thrown if jobId is empty or cannot be recognised e.g. in + * invalid format + */ + boolean cancelJob(@WebParam(name = "jobId") String jobId); + + /** + * Return the status of the job. @see JobStatus + * + * @param jobId + * - unique job identifier + * @return JobStatus - status of the job + * @throws InvalidParameterException + * thrown if jobId is empty or cannot be recognised e.g. in + * invalid format + */ + JobStatus getJobStatus(@WebParam(name = "jobId") String jobId); + + /** + * Reads 1kb chunk from the statistics file which is specific to a given web + * service from the position. If in time of a request less then 1kb data is + * available from the position to the end of the file, then it returns all + * the data available from the position to the end of the file. + * + * @param jobId + * - unique job identifier + * @param position + * - next position within the file to read + * @return ChunkHolder - @see ChunkHolder which contains a chuink of data + * and a next position within the file from which no data has been + * read + * @throws InvalidParameterException + * thrown if jobId is empty or cannot be recognised e.g. in + * invalid format and also if the position value is negative + */ + ChunkHolder pullExecStatistics(@WebParam(name = "jobId") String jobId, + @WebParam(name = "position") long position); + +} diff --git a/webservices/compbio/data/msa/Metadata.java b/webservices/compbio/data/msa/Metadata.java new file mode 100644 index 0000000..67abb5d --- /dev/null +++ b/webservices/compbio/data/msa/Metadata.java @@ -0,0 +1,50 @@ +package compbio.data.msa; + +import javax.jws.WebParam; + +import compbio.metadata.Limit; +import compbio.metadata.LimitsManager; +import compbio.metadata.PresetManager; +import compbio.metadata.RunnerConfig; + +public interface Metadata { + + /** + * Get options supported by a web service + * + * @return RunnerConfig the list of options and parameters supported by a + * web service. + */ + RunnerConfig getRunnerOptions(); + + /** + * Get presets supported by a web service + * + * @return PresetManager the object contains information about presets + * supported by a web service + */ + PresetManager getPresets(); + + /** + * Get a Limit for a preset. + * + * @param presetName + * the name of the preset. if no name is provided, then the + * default preset is returned. If no limit for a particular + * preset is defined then the default preset is returned + * @return Limit + */ + Limit getLimit(@WebParam(name = "presetName") String presetName); + + /** + * List Limits supported by a web service. + * + * @param presetName + * the name of the preset. if no name is provided, then the + * default preset is returned. If no limit for a particular + * preset is defined then the default preset is returned + * @return LimitManager + */ + LimitsManager getLimits(); + +} diff --git a/webservices/compbio/data/msa/MsaWS.java b/webservices/compbio/data/msa/MsaWS.java index 4a22009..b1735fb 100644 --- a/webservices/compbio/data/msa/MsaWS.java +++ b/webservices/compbio/data/msa/MsaWS.java @@ -26,17 +26,11 @@ import javax.jws.WebService; import compbio.data.sequence.Alignment; import compbio.data.sequence.FastaSequence; -import compbio.metadata.ChunkHolder; -import compbio.metadata.JobStatus; import compbio.metadata.JobSubmissionException; -import compbio.metadata.Limit; import compbio.metadata.LimitExceededException; -import compbio.metadata.LimitsManager; import compbio.metadata.Option; import compbio.metadata.Preset; -import compbio.metadata.PresetManager; import compbio.metadata.ResultNotAvailableException; -import compbio.metadata.RunnerConfig; import compbio.metadata.UnsupportedRuntimeException; import compbio.metadata.WrongParameterException; @@ -45,248 +39,158 @@ import compbio.metadata.WrongParameterException; * * @author pvtroshin * - * Date September 2009 + * Date November 2010 * * @param * executable type / web service type */ -@WebService(targetNamespace = "http://msa.data.compbio/01/01/2010/") -public interface MsaWS { - - /** - * Align a list of sequences with default settings. - * - * Any dataset containing a greater number of sequences or the average - * length of the sequences are greater then defined in the default Limit - * will not be accepted for an alignment operation and - * JobSubmissionException will be thrown. - * - * @param sequences - * List of FastaSequence objects. The programme does not perform - * any sequence validity checks. Nor does it checks whether the - * sequences names are unique. It is responsibility of the caller - * to validate this information - * @return jobId - unique identifier for the job - * @throws JobSubmissionException. This - * exception is thrown when the job could not be submitted due - * to the following reasons: 1) The number of sequences in the - * submission or their average length is greater then defined by - * the default Limit. 2) Any problems on the server side e.g. it - * is misconfigured or malfunction, is reported via this - * exception. In the first case the information on the limit - * could be obtained from an exception. - * @throws InvalidParameterException - * thrown if input list of fasta sequence is null or empty - * @throws UnsupportedRuntimeException - * thrown if server OS does not support native executables for a - * given web service, e.g. JWS2 is deployed on Windows and Mafft - * service is called - * @throws LimitExceededException - * is throw if the input sequences number or average length - * exceeds what is defined by the limit - */ - String align( - @WebParam(name = "fastaSequences") List sequences) - throws UnsupportedRuntimeException, LimitExceededException, - JobSubmissionException; - - /** - * Align a list of sequences with options. - * - * @see Option - * - * Default Limit is used to decide whether the calculation will be - * permitted or denied - * - * @param sequences - * List of FastaSequence objects. The programme does not perform - * any sequence validity checks. Nor does it checks whether the - * sequences names are unique. It is responsibility of the caller - * to validate this information - * @param options - * A list of Options - * @return jobId - unique identifier for the job - * @throws JobSubmissionException. This - * exception is thrown when the job could not be submitted due - * to the following reasons: 1) The number of sequences in the - * submission or their average length is greater then defined by - * the default Limit. 2) Any problems on the server side e.g. it - * is misconfigured or malfunction, is reported via this - * exception. In the first case the information on the limit - * could be obtained from an exception. - * @throws WrongParameterException - * is throws when 1) One of the Options provided is not - * supported, 2) The value of the option is defined outside the - * boundaries. In both cases exception object contain the - * information on the violating Option. - * @throws InvalidParameterException - * thrown if input list of fasta sequence is null or empty - * @throws UnsupportedRuntimeException - * thrown if server OS does not support native executables for a - * given web service, e.g. JWS2 is deployed on Windows and Mafft - * service is called - * @throws LimitExceededException - * is throw if the input sequences number or average length - * exceeds what is defined by the limit - */ - String customAlign( - @WebParam(name = "fastaSequences") List sequences, - @WebParam(name = "options") List> options) - throws UnsupportedRuntimeException, LimitExceededException, - JobSubmissionException, WrongParameterException; - - /** - * Align a list of sequences with preset. @see Preset - * - * Limit for a presetName is used whether the calculation will be permitted - * or denied. If no Limit was defined for a presetName, than default limit - * is used. - * - * @param sequences - * List of FastaSequence objects. The programme does not perform - * any sequence validity checks. Nor does it checks whether the - * sequences names are unique. It is responsibility of the caller - * to validate this information - * @param preset - * A list of Options - * @return String - jobId - unique identifier for the job - * @throws JobSubmissionException. This - * exception is thrown when the job could not be submitted due - * to the following reasons: 1) The number of sequences in the - * submission or their average length is greater then defined by - * the default Limit. 2) Any problems on the server side e.g. it - * is misconfigured or malfunction, is reported via this - * exception. In the first case the information on the limit - * could be obtained from an exception. - * @throws WrongParameterException - * is throws when 1) One of the Options provided is not - * supported, 2) The value of the option is defined outside the - * boundaries. In both cases exception object contain the - * information on the violating Option. - * @throws InvalidParameterException - * thrown if input list of fasta sequence is null or empty - * @throws UnsupportedRuntimeException - * thrown if server OS does not support native executables for a - * given web service, e.g. JWS2 is deployed on Windows and Mafft - * service is called - * @throws LimitExceededException - * is throw if the input sequences number or average length - * exceeds what is defined by the limit - */ - String presetAlign( - @WebParam(name = "fastaSequences") List sequences, - @WebParam(name = "preset") Preset preset) - throws UnsupportedRuntimeException, LimitExceededException, - JobSubmissionException, WrongParameterException; - - /** - * Return the result of the job. - * - * @param jobId - * a unique job identifier - * @return Alignment - * @throws ResultNotAvailableException - * this exception is throw if the job execution was not - * successful or the result of the execution could not be found. - * (e.g. removed). Exception could also be thrown is dues to the - * lower level problems on the server i.e. IOException, - * FileNotFoundException problems as well as - * UnknownFileFormatException. - * @throws InvalidParameterException - * thrown if jobId is empty or cannot be recognised e.g. in - * invalid format - */ - Alignment getResult(@WebParam(name = "jobId") String jobId) - throws ResultNotAvailableException; - - /** - * Stop running job but leave its output untouched - * - * @return true if job was cancelled successfully, false otherwise - * @throws InvalidParameterException - * thrown if jobId is empty or cannot be recognised e.g. in - * invalid format - */ - boolean cancelJob(@WebParam(name = "jobId") String jobId); - - /** - * Return the status of the job. @see JobStatus - * - * @param jobId - * - unique job identifier - * @return JobStatus - status of the job - * @throws InvalidParameterException - * thrown if jobId is empty or cannot be recognised e.g. in - * invalid format - */ - JobStatus getJobStatus(@WebParam(name = "jobId") String jobId); - - /** - * Reads 1kb chunk from the statistics file which is specific to a given web - * service from the position. If in time of a request less then 1kb data is - * available from the position to the end of the file, then it returns all - * the data available from the position to the end of the file. - * - * @param jobId - * - unique job identifier - * @param position - * - next position within the file to read - * @return ChunkHolder - @see ChunkHolder which contains a chuink of data - * and a next position within the file from which no data has been - * read - * @throws InvalidParameterException - * thrown if jobId is empty or cannot be recognised e.g. in - * invalid format and also if the position value is negative - */ - ChunkHolder pullExecStatistics(@WebParam(name = "jobId") String jobId, - @WebParam(name = "position") long position); - - /* - * TODO - * - * @param jobId - * - * @return - * - * byte getProgress(@WebParam(name = "jobId") String jobId); - */ - - /** - * Get options supported by a web service - * - * @return RunnerConfig the list of options and parameters supported by a - * web service. - */ - RunnerConfig getRunnerOptions(); - - /** - * Get presets supported by a web service - * - * @return PresetManager the object contains information about presets - * supported by a web service - */ - PresetManager getPresets(); - - /** - * Get a Limit for a preset. - * - * @param presetName - * the name of the preset. if no name is provided, then the - * default preset is returned. If no limit for a particular - * preset is defined then the default preset is returned - * @return Limit - */ - Limit getLimit(@WebParam(name = "presetName") String presetName); - - /** - * List Limits supported by a web service. - * - * @param presetName - * the name of the preset. if no name is provided, then the - * default preset is returned. If no limit for a particular - * preset is defined then the default preset is returned - * @return LimitManager - */ - LimitsManager getLimits(); +@WebService(targetNamespace = "http://msa.data.compbio/01/12/2010/") +public interface MsaWS extends JManagement, Metadata { + + /** + * Align a list of sequences with default settings. + * + * Any dataset containing a greater number of sequences or the average + * length of the sequences are greater then defined in the default Limit + * will not be accepted for an alignment operation and + * JobSubmissionException will be thrown. + * + * @param sequences + * List of FastaSequence objects. The programme does not perform + * any sequence validity checks. Nor does it checks whether the + * sequences names are unique. It is responsibility of the caller + * to validate this information + * @return jobId - unique identifier for the job + * @throws JobSubmissionException. This + * exception is thrown when the job could not be submitted due + * to the following reasons: 1) The number of sequences in the + * submission or their average length is greater then defined by + * the default Limit. 2) Any problems on the server side e.g. it + * is misconfigured or malfunction, is reported via this + * exception. In the first case the information on the limit + * could be obtained from an exception. + * @throws InvalidParameterException + * thrown if input list of fasta sequence is null or empty + * @throws UnsupportedRuntimeException + * thrown if server OS does not support native executables for a + * given web service, e.g. JWS2 is deployed on Windows and Mafft + * service is called + * @throws LimitExceededException + * is throw if the input sequences number or average length + * exceeds what is defined by the limit + */ + String align( + @WebParam(name = "fastaSequences") List sequences) + throws UnsupportedRuntimeException, LimitExceededException, + JobSubmissionException; + + /** + * Align a list of sequences with options. + * + * @see Option + * + * Default Limit is used to decide whether the calculation will be + * permitted or denied + * + * @param sequences + * List of FastaSequence objects. The programme does not perform + * any sequence validity checks. Nor does it checks whether the + * sequences names are unique. It is responsibility of the caller + * to validate this information + * @param options + * A list of Options + * @return jobId - unique identifier for the job + * @throws JobSubmissionException. This + * exception is thrown when the job could not be submitted due + * to the following reasons: 1) The number of sequences in the + * submission or their average length is greater then defined by + * the default Limit. 2) Any problems on the server side e.g. it + * is misconfigured or malfunction, is reported via this + * exception. In the first case the information on the limit + * could be obtained from an exception. + * @throws WrongParameterException + * is throws when 1) One of the Options provided is not + * supported, 2) The value of the option is defined outside the + * boundaries. In both cases exception object contain the + * information on the violating Option. + * @throws InvalidParameterException + * thrown if input list of fasta sequence is null or empty + * @throws UnsupportedRuntimeException + * thrown if server OS does not support native executables for a + * given web service, e.g. JWS2 is deployed on Windows and Mafft + * service is called + * @throws LimitExceededException + * is throw if the input sequences number or average length + * exceeds what is defined by the limit + */ + String customAlign( + @WebParam(name = "fastaSequences") List sequences, + @WebParam(name = "options") List> options) + throws UnsupportedRuntimeException, LimitExceededException, + JobSubmissionException, WrongParameterException; + + /** + * Align a list of sequences with preset. @see Preset + * + * Limit for a presetName is used whether the calculation will be permitted + * or denied. If no Limit was defined for a presetName, than default limit + * is used. + * + * @param sequences + * List of FastaSequence objects. The programme does not perform + * any sequence validity checks. Nor does it checks whether the + * sequences names are unique. It is responsibility of the caller + * to validate this information + * @param preset + * A list of Options + * @return String - jobId - unique identifier for the job + * @throws JobSubmissionException. This + * exception is thrown when the job could not be submitted due + * to the following reasons: 1) The number of sequences in the + * submission or their average length is greater then defined by + * the default Limit. 2) Any problems on the server side e.g. it + * is misconfigured or malfunction, is reported via this + * exception. In the first case the information on the limit + * could be obtained from an exception. + * @throws WrongParameterException + * is throws when 1) One of the Options provided is not + * supported, 2) The value of the option is defined outside the + * boundaries. In both cases exception object contain the + * information on the violating Option. + * @throws InvalidParameterException + * thrown if input list of fasta sequence is null or empty + * @throws UnsupportedRuntimeException + * thrown if server OS does not support native executables for a + * given web service, e.g. JWS2 is deployed on Windows and Mafft + * service is called + * @throws LimitExceededException + * is throw if the input sequences number or average length + * exceeds what is defined by the limit + */ + String presetAlign( + @WebParam(name = "fastaSequences") List sequences, + @WebParam(name = "preset") Preset preset) + throws UnsupportedRuntimeException, LimitExceededException, + JobSubmissionException, WrongParameterException; + + /** + * Return the result of the job. + * + * @param jobId + * a unique job identifier + * @return Alignment + * @throws ResultNotAvailableException + * this exception is throw if the job execution was not + * successful or the result of the execution could not be found. + * (e.g. removed). Exception could also be thrown is dues to the + * lower level problems on the server i.e. IOException, + * FileNotFoundException problems as well as + * UnknownFileFormatException. + * @throws InvalidParameterException + * thrown if jobId is empty or cannot be recognised e.g. in + * invalid format + */ + Alignment getResult(@WebParam(name = "jobId") String jobId) + throws ResultNotAvailableException; } diff --git a/webservices/compbio/ws/server/AAConWS.java b/webservices/compbio/ws/server/AAConWS.java new file mode 100644 index 0000000..cf382df --- /dev/null +++ b/webservices/compbio/ws/server/AAConWS.java @@ -0,0 +1,140 @@ +package compbio.ws.server; + +import java.io.File; +import java.util.List; + +import javax.annotation.Resource; +import javax.jws.WebService; +import javax.xml.ws.WebServiceContext; + +import org.apache.log4j.Logger; + +import compbio.conservation.Method; +import compbio.data.msa.Annotation; +import compbio.data.sequence.FastaSequence; +import compbio.data.sequence.JalviewAnnotation; +import compbio.data.sequence.MultiAnnotatedSequence; +import compbio.engine.AsyncExecutor; +import compbio.engine.Configurator; +import compbio.engine.client.ConfiguredExecutable; +import compbio.metadata.ChunkHolder; +import compbio.metadata.JobStatus; +import compbio.metadata.JobSubmissionException; +import compbio.metadata.Limit; +import compbio.metadata.LimitExceededException; +import compbio.metadata.LimitsManager; +import compbio.metadata.Option; +import compbio.metadata.Preset; +import compbio.metadata.PresetManager; +import compbio.metadata.ResultNotAvailableException; +import compbio.metadata.RunnerConfig; +import compbio.metadata.UnsupportedRuntimeException; +import compbio.metadata.WrongParameterException; +import compbio.runner.Util; +import compbio.runner.conservation.AACon; + +@WebService(endpointInterface = "compbio.data.msa.MsaWS", targetNamespace = "http://msa.data.compbio/01/01/2010/", serviceName = "MuscleWS") +public class AAConWS implements Annotation { + + // Ask for resource injection + @Resource + WebServiceContext wsContext; + + private static Logger statLog = Logger.getLogger("AAConWS-stats"); + + private static Logger log = Logger.getLogger(AAConWS.class); + + private static final RunnerConfig aaconOptions = Util + .getSupportedOptions(AACon.class); + + private static final PresetManager aaconPresets = Util + .getPresets(AACon.class); + + ConfiguredExecutable init(List sequences) + throws JobSubmissionException { + AACon aacon = new AACon(); + aacon.setInput("fasta.in").setOutput("fasta.out"); + return Configurator.configureExecutable(aacon, sequences); + } + + @SuppressWarnings("unchecked") + public MultiAnnotatedSequence getResult(String jobId) + throws ResultNotAvailableException { + WSUtil.validateJobId(jobId); + AsyncExecutor asyncEngine = Configurator.getAsyncEngine(jobId); + ConfiguredExecutable aacon = (ConfiguredExecutable) asyncEngine + .getResults(jobId); + MultiAnnotatedSequence mas = aacon.getResults(); + // log(jobId, "getResults"); + return mas; + } + + @SuppressWarnings("unchecked") + public JalviewAnnotation getJalviewAnnotation(String jobId) + throws ResultNotAvailableException { + MultiAnnotatedSequence result = getResult(jobId); + + // log(jobId, "getResults"); + return result.toJalviewAnnotation(); + } + + public Limit getLimit(String presetName) { + return new AACon().getLimit(presetName); + } + + public LimitsManager getLimits() { + return new AACon().getLimits(); + } + + public ChunkHolder pullExecStatistics(String jobId, long position) { + WSUtil.validateJobId(jobId); + String file = Configurator.getWorkDirectory(jobId) + File.separator + + AACon.getStatFile(); + return WSUtil.pullFile(file, position); + } + + public boolean cancelJob(String jobId) { + WSUtil.validateJobId(jobId); + return WSUtil.cancelJob(jobId); + } + + public JobStatus getJobStatus(String jobId) { + WSUtil.validateJobId(jobId); + return WSUtil.getJobStatus(jobId); + } + + public PresetManager getPresets() { + return aaconPresets; + } + + public RunnerConfig getRunnerOptions() { + return aaconOptions; + } + + @Override + public String analize(List sequences) + throws UnsupportedRuntimeException, LimitExceededException, + JobSubmissionException { + // TODO Auto-generated method stub + return null; + } + + @Override + public String customAnalize(List sequences, + List> options) throws UnsupportedRuntimeException, + LimitExceededException, JobSubmissionException, + WrongParameterException { + // TODO Auto-generated method stub + return null; + } + + @Override + public String presetAnalize(List sequences, + Preset preset) throws UnsupportedRuntimeException, + LimitExceededException, JobSubmissionException, + WrongParameterException { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/webservices/compbio/ws/server/WSUtil.java b/webservices/compbio/ws/server/WSUtil.java index dd1051f..62996e5 100644 --- a/webservices/compbio/ws/server/WSUtil.java +++ b/webservices/compbio/ws/server/WSUtil.java @@ -36,73 +36,73 @@ import compbio.util.Timer; public final class WSUtil { - public static final void validateJobId(String jobId) - throws InvalidParameterException { - if (!compbio.engine.client.Util.isValidJobId(jobId)) { - throw new InvalidParameterException( - "JobId is not provided or cannot be recognised! Given value: " - + jobId); + public static final void validateJobId(String jobId) + throws InvalidParameterException { + if (!compbio.engine.client.Util.isValidJobId(jobId)) { + throw new InvalidParameterException( + "JobId is not provided or cannot be recognised! Given value: " + + jobId); + } } - } - public static final void validateFastaInput(List sequences) - throws InvalidParameterException { - if (sequences == null || sequences.isEmpty()) { - throw new InvalidParameterException( - "List of fasta sequences required but not provided! "); + public static final void validateFastaInput(List sequences) + throws InvalidParameterException { + if (sequences == null || sequences.isEmpty()) { + throw new InvalidParameterException( + "List of fasta sequences required but not provided! "); + } } - } - public static JobStatus getJobStatus(String jobId) { - AsyncExecutor asyncEngine = Configurator.getAsyncEngine(jobId); - return asyncEngine.getJobStatus(jobId); - } - - public static ChunkHolder pullFile(String file, long position) { - return ProgressGetter.pull(file, position); - } + public static JobStatus getJobStatus(String jobId) { + AsyncExecutor asyncEngine = Configurator.getAsyncEngine(jobId); + return asyncEngine.getJobStatus(jobId); + } - public static byte getProgress(String jobId) { - throw new UnsupportedOperationException(); - } + public static ChunkHolder pullFile(String file, long position) { + return ProgressGetter.pull(file, position); + } - public static AsyncExecutor getEngine(ConfiguredExecutable confClustal) { - assert confClustal != null; - return Configurator.getAsyncEngine(confClustal); - } + public static byte getProgress(String jobId) { + throw new UnsupportedOperationException(); + } - public static boolean cancelJob(String jobId) { - AsyncExecutor asyncEngine = Configurator.getAsyncEngine(jobId); - return asyncEngine.cancelJob(jobId); - } + public static AsyncExecutor getEngine(ConfiguredExecutable confClustal) { + assert confClustal != null; + return Configurator.getAsyncEngine(confClustal); + } - public static String align(List sequences, - ConfiguredExecutable confExec, WSLogger logger, - String callingMethod, Limit limit) - throws LimitExceededException, JobSubmissionException { - Timer timer = Timer.getMilliSecondsTimer(); - if (limit.isExceeded(sequences)) { - throw LimitExceededException.newLimitExceeded(limit, sequences); + public static boolean cancelJob(String jobId) { + AsyncExecutor asyncEngine = Configurator.getAsyncEngine(jobId); + return asyncEngine.cancelJob(jobId); } - compbio.runner.Util.writeInput(sequences, confExec); - AsyncExecutor engine = Configurator.getAsyncEngine(confExec); - String jobId = engine.submitJob(confExec); - if (logger != null) { - logger.log(timer, callingMethod, jobId); + + public static String align(List sequences, + ConfiguredExecutable confExec, WSLogger logger, + String callingMethod, Limit limit) + throws LimitExceededException, JobSubmissionException { + Timer timer = Timer.getMilliSecondsTimer(); + if (limit != null && limit.isExceeded(sequences)) { + throw LimitExceededException.newLimitExceeded(limit, sequences); + } + compbio.runner.Util.writeInput(sequences, confExec); + AsyncExecutor engine = Configurator.getAsyncEngine(confExec); + String jobId = engine.submitJob(confExec); + if (logger != null) { + logger.log(timer, callingMethod, jobId); + } + return jobId; } - return jobId; - } - /* - * TODO Rewrite using purely CommandBuilder. This is breaking encapsulation - */ - public static final List getCommands(List> options, - String keyValueSeparator) { - List oList = new ArrayList(); - for (Option o : options) { - oList.add(o.toCommand(keyValueSeparator)); + /* + * TODO Rewrite using purely CommandBuilder. This is breaking encapsulation + */ + public static final List getCommands(List> options, + String keyValueSeparator) { + List oList = new ArrayList(); + for (Option o : options) { + oList.add(o.toCommand(keyValueSeparator)); + } + return oList; } - return oList; - } } -- 1.7.10.2