V1_0.java [src/java/m/wqm/nutrientslpsrp] Revision: c8267d3d811857083245c77ec9baaccce7b3e13f  Date: Mon Sep 28 15:08:45 MDT 2015
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package m.wqm.nutrientslpsrp;

/**
 *
 * @author Shaun Case
 */
import csip.ModelDataService;
import csip.utils.JSONUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.Path;
import javax.ws.rs.core.UriBuilder;
import oms3.annotations.Description;
import oms3.annotations.Name;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

/**
 *
 * @author Shaun Case
 */
@Name("WQM-21: Nutrient Soil Leaching and Runoff Loss Potentials for an Area of Analysis (NutrientSLP-SRP)")
@Description("This service intersects area of analysis (AoA) and soil mapunit geometries, gets soil parameters, and computes nutrient soil leaching and runoff potentials as an end-to-end process. The services combines WQM-02, WQM-05, and WQM-06 services into a single service. It returns a results payload containing the relevant attributes for each soil component in the AoA, leaching (SLP) and runoff (SRP) potentials for each soil component, and weighted average leaching and runoff loss potential values for the AoA. The services allows for submitting parameter edits. For example, the request payload can contain just the AoA geometry and the services gets soil parameters and computes SLP and SRP and returns the results, including the parameters. If an application user edits the parameters, a subsequent request payload can contain the parameter edits and not the geometry.")
@Path("m/nutrientslpsrp/1.0")


public class V1_0 extends ModelDataService {

    //Quick Parameters, here for quick modification
    private final String WQM_02_Service_Path = "/csip-wqm/m/wqmsoilattributes/1.0";
    private final String WQM_05_Service_Path = "/csip-wqm/m/nutrient_slp/1.0";
    private final String WQM_06_Service_Path = "/csip-wqm/m/scsednut_srp/1.0";    
    
    
    private boolean aoa_comp_drained = false;
    private String aoa_id = "0";
    private JSONArray soilComponents = null;
    private JSONObject aoaGeometry = null;
    private Boolean is_5_6_Only;
    private String error_msg = "";
    
    private wqm_21 Service;

    @Override
    // reading the inputs from the json file into input object and placing it in the arraylist
    protected void preProcess(){        
        this.Service = null;
        
        JSONArray request = getRequest().optJSONArray("parameter");
        
	try{
	    if ( JSONUtils.checkKeyExistsB( JSONUtils.preprocess(request), "AoAId") && JSONUtils.checkKeyExistsB( JSONUtils.preprocess(request), "aoa_geometry") ){
		aoa_id = getStringParam("AoAId");
		aoa_comp_drained = getBooleanParam("aoa_comp_drained");
		//Get the entire aoa_geometry group as it matches the input payload exactly for WQM-2
		aoaGeometry = getJSONParam("aoa_geometry");
		this.is_5_6_Only = false;
	    }
	    else
		if ( JSONUtils.checkKeyExistsB( JSONUtils.preprocess(request), "soilcomponents") ){
		    //  If this key exists then we have it at the top level by design, so keep the object as the payload
		    aoa_id = getStringParam("AoAId");
		    this.soilComponents = request;
		    this.is_5_6_Only = true;
		}
		else{
		    //  No valid input stream for this service
		    this.error_msg = "No valid input parameters found .";
		}      
	}
	catch( Exception ex ){
	    this.error_msg = "Error reading JSON request: " + ex.getMessage();
	}
    }

    @Override
    protected String process(){
	if ( this.error_msg.isEmpty() ){
	    try{
		//  Call WQM-2 processing member functions, then apply results to WQM-5() and WQM-6() objects inputs        
		if ( this.is_5_6_Only ){
		    //This is just a 5/6 combined request
		    Service = new wqm_21( this.soilComponents );           
		}
		else{
		    //This is a full combined request 2/5/6
		    Service = new wqm_21( this.aoa_id, this.aoa_comp_drained, this.aoaGeometry);
		}

		if ( !Service.process() ){
		    this.error_msg += " " + Service.getErrorMsg();
		}
	    }
	    catch( Exception ex ){
		this.error_msg += "Error processing this request: " + ex.getMessage();
	    }
	}
        
        return ( this.error_msg.isEmpty()? EXEC_OK : this.error_msg );
    }

