Updated Clone

git-svn-id: https://svn.d4science.research-infrastructures.eu/gcube/trunk/portlets/user/tabular-data-gwt-service@99220 82a268e6-3cf1-43bd-a215-b396298e98cf
This commit is contained in:
Giancarlo Panichi 2014-08-05 16:15:56 +00:00
parent 72d56be84c
commit 0dc3ddff6a
11 changed files with 408 additions and 71 deletions

View File

@ -20,6 +20,7 @@ import org.gcube.portlets.user.td.gwtservice.shared.history.OpHistory;
import org.gcube.portlets.user.td.gwtservice.shared.history.RollBackSession;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.AddColumnMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.ChangeTableTypeMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.CloneMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.CodelistMappingMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.DeleteColumnMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.DeleteRowsMonitor;
@ -377,10 +378,19 @@ public interface TDGWTService extends RemoteService {
* @param labelColumnSession
* @throws TDGWTServiceException
*/
public TRId startCloneTabularResource(
public void startCloneTabularResource(
CloneTabularResourceSession cloneTabularResourceSession)
throws TDGWTServiceException;
/**
* Get Operation Monitor during the Clone operation
*
* @return
* @throws TDGWTServiceException
*/
public CloneMonitor getCloneMonitor() throws TDGWTServiceException;
//
/**
* Initialize Codelists Paging Loader

View File

@ -19,6 +19,7 @@ import org.gcube.portlets.user.td.gwtservice.shared.history.OpHistory;
import org.gcube.portlets.user.td.gwtservice.shared.history.RollBackSession;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.AddColumnMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.ChangeTableTypeMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.CloneMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.CodelistMappingMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.DeleteColumnMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.DeleteRowsMonitor;
@ -155,7 +156,8 @@ public interface TDGWTServiceAsync {
void startTDOpen(TDOpenSession tdOpenSession, AsyncCallback<Void> callback);
//Clone
void startCloneTabularResource(CloneTabularResourceSession cloneTabularResourceSession,AsyncCallback<TRId> callback);
void getCloneMonitor(AsyncCallback<CloneMonitor> callback);
void startCloneTabularResource(CloneTabularResourceSession cloneTabularResourceSession, AsyncCallback<Void> callback);
//SDMX

View File

@ -10,8 +10,7 @@ public class SessionConstants {
protected static final String SDMX_REGISTRY_SOURCE = "SDMX_REGISTRY_SOURCE";
protected static final String CLONE_TABULAR_RESOURCE_SESSION = "CLONE_TABULAR_RESOURCE_SESSION";
protected static final String SDMX_CLIENT_ATTRIBUTE = "SDMX_CLIENT";
protected static final String SDMX_IMPORT_SESSION = "SDMX_IMPORT";
protected static final String SDMX_IMPORT_TABULAR_RESOURCE = "SDMX_IMPORT_TABULAR_RESOURCE";
@ -29,7 +28,12 @@ public class SessionConstants {
protected static final String CSV_EXPORT_TASK = "CSV_EXPORT_TASK";
protected static final String CSV_EXPORT_END = "CSV_EXPORT_END";
protected static final String CSV_EXPORT_MONITOR = "CSV_EXPORT_MONITOR";
protected static final String CLONE_TABULAR_RESOURCE_SESSION = "CLONE_TABULAR_RESOURCE_SESSION";
protected static final String CLONE_TABULAR_RESOURCE_MONITOR = "CLONE_TABULAR_RESOURCE_MONITOR";
protected static final String CLONE_TABULAR_RESOURCE_TASK = "CLONE_TABULAR_RESOURCE_TASK";
protected static final String CHANGE_COLUMN_TYPE_SESSION = "CHANGE_COLUMN_TYPE_SESSION";
protected static final String CHANGE_COLUMN_TYPE_MONITOR = "CHANGE_COLUMN_TYPE_MONITOR";
protected static final String CHANGE_COLUMN_TYPE_TASK = "CHANGE_COLUMN_TYPE_TASK";

View File

@ -26,6 +26,7 @@ import org.gcube.portlets.user.td.gwtservice.shared.file.FileUploadMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.history.RollBackSession;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.AddColumnMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.ChangeTableTypeMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.CloneMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.CodelistMappingMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.DeleteColumnMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.DeleteRowsMonitor;
@ -1281,6 +1282,46 @@ public class SessionUtil {
cloneTabularResourceSession);
}
public static CloneMonitor getCloneMonitor(HttpSession httpSession) {
CloneMonitor cloneMonitor = (CloneMonitor) httpSession
.getAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_MONITOR);
if (cloneMonitor != null) {
return cloneMonitor;
} else {
cloneMonitor = new CloneMonitor();
httpSession.setAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_MONITOR, cloneMonitor);
return cloneMonitor;
}
}
public static void setCloneMonitor(HttpSession httpSession,
DeleteRowsMonitor cloneMonitor) {
CloneMonitor cm = (CloneMonitor) httpSession
.getAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_MONITOR);
if (cm != null) {
httpSession.removeAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_MONITOR);
}
httpSession.setAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_MONITOR, cloneMonitor);
}
public static Task getCloneTask(HttpSession httpSession) {
Task monitor = (Task) httpSession.getAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_TASK);
if (monitor == null) {
logger.error("CLONE_TABULAR_RESOURCE_TASK was not acquired");
}
return monitor;
}
public static void setCloneTask(HttpSession httpSession, Task task) {
Task monitor = (Task) httpSession.getAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_TASK);
if (monitor != null)
httpSession.removeAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_TASK);
httpSession.setAttribute(SessionConstants.CLONE_TABULAR_RESOURCE_TASK, task);
}
// /

View File

@ -100,6 +100,7 @@ import org.gcube.portlets.user.td.gwtservice.server.file.FileUtil;
import org.gcube.portlets.user.td.gwtservice.server.opexecution.OpExecution4AddColumn;
import org.gcube.portlets.user.td.gwtservice.server.opexecution.OpExecution4ChangeColumnType;
import org.gcube.portlets.user.td.gwtservice.server.opexecution.OpExecution4ChangeTableType;
import org.gcube.portlets.user.td.gwtservice.server.opexecution.OpExecution4Clone;
import org.gcube.portlets.user.td.gwtservice.server.opexecution.OpExecution4CodelistMapping;
import org.gcube.portlets.user.td.gwtservice.server.opexecution.OpExecution4DeleteColumn;
import org.gcube.portlets.user.td.gwtservice.server.opexecution.OpExecution4DeleteRows;
@ -143,6 +144,7 @@ import org.gcube.portlets.user.td.gwtservice.shared.history.OpHistory;
import org.gcube.portlets.user.td.gwtservice.shared.history.RollBackSession;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.AddColumnMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.ChangeTableTypeMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.CloneMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.CodelistMappingMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.DeleteColumnMonitor;
import org.gcube.portlets.user.td.gwtservice.shared.monitor.DeleteRowsMonitor;
@ -4493,8 +4495,8 @@ public class TDGWTServiceImpl extends RemoteServiceServlet implements
logger.debug("Task exception:"
+ task.getErrorCause());
task.getErrorCause().printStackTrace();
labelColumnMonitor.setError(new Throwable(task
.getErrorCause()));
labelColumnMonitor.setError(task
.getErrorCause());
} else {
logger.debug("Task exception: Error In LabelColumnMonitor");
labelColumnMonitor.setError(new Throwable(
@ -5005,7 +5007,7 @@ public class TDGWTServiceImpl extends RemoteServiceServlet implements
* {@inheritDoc}
*/
@Override
public TRId startCloneTabularResource(
public void startCloneTabularResource(
CloneTabularResourceSession cloneTabularResourceSession)
throws TDGWTServiceException {
try {
@ -5021,57 +5023,26 @@ public class TDGWTServiceImpl extends RemoteServiceServlet implements
TabularDataService service = TabularDataServiceFactory.getService();
TabularResourceId serviceTR = new TabularResourceId(
Long.valueOf(cloneTabularResourceSession.getTrId().getId()));
OpExecution4Clone opEx = new OpExecution4Clone(service,
cloneTabularResourceSession);
OpExecutionDirector director = new OpExecutionDirector();
director.setOperationExecutionBuilder(opEx);
director.constructOperationExecution();
OperationExecution invocation = director.getOperationExecution();
TabularResource cloned = service.cloneTabularResource(serviceTR);
logger.debug("Clone: " + cloned.toString());
Table table = service.getLastTable(cloned.getId());
if (table == null) {
String report = "This tabular resource has not valid last table! (TR id="
+ cloned.getId() + ")";
logger.error(report);
throw new TDGWTServiceException(report);
if (invocation == null) {
throw new TDGWTServiceException(
"Error in invocation: Operation not supported");
}
Long id = Long.valueOf(cloneTabularResourceSession.getTrId().getId());
logger.debug("Last Table: " + table.toString());
Table viewTable = null;
if (table.contains(DatasetViewTableMetadata.class)) {
DatasetViewTableMetadata dwm = table
.getMetadata(DatasetViewTableMetadata.class);
try {
viewTable = service.getTable(dwm
.getTargetDatasetViewTableId());
} catch (Exception e) {
logger.error("view table not found");
}
} else {
logger.debug("No DatasetViewTableMeatadata found");
}
TRId trId;
if (viewTable == null) {
trId = new TRId(new Long(cloned.getId().getValue()).toString(),
cloned.getTableType(), new Long(table.getId()
.getValue()).toString(), table.getTableType()
.getName());
} else {
trId = new TRId(new Long(cloned.getId().getValue()).toString(),
cloned.getTableType(), String.valueOf(viewTable.getId()
.getValue()), viewTable.getTableType()
.getName(), String.valueOf(table.getId()
.getValue()), true);
}
TabResource tabResource = new TabResource();
tabResource.setTrId(trId);
SessionUtil.setTabResource(session, tabResource);
SessionUtil.setTRId(session, trId);
return trId;
TabularResourceId serviceTR = new TabularResourceId(id);
logger.debug("OperationInvocation: \n" + invocation.toString());
Task trTask = service.execute(invocation, serviceTR);
logger.debug("Start Task on service: TaskId " + trTask.getId());
SessionUtil.setCloneTask(session, trTask);
} catch (TDGWTSessionExpiredException e) {
throw e;
} catch (SecurityException e) {
@ -5086,6 +5057,114 @@ public class TDGWTServiceImpl extends RemoteServiceServlet implements
}
/**
*
* {@inheritDoc}
*/
@Override
public CloneMonitor getCloneMonitor()
throws TDGWTServiceException {
try {
HttpSession session = this.getThreadLocalRequest().getSession();
CloneTabularResourceSession cloneTabularResourceSession = SessionUtil
.getCloneTabularResourceSession(session);
Task task = SessionUtil.getCloneTask(session);
CloneMonitor cloneMonitor = new CloneMonitor();
if (task == null) {
logger.debug("Task null");
throw new TDGWTServiceException(
"Error in CloneMonitor task null");
} else {
TaskStatus status = task.getStatus();
if (status == null) {
logger.debug("Services TaskStatus : null");
throw new TDGWTServiceException(
"Error in CloneMonitor Status null");
} else {
logger.debug("Services TaskStatus: " + task.getStatus());
cloneMonitor.setStatus(TaskStateMap.map(task
.getStatus()));
// Table table;
TRId trId;
TabResource tabResource;
switch (cloneMonitor.getStatus()) {
case FAILED:
if (task.getResult() != null) {
logger.debug("Task exception: "
+ task.getErrorCause());
task.getErrorCause().printStackTrace();
cloneMonitor.setError(new Throwable(task
.getErrorCause()));
} else {
logger.debug("Task exception: Error In CloneMonitor");
cloneMonitor.setError(new Throwable(
"Error cloning tabular resource"));
}
cloneMonitor.setProgress(task.getProgress());
break;
case SUCCEDED:
logger.debug("Task Result:" + task.getResult());
cloneMonitor.setProgress(task.getProgress());
trId = new TRId();
trId.setId(cloneTabularResourceSession.getTrId().getId());
trId = retrieveTabularResourceBasicData(trId);
cloneMonitor.setTrId(trId);
tabResource = SessionUtil.getTabResource(session);
tabResource.setTrId(trId);
SessionUtil.setTabResource(session, tabResource);
SessionUtil.setTRId(session, trId);
break;
case IN_PROGRESS:
cloneMonitor.setProgress(task.getProgress());
break;
case VALIDATING_RULES:
cloneMonitor.setProgress(task.getProgress());
break;
case GENERATING_VIEW:
break;
case ABORTED:
break;
case STOPPED:
logger.debug("Task Result:" + task.getResult());
cloneMonitor.setProgress(task.getProgress());
trId = new TRId();
trId.setId(cloneTabularResourceSession.getTrId().getId());
trId = retrieveTabularResourceBasicData(trId);
cloneMonitor.setTrId(trId);
tabResource = SessionUtil.getTabResource(session);
tabResource.setTrId(trId);
SessionUtil.setTabResource(session, tabResource);
SessionUtil.setTRId(session, trId);
break;
case INITIALIZING:
break;
default:
break;
}
}
SessionUtil.setCloneTask(session, task);
}
logger.debug("CloneMonitor(): " + cloneMonitor);
return cloneMonitor;
} catch (TDGWTSessionExpiredException e) {
throw e;
} catch (Throwable e) {
e.printStackTrace();
throw new TDGWTServiceException(
"Error in monitor CloneMonitor: "
+ e.getLocalizedMessage());
}
}
/**
*
* {@inheritDoc}

View File

@ -0,0 +1,103 @@
/**
*
*/
package org.gcube.portlets.user.td.gwtservice.server;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author "Giancarlo Panichi" <a
* href="mailto:g.panichi@isti.cnr.it">g.panichi@isti.cnr.it</a>
*
*/
public class TDLogsServlet extends HttpServlet {
private static final long serialVersionUID = -737451890907300011L;
protected static Logger logger = LoggerFactory
.getLogger(TDLogsServlet.class);
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
handleRequest(req, resp);
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
handleRequest(req, resp);
}
protected void handleRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
logger.debug("TDLogsServlet");
long startTime = System.currentTimeMillis();
HttpSession session = request.getSession();
if (session == null) {
logger.error("Error getting the upload session, no session valid found: "
+ session);
response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT,
"ERROR-Error getting the user session, no session found"
+ session);
return;
}
logger.debug("TDLogsServlet import session id: " + session.getId());
ByteArrayInputStream in = null;
ByteArrayOutputStream out = null;
try {
response.setContentType("text/html; charset=utf-8");
String relativeWebPath = "/logs/TabularDataManagerLogBack.log";
out = new ByteArrayOutputStream();
String absoluteDiskPath = getServletContext().getRealPath(
relativeWebPath);
File file = new File(absoluteDiskPath);
in = new ByteArrayInputStream(FileUtils.readFileToByteArray(file));
IOUtils.copy(in, out);
response.getOutputStream().write(out.toByteArray());
response.setStatus(HttpServletResponse.SC_OK);
} catch (FileNotFoundException e) {
logger.error("File not found: " + e.getLocalizedMessage());
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
e.getLocalizedMessage());
} catch (IOException e) {
logger.error("IO error: " + e.getLocalizedMessage());
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
e.getLocalizedMessage());
} finally {
if (in != null) {
in.close();
}
if (out != null) {
out.close();
}
}
logger.debug("Response in " + (System.currentTimeMillis() - startTime));
}
}

