Overview

PhosphoPrediction项目是Chris和我做的一个新项目,主要是为本地客户端程序添加一个相同功能的web server。由于出差新疆,只能晚上回酒店自己加班写代码,在Chris的帮助下,前前后后忙了大约两周总算有了个不错的小成果,心中颇感欣慰。这段时间,Chris不仅给了我技术上的指导,更给我排解了心中的许多烦恼,在此感谢我最好的朋友Chris(我知道你不喜欢我当面夸奖感谢你,哈哈)。

1.Java web服务器实现的功能

1.1 前端接收序列传入后台

很多类似的项目一次只能接收一条序列,本项目可以同时接收多条序列,包含名字的FASTA格式的序列和不包含名字的raw序列。在webserver界面可以选择model,默认为ATM;同时可以选择threshold,默认全部显示。

1.2 后台处理序列,并展示在jsp页面

后台共接收3个参数,即seqStringmodelthreshold,并生成getterssetters

private String seqString;
private String model;
private double threshold;

public String getSeqString() {
    return seqString;
}
public void setSeqString(String seqString) {
    this.seqString = seqString;
}

public String getModel() {
    return model;
}
public void setModel(String model) {
    this.model = model;
}

    public double getThreshold() {
    return threshold;
}
public void setThreshold(double threshold) {
    this.threshold = threshold;
}

我们在前端得到的threshold,仅仅是double类型的value,我们处理成String类型并展示在result界面。我们可以根据选择的阈值threshold,展示我们所需要的结果。为此,我们将处理result的模块剥离出来,放在formatResults.java类中。该类提供两个方法,是重载关系,唯一的区别是传入的参数threshold

//不包含threshold的方法,得到全部的sites对应的results
public ArrayList<Result> getFormatResults(String originSeqString, String model) {
    RemoteWork robot = new RemoteWork();
    ArrayList<Result> results = robot.formatInput(originSeqString);
    ArrayList<Result> allResults = new ArrayList<>();
    String seq_name="";
    String seq="";
    int input_count=1;
    String input_replace="";
    Result res;
    for(int i=0; i<results.size(); i++){
        res=results.get(i);
        if(res.getName().substring(0, 5).equals("input")) {
            input_replace = "-" + input_count;
            res.setName(res.getName().replaceAll("-input", input_replace)+" *");
            input_count++;
        }
        seq_name = res.getName();
        seq = res.getSeq();
        
        res.setDiso(robot.runDiso(seq));
        
        res.setSs(robot.runSable_ss(seq));
        
        res.setSa(robot.runSable_sa(seq));
        
        robot.getFunc(seq_name);
        
        res.setSites(robot.Predict(seq, model, seq_name));
        allResults.add(res);
    }
    return allResults;
}

//包含threshold的方法,得到筛选的sites对应的results
public ArrayList<Result> getFormatResults(String originSeqString, String originModel, double originThreshold) {
    ArrayList<Result> allResults = getFormatResults(originSeqString, originModel);
    
    ArrayList<Result> formatResults = new ArrayList<>();
    PredictionSite site=new PredictionSite();
    
    for(Result result:allResults) {
        ArrayList<PredictionSite> selectedPredictionSites = new ArrayList<>();
        for(int j=0; j<result.getSites().size(); j++) {
            site=result.getSites().get(j);
            double d=Double.valueOf(site.getProb());
            if(originThreshold==0) {
                selectedPredictionSites.add(site);
            }
            else if(originThreshold==0.3){
                if(!(d<0.3)){
                    selectedPredictionSites.add(site);
                }
            }
            else if(originThreshold==0.5){
                if(!(d<0.5)){
                    selectedPredictionSites.add(site);
                }
            }
            else if(originThreshold==0.8){
                if(!(d<0.8)){
                    selectedPredictionSites.add(site);
                }
            }
        }
        result.setSites(selectedPredictionSites);
        formatResults.add(result);
    }
    
    return formatResults;
}

action中得到formatResults后,在jsp页面中循环,这里用到了jstl表达式,需要引入包jstl.jar,可以点击这里下载jsp页面的头部加上这么一句:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>