    @Override
    //writing the results back to JSON
    protected void postProcess() throws Exception {
        
        //  Return the result arrays produced by WQM-2, WQM-5, and WQM-6
        JSONArray resultArr = new JSONArray();
        if ( null != this.Service ){
            putResult("AoAId", this.aoa_id, "Area of analysis identifier");  
            putResult("aoa_nslp", this.Service.WQM_5.getAoaNSLP(), "Soil leaching potential of the area of analysis");
            putResult("aoa_srp", this.Service.WQM_6.getAoaSRP(), "Soil runoff potential for the area of analysis");            
            
            resultArr.put( this.Service.createJSONResult() );
            
            putResult("soil_components", resultArr, "List of Soil Components");            
        }               
    }
    
    
    //Inner classes
    class wqm_21{
        private final String WQM2Service;
        private final String WQM5Service;
        private final String WQM6Service;
        private final String aoaId;
        private final Boolean aoa_comp_drained;
        private String aoa_nslp;
        private String aoa_srp;
        private final JSONObject aoaGeometry; 
        private final JSONArray soilComponents;
        private JSONObject results;
        private String error_msg;
        
        private wqm_2Result WQM_2;
        private wqm_5Result WQM_5;
        private wqm_6Result WQM_6;
        
        wqm_21( JSONArray soilComponents ){
            this.aoaId = "none";
            this.aoa_comp_drained = false;
            this.aoaGeometry = null;             
            this.aoa_nslp = "Error";
            this.soilComponents = soilComponents;
            this.error_msg = "";
            this.results = null;
            this.WQM_2 = null;
            this.WQM_5 = null;
            this.WQM_6 = null;            
            
            WQM2Service = UriBuilder.fromUri(getRequestURL()).replacePath(WQM_02_Service_Path).toString();
            WQM5Service = UriBuilder.fromUri(getRequestURL()).replacePath(WQM_05_Service_Path).toString();
            WQM6Service = UriBuilder.fromUri(getRequestURL()).replacePath(WQM_06_Service_Path).toString();           
        }
        
        wqm_21( String AoAId, Boolean aoa_comp_drained, JSONObject AoAGeometry ){
            this.aoaId = AoAId;
            this.aoa_comp_drained = aoa_comp_drained;
            this.aoaGeometry = AoAGeometry;
            this.soilComponents = null;
            
            this.error_msg = "";
            this.results = null;
            this.WQM_2 = null;
            this.WQM_5 = null;
            this.WQM_6 = null; 
            
            WQM2Service = UriBuilder.fromUri(getRequestURL()).replacePath(WQM_02_Service_Path).toString();
            WQM5Service = UriBuilder.fromUri(getRequestURL()).replacePath(WQM_05_Service_Path).toString();
            WQM6Service = UriBuilder.fromUri(getRequestURL()).replacePath(WQM_06_Service_Path).toString();             
        }
        