View File

@ -0,0 +1,70 @@
package org.gcube.portlets.user.td.gwtservice.server.opexecution;
import java.util.HashMap;
import java.util.Map;
import org.gcube.data.analysis.tabulardata.commons.webservice.types.operations.OperationDefinition;
import org.gcube.data.analysis.tabulardata.commons.webservice.types.operations.OperationExecution;
import org.gcube.data.analysis.tabulardata.model.table.TableId;
import org.gcube.data.analysis.tabulardata.service.TabularDataService;
import org.gcube.portlets.user.td.gwtservice.server.trservice.OperationDefinitionMap;
import org.gcube.portlets.user.td.gwtservice.shared.Constants;
import org.gcube.portlets.user.td.gwtservice.shared.OperationsId;
import org.gcube.portlets.user.td.gwtservice.shared.exception.TDGWTServiceException;
import org.gcube.portlets.user.td.gwtservice.shared.tr.clone.CloneTabularResourceSession;
import org.gcube.portlets.user.td.widgetcommonevent.shared.TRId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Operation Execution for delete column
*
* @author "Giancarlo Panichi" email: <a
* href="mailto:g.panichi@isti.cnr.it">g.panichi@isti.cnr.it</a>
*
*/
public class OpExecution4Clone extends OpExecutionBuilder {
protected static Logger logger = LoggerFactory
.getLogger(OpExecution4Clone.class);
private TabularDataService service;
private CloneTabularResourceSession cloneTabularResourceSession;
public OpExecution4Clone(TabularDataService service,
CloneTabularResourceSession cloneTabularResourceSession) {
this.service = service;
this.cloneTabularResourceSession = cloneTabularResourceSession;
}
@Override
public void buildOpEx() throws TDGWTServiceException {
logger.debug(cloneTabularResourceSession.toString());
if(cloneTabularResourceSession.getTrId()==null){
logger.error("TRId is null");
new TDGWTServiceException("No valid tabular resource id present");
}
TRId trId=cloneTabularResourceSession.getTrId();
TableId tableId;
if(trId.isViewTable()){
tableId=new TableId(Long.valueOf(trId.getReferenceTargetTableId()));
} else {
tableId=new TableId(Long.valueOf(trId.getTableId()));
}
OperationDefinition operationDefinition;
operationDefinition = OperationDefinitionMap.map(
OperationsId.CLONE.toString(), service);
Map<String, Object> map = new HashMap<String, Object>();
map.put(Constants.PARAMETER_CLONE_TABLE, tableId);
OperationExecution invocation = new OperationExecution(
operationDefinition.getOperationId(), map);
operationExecutionSpec.setOp(invocation);
}
}