<c:forEach items="${formatResults }" var="res">
    <b> ${res.getName() } </b>
    <c:forEach items="${res.getSites() }" var="site">
        <c:out value="${site.getRank() }"/>
        <c:out value="${site.getPos() }"/>
        <c:out value="${site.getLeft().substring(0, 4) }"/></span><span class="emphasisLetter"> ${site.getLeft().substring(4) } </span><span class="constant"><c:out value="${site.getRight() }"/></span>     
        <c:out value="${site.getProb() }"/>
    </c:forEach>

然后显示出Original SequenceNative DisorderSecondary StructureSolvent Accessibility

    <c:out value="${res.getSeq() }"/>
    <c:out value="${res.getDiso() }"/>
    <c:out value="${res.getSs() }"/>
    <c:out value="${res.getSa() }"/>
</c:forEach>

这里面JSTL的详细用法,可参考JSTL标签库学习总结

1.3 jsp页面同时添加导出结果按钮

添加导出为exceltxt两个功能的按钮。不在服务器端生成文件,而在客户端在线实时生成,节约服务器资源。

//结果写入excel文件
public InputStream getExcelFile() {  
    HttpSession session = ServletActionContext.getRequest().getSession();
    formatResults=(ArrayList<Result>)session.getAttribute("formatResults"); 
    
    HSSFWorkbook workbook = new HSSFWorkbook();
    HSSFSheet sheet = workbook.createSheet("sheet1");
    Result result;
    PredictionSite site;
    int k=0;
    for(int i=0; i<formatResults.size(); i++){
        
        result=formatResults.get(i);
        HSSFRow row = sheet.createRow(k++);
        HSSFCell cell = row.createCell(0);
        cell.setCellValue(result.getName());
        row = sheet.createRow(k++);
        cell = row.createCell(0);  
        cell.setCellValue("Rank");  
        cell = row.createCell(1);  
        cell.setCellValue("Position");  
        cell = row.createCell(2);  
        cell.setCellValue("Surrounding Sequence");  
        cell = row.createCell(3);  
        cell.setCellValue("Probability Score"); 
        for( int j=0;j<result.getSites().size();j++ ){
            site=result.getSites().get(j);
            
            row = sheet.createRow(k++);
            cell = row.createCell(0);
            cell.setCellValue(site.getRank());  
            cell = row.createCell(1);  
            cell.setCellValue(site.getPos());  
            cell = row.createCell(2);  
            cell.setCellValue(site.getLeft()+site.getRight());  
            cell = row.createCell(3);  
            cell.setCellValue(site.getProb());
            
        }
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("Original Sequence:"); 
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue(result.getSeq()); 
        
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("Native Disorder:"); 
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue(result.getDiso()); 
        
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("Secondary Structure:"); 
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue(result.getSs()); 
        
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("Solvent Accessibility:"); 
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue(result.getSa()); 
        //换行,打印空行
        row=sheet.createRow(k++);
        cell = row.createCell(0);
        cell.setCellValue("\n");
    }

    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    try {  
        workbook.write(baos);  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
    byte[] ba = baos.toByteArray();  
    ByteArrayInputStream bais = new ByteArrayInputStream(ba);
    return bais;  
    }
public String downloadExcel() throws Exception{
    return SUCCESS;
}

输出excel文件的模块,主要参考了在线实时生成Excel文件流供下载。但是这篇博客写的太繁琐,于是将它简化了一大部分。这其中需要用到一个包:poi-3.13-20150929.jar,最新版本的poi可以在这里下载。更加详细的POI操作excel的方法,请参考这里
而导出txt文件的方法则要简单许多。

public InputStream getTxtFile(){
    HttpSession session = ServletActionContext.getRequest().getSession();
    formatResults=(ArrayList<Result>)session.getAttribute("formatResults");
    Result result;
    PredictionSite site;

    StringBuffer sb=new StringBuffer();
    String newLine,tab,str0,str1,str2,str3,str4,str5,str6,str7,str8,
                str9,str10,str11,str12,str13,str14,str15,str16;
    newLine="\n";
    tab="\t";
    for(int i=0; i<formatResults.size(); i++){

        result=formatResults.get(i);
        str0=result.getName();
        sb.append(str0).append(newLine);
        str1="Rank";  
        str2="Position";  
        str3="Surrounding Sequence";  
        str4="Probability Score"; 
        sb.append(str1).append(tab).append(str2).append(tab).append(str3).append(tab).append(str4).append(tab).append(newLine);
        for( int j=0;j<result.getSites().size();j++ ){
            site=result.getSites().get(j);
            str5=site.getRank();  
            str6=site.getPos();  
            str7=(site.getLeft()+site.getRight());  
            str8=site.getProb();
            sb.append(str5).append(tab).append(str6).append(tab).append(str7).append(tab).append(str8).append(tab).append(newLine);
            
        }
        str9="Original Sequence:"; 
        str10=result.getSeq(); 
        str11="Native Disorder:"; 
        str12=result.getDiso(); 
        str13="Secondary Structure:"; 
        str14=result.getSs(); 
        str15="Solvent Accessibility:"; 
        str16=result.getSa(); 
        sb.append(str9).append(newLine).append(str10).append(newLine).append(str11).append(newLine).append(str12).append(newLine)
        .append(str13).append(newLine).append(str14).append(newLine).append(str15).append(newLine).append(str16).append(newLine).append(newLine);
    }
    
    ByteArrayInputStream in = new ByteArrayInputStream(sb.toString().getBytes());  
    return in; 
}
public String downloadTxt() throws Exception{
    return SUCCESS;
}

2.需要注意的地方

这次的项目,过程中出现过几次低级错误。Chris说:“越是奇葩的bug,往往隐藏着低级的错误。”这句话在这个项目中屡屡应验。记录一下,以避免再犯。

2.1 struts.xml文件配置

本项目共产生3actionwebserver界面点击提交,excel文件导出,txt文件导出。配置如下

    <action name="result" class="jphospho.JPhospho" method="execute">
        <result>
            /serverResult.jsp
        </result>
    </action>
    
    <action name="excel" class="jphospho.JPhospho" method="downloadExcel">
        <result type="stream">
            <param name="contentType">application/vnd.ms-excel</param>  
            <param name="contentDisposition">attachment;fileName="Detail.xls"</param>  
            <param name="inputName">excelFile</param>  
            <param name="bufferSize">1024</param>  
        </result>
    </action>
    
    <action name="txt" class="jphospho.JPhospho" method="downloadTxt">  
        <result type="stream">  
            <param name="contentType">application/octet-stream</param>  
            <param name="inputName">txtFile</param>  
            <param name="contentDisposition">attachment;fileName="Detail.txt"</param>  
            <param name="bufferSize">4096</param>  
        </result>  
    </action>

需要注意的是,第一个actionresult type不能是chain,如果是chain,则会出现不跳转而直接下载文件的bug。那么怎么在action之间进行传值呢?详见下一条。

2.2 action之间传值

配置struts.xml是行不通的,我们采用了HttpSession传值方法。在第一个action中的return SUCCESS之前,加上这么两句代码

    HttpSession session = ServletActionContext.getRequest().getSession();
    session.setAttribute("formatResults", formatResults); 

这样,formatResults就保存在了session中,然后可以在另外两个action中取值了。

    HttpSession session = ServletActionContext.getRequest().getSession();
    formatResults=(ArrayList<Result>)session.getAttribute("formatResults"); 

2.3 处理输入的序列

输入序列如果不包含名字,是多条裸序列,那么原来的项目会当做是一条序列处理,这显然是不符合要求的。我们为每一个序列之前添加一个>ii1开始计数。在这里,处理换行符的时候,有一点要特别注意。接收的字符串中的换行符,一定是\r\n,而输出的文件中,换行需要System.getProperty("line.separator"),否则就会出现问题。

public String getFormatSeqString(String originSeqString) {
    String formatSeqString;
    boolean b = String.valueOf(originSeqString.trim().charAt(0)).equals(">");
    if(b) {
        formatSeqString = originSeqString;
        return formatSeqString;
    } 
    
    String[] strs = originSeqString.trim().split("\r\n");
    ArrayList<String> list = new ArrayList<>();
    int k=1;
    for(int i=0; i<strs.length; i++){
        if(!strs[i].equals("") && !strs[i].equals("\r\n")){
            list.add(">"+(k++)+"\r\n"+strs[i]);
        }
    }
    formatSeqString=String.join("\r\n",list);
    return formatSeqString;
}

2.4 初始化问题

在生成文件方法里,StringBuffer必须得用new初始化,否则会出现空指针错误。

StringBuffer sb=new StringBuffer();

关于StringStringBufferStringBuilder的区别可以参考这里StringBuffer是可扩展且线程安全的,因此,我们用它最好。
另外,多重循环的初始化一定要注意初始化的位置。