        public Boolean process(){
            Boolean ret_val = true;
            JSONObject result;
            JSONObject WQM5_input = null;                        
            ServiceCall wqmService = new ServiceCall();             
            
            try{
                if (this.aoaGeometry != null ){
                    JSONObject wqm2RequestString = this.createWQM2Request();

                    //  Call WQM-2           
                    result = wqmService.getResult( this.WQM2Service, wqm2RequestString );

                    if ( null != result ){            
                        // Parse WQM-2 results and build WQM-5 and WQM-6 requests
                        if ( this.parseWQM2Result( result ) ){                        
                            WQM5_input = this.WQM_2.convertResultToWQM5Input( this.aoaId, this.aoa_comp_drained );
                            
                            if ( this.WQM_2.getError() || ( null == WQM5_input ) ){
                                ret_val = false;
                                this.error_msg += " " + wqmService.getErrorMsg();
                            }                                                                    
                        }
                        else{
                            ret_val = false;                            
                        }                   
                    }
                    else{
                        ret_val = false;
                        this.error_msg += " " + wqmService.getErrorMsg();
                    }                            
                }//End WQM-02 request
                else{
                    WQM5_input = this.createWQM5Request();                                        
                }
                
		if ( ret_val == true ){
		    //  Send this input to the WQM5 service...
		    result = wqmService.getResult( this.WQM5Service, WQM5_input );

		    if ( null != result ){
			// Got a return from WQM-05
			//Store result
			if ( this.parseWQM5Result( result ) ){                     
			    //Call WQM-06
			    //  NOTE:  WQM-06 uses the same input as WQM-05...no need to change here.
			    result = wqmService.getResult( this.WQM6Service, WQM5_input );

			    //Store result
			    if ( this.parseWQM6Result( result, this.WQM_5.getFinalComponentMap())){             
				this.aoa_nslp = this.WQM_5.getAoaNSLP();
				this.aoa_srp = this.WQM_6.getAoaSRP();
			    }
			    else{
				ret_val = false;
			    }                        
			}
			else{			    
			    ret_val = false;
			} 
		    }
		    else{
			ret_val = false;
			this.error_msg += " " + wqmService.getErrorMsg();
		    }   
		}
            }  // End try block
            catch(JSONException ex){
                this.error_msg += " " + ex.getMessage();
                ret_val = false;
            }
            
            return ret_val;
        }
        
        public JSONArray createJSONResult(){
            JSONArray ret_val = new JSONArray();
            ArrayList<V1_0.wqm_21.finalSoilComponent> componentList;
            componentList = this.WQM_5.getFinalComponentList();
            
            try{
                for ( V1_0.wqm_21.finalSoilComponent component : componentList) {
                    JSONArray array = new JSONArray();
                    array.put(component.getJSONCokey());
                    array.put(component.getJSONCompNSLP());
                    array.put(component.getJSONCompSRP());
                    ret_val.put(JSONUtils.dataDesc("soil_component", array, "Soil Component"));
                }   
            }
            catch (JSONException ex){
                LOG.info("Did not finish building WQM-21 result output.");
                LOG.info( ex.getMessage() );                
            }
            
            return ret_val;
        }
        
        private JSONObject createWQM2Request() throws JSONException{
           JSONObject ret_val;
           JSONArray headerArray;
           JSONObject metainfo;
           
           metainfo = new JSONObject();
           ret_val = new JSONObject();
           headerArray = new JSONArray();
           
           metainfo.put( "MultipartRequest", "Bundled Service Request WQM-21");
           metainfo.put( "OriginalSource", getRequestHost() );
           metainfo.put( "OriginalRequest", getRequestURL() );
           metainfo.put( "OriginalSUID", getSUID() );
           ret_val.put("metainfo", metainfo );                     
           headerArray.put(JSONUtils.dataDesc("AoAId",this.aoaId, "Area of Analysis Identifier" ));
           headerArray.put(JSONUtils.dataDesc( "aoa_geometry", this.aoaGeometry, null ) );
           ret_val.put( "parameter", headerArray);           
            
           return ret_val;          
        }

        private JSONObject createWQM5Request() throws JSONException{
           JSONObject ret_val;
           JSONObject metainfo;
           
           metainfo = new JSONObject();
           ret_val = new JSONObject();
           
           metainfo.put( "MultipartRequest", "Bundled Service Request WQM-21");
           metainfo.put( "OriginalSource", getRequestHost() );
           metainfo.put( "OriginalRequest", getRequestURL() );
           metainfo.put( "OriginalSUID", getSUID() );
           ret_val.put("metainfo", metainfo );                        
           ret_val.put( "parameter", this.soilComponents);           
            
           return ret_val;          
        }        
        
        private Boolean parseWQM2Result( JSONObject result ){
            Boolean ret_val = true;
            
            this.WQM_2 = new V1_0.wqm_21.wqm_2Result( result );
            if ( this.WQM_2.getError() ){
                this.error_msg += this.WQM_2.getErrorMsg();
                ret_val = false;                
            }
            
            return ret_val;            
        }        
        