View File

@ -95,5 +95,6 @@ public class Constants {
public static final String PARAMETER_DENORMALIZATION_VALUE_COLUMN = "value_column";
public static final String PARAMETER_DENORMALIZATION_ATTRIBUTE_COLUMN = "attribute_column";
public static final String PARAMETER_CLONE_TABLE = "table";
}

View File

@ -67,6 +67,7 @@ package org.gcube.portlets.user.td.gwtservice.shared;
public enum OperationsId {
CSVImport("100"),
CSVExport("101"),
CLONE("102"),
SDMXCodelistImport("200"),
SDMXCodelistExport("201"),
SDMXDatasetImport("202"),

View File

@ -0,0 +1,14 @@
package org.gcube.portlets.user.td.gwtservice.shared.monitor;
import java.io.Serializable;
public class CloneMonitor extends OperationMonitor implements Serializable {
/**
*
*/
private static final long serialVersionUID = 8626668361737808483L;
}

View File

@ -5,64 +5,76 @@
<web-app>
<!-- Servlets -->
<!-- Servlets -->
<servlet>
<servlet-name>TDGWTService</servlet-name>
<servlet-class>org.gcube.portlets.user.td.gwtservice.server.TDGWTServiceImpl</servlet-class>
</servlet>
<servlet>
<servlet-name>CSVImportFileServlet</servlet-name>
<servlet-class>org.gcube.portlets.user.td.gwtservice.server.CSVImportFileServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>LocalUploadServlet</servlet-name>
<servlet-class>org.gcube.portlets.user.td.gwtservice.server.LocalUploadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>CodelistMappingUploadServlet</servlet-name>
<servlet-class>org.gcube.portlets.user.td.gwtservice.server.CodelistMappingUploadServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>TDLogsServlet</servlet-name>
<servlet-class>org.gcube.portlets.user.td.gwtservice.server.TDLogsServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>jUnitHostImpl</servlet-name>
<servlet-class>com.google.gwt.junit.server.JUnitHostImpl</servlet-class>
</servlet>
<!-- Servlets Mapping -->
<servlet-mapping>
<servlet-name>TDGWTService</servlet-name>
<url-pattern>/tdgwtservice/TDGWTService</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>CSVImportFileServlet</servlet-name>
<url-pattern>/tdgwtservice/CSVImportFileServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>LocalUploadServlet</servlet-name>
<url-pattern>/tdgwtservice/LocalUploadServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>CodelistMappingUploadServlet</servlet-name>
<url-pattern>/tdgwtservice/CodelistMappingUploadServlet</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>jUnitHostImpl</servlet-name>
<url-pattern>TDServiceManagerWidgets/junithost/*</url-pattern>
</servlet-mapping>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>TDGWTService.html</welcome-file>
</welcome-file-list>
<servlet-mapping>
<servlet-name>TDLogsServlet</servlet-name>
<url-pattern>/tdgwtservice/TDLogsServlet</url-pattern>
</servlet-mapping>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>TDGWTService.html</welcome-file>
</welcome-file-list>
</web-app>