/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2016, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* 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 GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.wps;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.Unmarshaller;
import org.geotoolkit.ows.xml.v110.ExceptionReport;
import org.geotoolkit.ows.xml.v110.ExceptionType;
import org.geotoolkit.process.ProcessDescriptor;
import org.geotoolkit.process.ProcessException;
import org.geotoolkit.processing.AbstractProcess;
import org.geotoolkit.security.ClientSecurity;
import org.geotoolkit.wps.xml.WPSMarshallerPool;
import org.geotoolkit.wps.xml.v100.Execute;
import org.geotoolkit.wps.xml.v100.ExecuteResponse;
import org.geotoolkit.wps.xml.v100.ProcessFailedType;
import org.geotoolkit.wps.xml.v100.ProcessStartedType;
import org.geotoolkit.wps.xml.v100.StatusType;
import org.opengis.parameter.ParameterValueGroup;
/**
*
* @author Johann Sorel (Geomatys)
*/
public class WPSProcess extends AbstractProcess {
private final Map<String,String> inputTypes;
private final WebProcessingClient client;
//keep track of last progress state
private Integer lastProgress;
private String lastMessage;
public WPSProcess(WebProcessingClient client, ProcessDescriptor desc, Map<String,String> inputTypes, ParameterValueGroup params) {
super(desc, params);
this.client = client;
this.inputTypes = inputTypes;
}
@Override
protected void execute() throws ProcessException {
final Execute exec = client.createRequest(getInput(), getDescriptor(), inputTypes);
final ExecuteResponse response = sendExecuteRequest(exec, this);
client.fillOutputs(outputParameters, getDescriptor(), response);
}
/**
* Send the Execute request to the server URL an return the unmarshalled response.
*
* @param exec the request
* @param process process used for throw ProcessException
* @return ExecuteResponse.
* @throws ProcessException is can't reach the server or if there is an error during Marshalling/Unmarshalling request
* or response.
*/
private ExecuteResponse sendExecuteRequest(final Execute exec, final WPSProcess process) throws ProcessException {
try {
Object respObj = client.sendSecuredRequestInPost(exec);
respObj = checkResult(respObj);
if (respObj instanceof ExecuteResponse) {
final ExecuteResponse response = (ExecuteResponse) respObj;
// Check if distant process has failed, in case we throw an exception.
final ProcessFailedType processFailed = response.getStatus().getProcessFailed();
if (processFailed != null) {
final StringBuilder errorText = new StringBuilder(process.getDescriptor().getIdentifier().getCode()+ " failed.");
final ExceptionReport report = processFailed.getExceptionReport();
if (report != null) {
final List<ExceptionType> exceptionTypes = report.getException();
for (ExceptionType type : exceptionTypes) {
errorText.append('\n').append(type.getExceptionCode()).append(" : ");
for (String txt : type.getExceptionText()) {
errorText.append("\n\t").append(txt);
}
}
}
throw new ProcessException(errorText.toString(), process, null);
} else {
return response;
}
} else if (respObj instanceof ExceptionReport) {
final ExceptionReport report = (ExceptionReport) respObj;
final ExceptionType excep = report.getException().get(0);
throw new ProcessException("Exception when executing the process.", process, new Exception(excep.toString()));
}
throw new ProcessException("Invalid response type.", process, null);
} catch (ProcessException e) {
throw e;
} catch (JAXBException ex) {
throw new ProcessException("Error when trying to parse the Execute response xml: ", process, ex);
} catch (IOException ex) {
throw new ProcessException("Error when trying to send request to the WPS server :", process, ex);
} catch (Exception e) {
throw new ProcessException(e.getMessage(), process, e);
}
}
/**
* A Function to ensure response object is success or failure. Otherwise, we request continually statusLocation until
* we reach wanted result.
* @param respObj The execute response given by service.
*/
private Object checkResult(Object respObj) throws IOException, JAXBException, InterruptedException {
if (respObj instanceof ExceptionReport) {
return respObj;
} else if (respObj instanceof ExecuteResponse) {
StatusType status = ((ExecuteResponse) respObj).getStatus();
if (status.getProcessFailed() != null || status.getProcessSucceeded() != null) {
return respObj;
}
client.getLogger().log(Level.INFO, "Response object got, it's nor a succes nor a fail. Start querying statusLocation.");
final Unmarshaller unmarshaller = WPSMarshallerPool.getInstance().acquireUnmarshaller();
final ClientSecurity security = client.getClientSecurity();
final URL statusLocation = security.secure(new URL(((ExecuteResponse) respObj).getStatusLocation()));
Object tmpResponse;
int timeLapse = 3000;
//we tolerate a few unmarshalling or IO errors, the servers behave differentely
//and may not offer the result file right from the start
int failCount = 0;
while (true) {
//timeLapse = Math.min(timeLapse * 2, maxTimeLapse);
synchronized (this) {
wait(timeLapse);
}
try{
tmpResponse = unmarshaller.unmarshal(security.decrypt(statusLocation.openStream()));
if (tmpResponse instanceof JAXBElement) {
tmpResponse = ((JAXBElement) tmpResponse).getValue();
}
failCount = 0;
}catch(UnmarshalException | IOException ex){
if(failCount<5){
failCount++;
continue;
}else{
//server seems to have a issue or can't provide status
//informations in any case we don't known what is
//happenning so we consider the process failed
throw ex;
}
}
if (tmpResponse instanceof ExecuteResponse) {
status = ((ExecuteResponse) tmpResponse).getStatus();
final ProcessStartedType processStarted = status.getProcessStarted();
if(processStarted!=null){
if( !Objects.equals(processStarted.getPercentCompleted(),lastProgress)
|| !Objects.equals(processStarted.getValue(), lastMessage)){
fireProgressing(processStarted.getValue(), processStarted.getPercentCompleted(), false);
lastProgress = processStarted.getPercentCompleted();
lastMessage = processStarted.getValue();
}
}
if (status.getProcessFailed() != null || status.getProcessSucceeded() != null) {
respObj = tmpResponse;
break;
}
} else if (tmpResponse instanceof ExceptionReport) {
respObj = tmpResponse;
break;
}
}
}
return respObj;
}
}