        private Boolean parseWQM5Result( JSONObject result ){
            Boolean ret_val = true;
            
            this.WQM_5 = new V1_0.wqm_21.wqm_5Result( result );
            if ( this.WQM_5.getError() ){
                this.error_msg += this.WQM_5.getErrorMsg();
                ret_val = false;                
            }
            
            return ret_val;
        }

        private Boolean parseWQM6Result( JSONObject result, HashMap<String, V1_0.wqm_21.finalSoilComponent> finalComponentMap ){
            Boolean ret_val = true;
            
            this.WQM_6 = new V1_0.wqm_21.wqm_6Result( result, finalComponentMap );
            if ( this.WQM_6.getError() ){
                this.error_msg += this.WQM_6.getErrorMsg();
                ret_val = false;                
            }            
            return ret_val;
        } 
        
        public JSONObject getResults(){return this.results;}
        public String getErrorMsg(){return this.error_msg;}
        public Boolean getError(){return !this.error_msg.isEmpty();}
        
        
        // Inner Classes
        
        class wqm_2Result{
        
            private String error_msg;
            private JSONObject originalResult;
            private ArrayList <V1_0.wqm_21.wqm2SoilComponent> components;
            
            wqm_2Result( JSONObject result ){
                this.error_msg = "";
                this.originalResult = result;
                components = new ArrayList<>();
                
                try{
                    JSONArray resultArray = result.getJSONArray("result");  
                    JSONObject soilsList = resultArray.optJSONObject(1);  //This should be the soil_component_list;
                    JSONArray soils = soilsList.getJSONArray("value");                 
                    
                   
                    //  for each array in this result...build a list of components.
                    if ( soils.length() > 0 ){
                        for( int i = 0; i < soils.length(); i++){
                            V1_0.wqm_21.wqm2SoilComponent tComponent;       
                            JSONArray componentData = soils.getJSONObject(i).getJSONArray("value");
                            tComponent = new V1_0.wqm_21.wqm2SoilComponent( componentData );  
                            this.components.add(tComponent);
                        }
                    } 
                    else{
                        this.error_msg += " There was no return result from the WQM-02 service. ";
                    }
                   
                }
                catch( JSONException ex ){
                    this.error_msg += " Error parsing returned data from the WQM-02 service: " + ex.getMessage();
                }                                                    
            }
            
            public JSONObject convertResultToWQM5Input( String AoAId, Boolean aoa_comp_drained ){
                JSONObject wqm5 = new JSONObject();
                JSONObject metainfo = new JSONObject();
                JSONArray headerArray = new JSONArray();
                JSONArray soilsArray = new JSONArray();
                
                try{
                //Put in necessary top level stuff here first
                    metainfo.put( "MultipartRequest", "Bundled Service Request WQM-21");
                    metainfo.put( "OriginalSource", getRequestHost() );
                    metainfo.put( "OriginalRequest", getRequestURL() );
                    metainfo.put( "OriginalSUID", getSUID() );
                    wqm5.put("metainfo", metainfo );                     
                    headerArray.put(JSONUtils.dataDesc("AoAId", AoAId, "Area of Analysis Identifier" ));
                
                    //Fill in all component data
                    for ( V1_0.wqm_21.wqm2SoilComponent component : this.components ){
                        soilsArray.put(component.getWQM5Array());
                    }
                    
                    headerArray.put( JSONUtils.dataDesc("soilcomponents", soilsArray, null) );                    
                    wqm5.put( "parameter", headerArray );                
                }
                catch( JSONException ex ){
                    this.error_msg += " Cannot convert WQM-02 result to WQM-05 input: " + ex.getMessage();
                }
                
                return wqm5;
            }
            
            public String getErrorMsg(){return this.error_msg;}
            public Boolean getError(){return !this.error_msg.isEmpty();}
        }
        
        class wqm_5Result{
            private final HashMap<String, V1_0.wqm_21.finalSoilComponent>finalComponentMap;
            private final ArrayList<V1_0.wqm_21.finalSoilComponent> finalComponents;
            private String error_msg;
            private String aoa_nslp;
            
            wqm_5Result( JSONObject result ){
                this.finalComponentMap = new HashMap<>();
                finalComponents = new ArrayList<>();
                this.error_msg = "";
                this.aoa_nslp = "Error";
                
                if ( null != result ){

                    try{
                        JSONArray resultArray = result.getJSONArray("result");  
                        Map<String, JSONObject> topLevel = JSONUtils.preprocess( resultArray );
                        this.aoa_nslp = topLevel.get("aoa_nslp").getString("value");
                                      
                        JSONArray soils = topLevel.get("soil_components").getJSONArray("value");

                        //  for each array in this result...build a list of components.
                        if ( soils.length() > 0 ){
                            for( int i = 0; i < soils.length(); i++){
                                V1_0.wqm_21.finalSoilComponent tComponent;       
                                Map<String, JSONObject> soilComponent;
                                String cokey;
                                String comp_nslp;
                                JSONArray componentData = soils.getJSONObject(i).getJSONArray("value"); //Value of "soil_component"
                                soilComponent = JSONUtils.preprocess( componentData );
                                cokey = JSONUtils.getStringParam( soilComponent,"cokey", "Error" );
                                comp_nslp = JSONUtils.getStringParam( soilComponent,"comp_nslp", "Error" );
                                                                        
                                tComponent = new V1_0.wqm_21.finalSoilComponent( cokey );  
                                tComponent.setCompNSLP( comp_nslp );
                                this.finalComponents.add( tComponent );
                                if ( !this.finalComponentMap.containsKey(cokey) ){
                                    this.finalComponentMap.put( cokey, tComponent );
                                }
                                else{
                                    //We've got a real problem if this ever happens
                                    this.error_msg = " Duplicate soil component keys found in WQM-05 output results.  Cannot proceed. ";
                                    break;
                                }
                            }
                        } 
                        else{
                            this.error_msg += " There was no return result from the WQM-05 service. ";
                        }                   
                    }
                    catch( JSONException ex ){
                        this.error_msg += " Error parsing returned data from the WQM-05 service: " + ex.getMessage();
                    }                                                                                                      
                }
                else{
                    this.error_msg = " WQM-05 service results are empty. ";
                }
            }
            
            public HashMap<String, V1_0.wqm_21.finalSoilComponent>getFinalComponentMap(){return this.finalComponentMap;}
            public ArrayList<V1_0.wqm_21.finalSoilComponent> getFinalComponentList(){ return this.finalComponents; }
            public String getAoaNSLP(){ return this.aoa_nslp;}
            public Boolean getError(){return !this.error_msg.isEmpty();}
            public String getErrorMsg(){return this.error_msg;}
        }   
        
        class wqm_6Result{
            private String error_msg;
            private String aoa_srp;
            
            wqm_6Result( JSONObject result, HashMap<String, V1_0.wqm_21.finalSoilComponent>finalComponentMap ){
                this.error_msg = "";
                this.aoa_srp = "Error";
                
                if ( null != result ){
                    if ( !finalComponentMap.isEmpty() ){
                        try{
                            JSONArray resultArray = result.getJSONArray("result");  
                            Map<String, JSONObject> topLevel = JSONUtils.preprocess( resultArray );
                            this.aoa_srp = topLevel.get("aoa_srp").getString("value");
                                      
                            JSONArray soils = topLevel.get("soil_components").getJSONArray("value");           


                            //  for each array in this result...build a list of components.
                            if ( soils.length() > 0 ){
                                for( int i = 0; i < soils.length(); i++){
                                    V1_0.wqm_21.finalSoilComponent tComponent;       
                                    Map<String, JSONObject> soilComponent;
                                    String cokey;
                                    String comp_srp;
                                    JSONArray componentData = soils.getJSONObject(i).getJSONArray("value"); //Value of "soil_component"
                                    soilComponent = JSONUtils.preprocess( componentData );
                                    cokey = JSONUtils.getStringParam( soilComponent,"cokey", "Error" );
                                    comp_srp = JSONUtils.getStringParam( soilComponent,"comp_srp", "Error" );

                                    tComponent = finalComponentMap.get( cokey );
                                    
                                    if ( null != tComponent ){
                                        tComponent.setCompSRP( comp_srp );
                                    }
                                    else{
                                        //We've got a real problem if this ever happens
                                        this.error_msg = " WQM-06 result parsing:  Cannot locate this soil component key in the WQM-05 output results.  Cannot proceed. ";
                                        break;
                                    }
                                }
                            } 
                            else{
                                this.error_msg += " There was no return result from the WQM-06 service. ";
                            }                   
                        }
                        catch( JSONException ex ){
                            this.error_msg += " Error parsing returned data from the WQM-06 service: " + ex.getMessage();
                        }                                                                         
                    }
                    else{
                        this.error_msg = " Cannot merge WQM-06 results with WQM-05 results.  Empty component map. ";
                    }
                }
                else{
                    this.error_msg = " WQM-06 service results are empty. ";
                }
            } 
            
            public String getAoaSRP(){ return this.aoa_srp;}
            public Boolean getError(){return !this.error_msg.isEmpty();}
            public String getErrorMsg(){return this.error_msg;}                        
        }   
        
        class finalSoilComponent{
            private final String cokey;
            private String comp_nslp;
            private String comp_srp;
            
            finalSoilComponent( String cokey ){
                this.cokey = cokey;
                this.comp_nslp = "Error";
                this.comp_srp = "Error";
            }
            
            public void setCompNSLP( String comp_nslp ){
                this.comp_nslp = comp_nslp;
            }
            
            public void setCompSRP( String comp_srp ){
                this.comp_srp = comp_srp;
            }
            
            public String getCompNSLP(){return this.comp_nslp;}
            public String getCompSRP(){return this.comp_srp;}
            public String getCokey(){return this.cokey;}
            public JSONObject getJSONCompNSLP() throws JSONException{return JSONUtils.dataDesc("comp_nslp", this.comp_nslp, "Soil leaching potential of the soil component" );}
            public JSONObject getJSONCompSRP() throws JSONException{return JSONUtils.dataDesc("comp_srp", this.comp_srp, "Soil runoff potential for soil component" );}
            public JSONObject getJSONCokey() throws JSONException{return JSONUtils.dataDesc("cokey", this.cokey, "Soil component key" );}            
        }
        
        class wqm2SoilComponent{            
            private JSONArray orignalResult;
            private JSONArray newResult;            
            
            private String error_msg = "";
            
            wqm2SoilComponent( JSONArray soilComponent ){    
                this.orignalResult = soilComponent;
                
                try{
                    this.newResult = new JSONArray( soilComponent.toString() );
                    this.newResult.put(JSONUtils.dataDesc("aoa_comp_drained", aoa_comp_drained, "Soil Component Key"));                    
                }
                catch( JSONException ex ){
                    this.error_msg = ex.getMessage();
                }                                    
            }
            
            public JSONArray getWQM5Array(){ return this.newResult; }
            public Boolean getError(){ return (!this.error_msg.isEmpty()); }
            public String getErrorMsg(){ return this.error_msg; }
            
        }
    }
    
    class ServiceCall{
        private final csip.Client newClient;
        private String error_msg;
        
        ServiceCall(){            
            this.error_msg = "";
            this.newClient = new csip.Client();              
        }
        
        public JSONObject getResult( String URI, JSONObject request ){
            JSONObject ret_val = null;
            
            try {               
                ret_val = this.newClient.doPOST( URI, request );                 
                if  (( null == ret_val ) || ( ret_val.length() <= 0 ) ){
                    this.error_msg += " No data was returned from " + URI + " for this request. ";
                }                                
            }
            catch (Exception ex) {
                this.error_msg += " Cannot make a connection to that location: " + URI + ".  " + ex.getMessage();
            }          
            
            return ret_val;
        }    
        
        public JSONObject getResult( String URI, String request ){
            JSONObject ret_val = null;
            
            try{
                ret_val = this.getResult( URI, new JSONObject(request) );
            }
            catch (JSONException ex ){
                this.error_msg += " Could not create a JSON Object for this request. " + ex.getMessage() + " ";
            }
            
            return ret_val;
        }
        
        public String getErrorMsg(){return this.error_msg;}
        public Boolean getError(){return !this.error_msg.isEmpty();}
    }
}