Skip to content
Snippets Groups Projects
Commit 98cd4408 authored by gmantele's avatar gmantele
Browse files

[ALL] Update the dependencies in the ANT scripts, the README and remove the dependency to CATALINA.

parent d2ae1b2a
No related branches found
No related tags found
No related merge requests found
...@@ -33,14 +33,18 @@ Each library has its own package (`adql` for ADQL, `uws` for UWS and `tap` for T ...@@ -33,14 +33,18 @@ Each library has its own package (`adql` for ADQL, `uws` for UWS and `tap` for T
### Dependencies ### Dependencies
Below are summed up the dependencies of each library: Below are summed up the dependencies of each library:
* ADQL: `adql`, `cds.utils` * ADQL: `adql`, `cds.utils`, `org.postgresql` *(for adql.translator.PgSphereTranslator only)*
* UWS: `uws`, `org.json` * UWS: `uws`, `org.json`, HTTP Multipart lib. (`com.oreilly.servlet`)
* TAP: `adql`, `uws`, `cds.*`, `org.json` * TAP: `adql`, `uws`, `cds.*`, `org.json`, `org.postgresql` *(for adql.translator.PgSphereTranslator only)*, HTTP Multipart lib. (`com.oreilly.servlet`), STIL (`nom.tap`, `org.apache.tools.bzip2`, `uk.ac.starlink`)
In the `lib` directory, you will find 2 JAR files:
* `cos-1.5beta.jar` to deal with HTTP multipart requests
* `stil3.0-5.jar` for [STIL](http://www.star.bris.ac.uk/~mbt/stil/) (VOTable and other formats support)
### ANT scripts ### ANT scripts
At the root of the repository, there are 3 ANT scripts. Each is dedicated to one library. They are able to generate JAR for sources, binaries and Javadoc. At the root of the repository, there are 3 ANT scripts. Each is dedicated to one library. They are able to generate JAR for sources, binaries and Javadoc.
3 properties must be set before using one of these scripts: 3 properties must be set before using one of these scripts:
* `CATALINA`: a path toward a JAR or a binary directory containing org.apache.catalina.connector.ClientAbortException.class * `POSTGRES`: a path toward a JAR or a binary directory containing all org.postgresql.* - [https://jdbc.postgresql.org/download.html](JDBC Postgres driver) - **(ONLY for ADQL and TAP if you want to keep adql.translator.PgSphereTranslator)**
* `SERVLET-API`: a path toward a JAR or a binary directory containing all javax.servlet.* * `SERVLET-API`: a path toward a JAR or a binary directory containing all javax.servlet.*
* (`JUNIT-API` *not required before the version 2.0 of the tap library*: a path toward one or several JARs or binary directories containing all classes to use JUnit.) * (`JUNIT-API` *not required before the version 2.0 of the tap library OR if you are not interested by the `test` directory (JUnit tests)*: a path toward one or several JARs or binary directories containing all classes to use JUnit.)
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<property name="adqlPath" value="adql/**" /> <property name="adqlPath" value="adql/**" />
<property name="utilsPath" value="cds/utils/**" /> <property name="utilsPath" value="cds/utils/**" />
<property name="licensePath" value="COPYING.LESSER.txt" /> <property name="licensePath" value="COPYING.LESSER" />
<property name="includesList" value="${adqlPath},${utilsPath},${licensePath}" /> <property name="includesList" value="${adqlPath},${utilsPath},${licensePath}" />
<property name="jarDest" value="." /> <property name="jarDest" value="." />
...@@ -22,6 +22,14 @@ ...@@ -22,6 +22,14 @@
<property name="adqlParserLink" value="adqlParser.jar" /> <property name="adqlParserLink" value="adqlParser.jar" />
<fail message="The property POSTGRES must be set! It provides the path toward a directory or a JAR which contains all classes inside org.postgresql.">
<condition><not><isset property="POSTGRES"/></not></condition>
</fail>
<path id="adql.classpath">
<pathelement location="${POSTGRES}" />
</path>
<echo>ADQL LIBRARY VERSION = ${version}</echo> <echo>ADQL LIBRARY VERSION = ${version}</echo>
<!-- BUILD ALL TASK --> <!-- BUILD ALL TASK -->
...@@ -43,7 +51,9 @@ ...@@ -43,7 +51,9 @@
<target name="compileLib" depends="clean" description="Build all the classes of the ADQL library. This target is particularly usefull because it lets highlighting missing dependencies."> <target name="compileLib" depends="clean" description="Build all the classes of the ADQL library. This target is particularly usefull because it lets highlighting missing dependencies.">
<mkdir dir="${compileDir}" /> <mkdir dir="${compileDir}" />
<javac destdir="${compileDir}" srcdir="${srcDir}" includes="${includesList}" includeantruntime="false" /> <javac destdir="${compileDir}" srcdir="${srcDir}" includes="${includesList}" includeantruntime="false">
<classpath refid="adql.classpath" />
</javac>
</target> </target>
<target name="buildLib" depends="compileLib" description="After 'clean', build the library JAR (only classes) and the runnable ADQL parser."> <target name="buildLib" depends="compileLib" description="After 'clean', build the library JAR (only classes) and the runnable ADQL parser.">
...@@ -63,7 +73,9 @@ ...@@ -63,7 +73,9 @@
</target> </target>
<target name="buildLibAndSrc" depends="buildLib" description="After 'clean' and 'buildLib', build the sources JAR (only .java)."> <target name="buildLibAndSrc" depends="buildLib" description="After 'clean' and 'buildLib', build the sources JAR (only .java).">
<jar compress="false" baseDir="${srcDir}" destfile="${srcJarFile}" includes="${includesList}" /> <jar compress="false" destfile="${srcJarFile}">
<zipfileset dir="${srcDir}" includes="${includesList},${licensePath}" prefix="${srcDir}" />
</jar>
</target> </target>
<!-- JAVADOC --> <!-- JAVADOC -->
......
...@@ -2,31 +2,34 @@ ...@@ -2,31 +2,34 @@
<!DOCTYPE project> <!DOCTYPE project>
<project name="tap" basedir="." default="buildLib"> <project name="tap" basedir="." default="buildLib">
<property name="version" value="1.1b" /> <property name="version" value="2.0beta" />
<property name="srcDir" value="src" /> <property name="srcDir" value="src" />
<property name="libDir" value="lib" /> <property name="libDir" value="lib" />
<property name="compileDir" value="antBuild" /> <property name="compileDir" value="antBuild" />
<property name="classesDir" value="${compileDir}"/> <property name="classesDir" value="${compileDir}"/>
<property name="javadocDir" value="javadoc/tap" /> <property name="javadocDir" value="javadoc/tap" />
<property name="cosJar" value="${libDir}/cos-1.5beta.jar" /> <property name="cosJarName" value="cos-1.5beta.jar" />
<property name="stilJar" value="${libDir}/stil3.0-5.jar" /> <property name="cosJar" value="${libDir}/${cosJarName}" />
<property name="stilJarName" value="stil3.0-5.jar" />
<property name="stilJar" value="${libDir}/${stilJarName}" />
<property name="tapPath" value="tap/**,adql/**,uws/**,cds/**" /> <property name="tapPath" value="tap/**,adql/**,uws/**,cds/**" />
<property name="jsonPath" value="org/json/**" /> <property name="jsonPath" value="org/json/**" />
<property name="extLibsPath" value="com/oreilly/servlet/**,nom/tam/**,org/apache/tools/bzip2/**,uk/ac/starlink/**" /> <property name="extLibsPath" value="com/oreilly/servlet/**,nom/tam/**,org/apache/tools/bzip2/**,uk/ac/starlink/**" />
<property name="licensePath" value="COPYING.LESSER.txt" /> <property name="licensePath" value="COPYING.LESSER" />
<property name="includesList" value="${tapPath},${jsonPath},${extLibsPath},${licensePath}" /> <property name="includesList" value="${tapPath},${jsonPath},${extLibsPath}" />
<property name="jarDest" value="." /> <property name="jarDest" value="." />
<property name="libJarFile" value="${jarDest}/tap_${version}.jar" /> <property name="libJarFile" value="${jarDest}/tap_${version}.jar" />
<property name="srcJarFile" value="${jarDest}/tap_src_${version}.jar" /> <property name="srcJarFile" value="${jarDest}/tap_src_${version}.jar" />
<property name="javadocJarFile" value="${jarDest}/tap_javadoc_${version}.jar" /> <property name="javadocJarFile" value="${jarDest}/tap_javadoc_${version}.jar" />
<fail message="Missing property: CATALINA ! It provides the path toward a directory or a JAR which contains the following class: org.apache.catalina.connector.ClientAbortException."> <fail message="The property POSTGRES must be set! It provides the path toward a directory or a JAR which contains all classes inside org.postgresql.">
<condition><not><isset property="CATALINA"/></not></condition> <condition><not><isset property="POSTGRES"/></not></condition>
</fail> </fail>
<fail message="The property SERVLET-API must be set! It provides the path toward a directory or a JAR which contains all classes inside javax.servlet."> <fail message="The property SERVLET-API must be set! It provides the path toward a directory or a JAR which contains all classes inside javax.servlet.">
<condition><not><isset property="SERVLET-API"/></not></condition> <condition><not><isset property="SERVLET-API"/></not></condition>
</fail> </fail>
...@@ -34,7 +37,7 @@ ...@@ -34,7 +37,7 @@
<path id="tap.classpath"> <path id="tap.classpath">
<pathelement location="${cosJar}" /> <pathelement location="${cosJar}" />
<pathelement location="${stilJar}" /> <pathelement location="${stilJar}" />
<pathelement location="${CATALINA}" /> <pathelement location="${POSTGRES}" />
<pathelement location="${SERVLET-API}" /> <pathelement location="${SERVLET-API}" />
</path> </path>
...@@ -67,14 +70,16 @@ ...@@ -67,14 +70,16 @@
<jar basedir="${classesDir}" destfile="${libJarFile}" includes="${includesList}"> <jar basedir="${classesDir}" destfile="${libJarFile}" includes="${includesList}">
<zipfileset src="${cosJar}" excludes="META-INF/*" /> <zipfileset src="${cosJar}" excludes="META-INF/*" />
<zipfileset src="${stilJar}" excludes="META-INF/*" /> <zipfileset src="${stilJar}" excludes="META-INF/*" />
<zipfileset dir="src" includes="${licensePath}" />
</jar> </jar>
<delete dir="${compileDir}" failonerror="true" /> <delete dir="${compileDir}" failonerror="true" />
</target> </target>
<target name="buildLibAndSrc" depends="buildLib" description="After 'clean' and 'buildLib', build the sources JAR (only .java)."> <target name="buildLibAndSrc" depends="buildLib" description="After 'clean' and 'buildLib', build the sources JAR (only .java).">
<jar compress="false" baseDir="${srcDir}" destfile="${srcJarFile}" includes="${includesList}"> <jar compress="false" destfile="${srcJarFile}">
<fileset file="${cosJar}" /> <zipfileset dir="${srcDir}" includes="${includesList},${licensePath}" prefix="${srcDir}" />
<fileset file="${stilJar}" /> <zipfileset dir="${libDir}" includes="${cosJarName}" fullPath="${cosJar}" />
<zipfileset dir="${libDir}" includes="${stilJarName}" fullPath="${stilJar}" />
</jar> </jar>
</target> </target>
......
...@@ -2,32 +2,34 @@ ...@@ -2,32 +2,34 @@
<!DOCTYPE project> <!DOCTYPE project>
<project name="uws" basedir="." default="buildLib"> <project name="uws" basedir="." default="buildLib">
<property name="version" value="4.1b" /> <property name="version" value="4.1" />
<property name="srcDir" value="src" /> <property name="srcDir" value="src" />
<property name="libDir" value="lib" />
<property name="compileDir" value="antBuild" /> <property name="compileDir" value="antBuild" />
<property name="classesDir" value="${compileDir}"/> <property name="classesDir" value="${compileDir}"/>
<property name="javadocDir" value="javadoc/uws" /> <property name="javadocDir" value="javadoc/uws" />
<property name="cosJarName" value="cos-1.5beta.jar" />
<property name="cosJar" value="${libDir}/${cosJarName}" />
<property name="uwsPath" value="uws/**" /> <property name="uwsPath" value="uws/**" />
<property name="jsonPath" value="org/json/**" /> <property name="jsonPath" value="org/json/**" />
<property name="licensePath" value="COPYING.LESSER.txt" /> <property name="licensePath" value="COPYING.LESSER" />
<property name="includesList" value="${uwsPath},${jsonPath},${licensePath}" /> <property name="extLibsPath" value="com/oreilly/servlet/**" />
<property name="includesList" value="${uwsPath},${jsonPath},${extLibsPath}" />
<property name="jarDest" value="." /> <property name="jarDest" value="." />
<property name="libJarFile" value="${jarDest}/uws_${version}.jar" /> <property name="libJarFile" value="${jarDest}/uws_${version}.jar" />
<property name="srcJarFile" value="${jarDest}/uws_src_${version}.jar" /> <property name="srcJarFile" value="${jarDest}/uws_src_${version}.jar" />
<property name="javadocJarFile" value="${jarDest}/uws_javadoc_${version}.jar" /> <property name="javadocJarFile" value="${jarDest}/uws_javadoc_${version}.jar" />
<fail message="Missing property: CATALINA ! It provides the path toward a directory or a JAR which contains the following class: org.apache.catalina.connector.ClientAbortException.">
<condition><not><isset property="CATALINA"/></not></condition>
</fail>
<fail message="The property SERVLET-API must be set! It provides the path toward a directory or a JAR which contains all classes inside javax.servlet."> <fail message="The property SERVLET-API must be set! It provides the path toward a directory or a JAR which contains all classes inside javax.servlet.">
<condition><not><isset property="SERVLET-API"/></not></condition> <condition><not><isset property="SERVLET-API"/></not></condition>
</fail> </fail>
<path id="uws.classpath"> <path id="uws.classpath">
<pathelement location="${CATALINA}" /> <pathelement location="${cosJar}" />
<pathelement location="${SERVLET-API}" /> <pathelement location="${SERVLET-API}" />
</path> </path>
...@@ -57,12 +59,18 @@ ...@@ -57,12 +59,18 @@
<target name="buildLib" depends="compileLib" description="After 'clean', build the library JAR (only classes)."> <target name="buildLib" depends="compileLib" description="After 'clean', build the library JAR (only classes).">
<echo>Generate the library:</echo> <echo>Generate the library:</echo>
<jar basedir="${classesDir}" destfile="${libJarFile}" includes="${includesList}" /> <jar basedir="${classesDir}" destfile="${libJarFile}" includes="${includesList}">
<zipfileset src="${cosJar}" excludes="META-INF/*" />
<zipfileset dir="src" includes="${licensePath}" />
</jar>
<delete dir="${compileDir}" failonerror="true" /> <delete dir="${compileDir}" failonerror="true" />
</target> </target>
<target name="buildLibAndSrc" depends="buildLib" description="After 'clean' and 'buildLib', build the sources JAR (only .java)."> <target name="buildLibAndSrc" depends="buildLib" description="After 'clean' and 'buildLib', build the sources JAR (only .java).">
<jar compress="false" baseDir="${srcDir}" destfile="${srcJarFile}" includes="${includesList}" /> <jar compress="false" destfile="${srcJarFile}">
<zipfileset dir="${srcDir}" includes="${includesList},${licensePath}" prefix="${srcDir}" />
<zipfileset dir="${libDir}" includes="${cosJarName}" fullPath="${cosJar}" />
</jar>
</target> </target>
<!-- JAVADOC --> <!-- JAVADOC -->
......
...@@ -45,6 +45,7 @@ import tap.log.TAPLog; ...@@ -45,6 +45,7 @@ import tap.log.TAPLog;
import tap.metadata.TAPMetadata; import tap.metadata.TAPMetadata;
import uk.ac.starlink.votable.VOSerializer; import uk.ac.starlink.votable.VOSerializer;
import uws.UWSException; import uws.UWSException;
import uws.job.ErrorType;
import uws.job.user.JobOwner; import uws.job.user.JobOwner;
import uws.service.UWS; import uws.service.UWS;
import uws.service.UWSService; import uws.service.UWSService;
...@@ -59,7 +60,7 @@ import adql.db.FunctionDef; ...@@ -59,7 +60,7 @@ import adql.db.FunctionDef;
* <p>At its creation it is creating and configuring the other resources in function of the given description of the TAP service.</p> * <p>At its creation it is creating and configuring the other resources in function of the given description of the TAP service.</p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (12/2014) * @version 2.0 (01/2015)
*/ */
public class TAP implements VOSIResource { public class TAP implements VOSIResource {
...@@ -727,10 +728,20 @@ public class TAP implements VOSIResource { ...@@ -727,10 +728,20 @@ public class TAP implements VOSIResource {
getLogger().logHttp(LogLevel.INFO, response, reqID, owner, "HTTP " + UWSException.OK + " - Action \"" + resourceName + "\" successfully executed.", null); getLogger().logHttp(LogLevel.INFO, response, reqID, owner, "HTTP " + UWSException.OK + " - Action \"" + resourceName + "\" successfully executed.", null);
}catch(Throwable t){ }catch(Throwable t){
// Write the error in the response and return the appropriate HTTP status code: // CLIENT ABORTION: (note: should work with Apache/Tomcat and JBoss)
errorWriter.writeError(t, response, request, reqID, owner, resourceName); if (t.getClass().getName().endsWith("ClientAbortException")){
// Log the error: // Log the client abortion:
getLogger().logHttp(LogLevel.ERROR, response, reqID, owner, "HTTP " + response.getStatus() + " - Can not complete the execution of the TAP resource \"" + resourceName + "\"!", t); getLogger().logHttp(LogLevel.INFO, response, reqID, owner, "HTTP " + response.getStatus() + " - HTTP request aborted by the client => the TAP resource \"" + resourceName + "\" has stopped!", t);
// Notify the client abortion in a TAP error:
errorWriter.writeError("The client aborts this HTTP request! It may happen due to a client timeout or to an interruption of the response waiting process.", ErrorType.TRANSIENT, UWSException.ACCEPTED_BUT_NOT_COMPLETE, response, request, reqID, owner, resourceName);
}
// ANY OTHER ERROR:
else{
// Log the error:
getLogger().logHttp(LogLevel.ERROR, response, reqID, owner, "HTTP " + response.getStatus() + " - Can not complete the execution of the TAP resource \"" + resourceName + "\"!", t);
// Write the error in the response and return the appropriate HTTP status code:
errorWriter.writeError(t, response, request, reqID, owner, resourceName);
}
}finally{ }finally{
// Notify the queue of the asynchronous jobs that a new connection is available: // Notify the queue of the asynchronous jobs that a new connection is available:
if (resourceName.equalsIgnoreCase(Sync.RESOURCE_NAME) && service.getFactory().countFreeConnections() >= 1) if (resourceName.equalsIgnoreCase(Sync.RESOURCE_NAME) && service.getFactory().countFreeConnections() >= 1)
......
...@@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletResponse; ...@@ -35,6 +35,7 @@ import javax.servlet.http.HttpServletResponse;
import uws.AcceptHeader; import uws.AcceptHeader;
import uws.UWSException; import uws.UWSException;
import uws.UWSToolBox; import uws.UWSToolBox;
import uws.job.ErrorType;
import uws.job.ExecutionPhase; import uws.job.ExecutionPhase;
import uws.job.JobList; import uws.job.JobList;
import uws.job.JobThread; import uws.job.JobThread;
...@@ -185,7 +186,7 @@ import uws.service.request.RequestParser; ...@@ -185,7 +186,7 @@ import uws.service.request.RequestParser;
* *
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 4.1 (12/2014) * @version 4.1 (01/2015)
*/ */
public class UWSService implements UWS { public class UWSService implements UWS {
...@@ -1152,7 +1153,13 @@ public class UWSService implements UWS { ...@@ -1152,7 +1153,13 @@ public class UWSService implements UWS {
actionApplied = true; actionApplied = true;
sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response); sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
}catch(Exception ex){ }catch(Exception ex){
sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response); if (ex.getClass().getName().endsWith("ClientAbortException")){
// Log the client abortion:
logger.logHttp(LogLevel.INFO, request, reqID, "HTTP " + UWSException.ACCEPTED_BUT_NOT_COMPLETE + " - Action \"" + action.getName() + "\" aborted by the client! [Client abort => " + ex.getClass().getName() + "]", ex);
// Notify the client abortion in a TAP error:
errorWriter.writeError("The client aborts this HTTP request! It may happen due to a client timeout or to an interruption of the response waiting process.", ErrorType.TRANSIENT, UWSException.ACCEPTED_BUT_NOT_COMPLETE, response, request, reqID, user, action.getName());
}else
sendError(ex, request, reqID, user, ((action != null) ? action.getName() : null), response);
}finally{ }finally{
executedAction = action; executedAction = action;
// Free resources about uploaded files ; only unused files will be deleted: // Free resources about uploaded files ; only unused files will be deleted:
......
...@@ -39,13 +39,12 @@ import javax.servlet.http.HttpServlet; ...@@ -39,13 +39,12 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.connector.ClientAbortException;
import uws.AcceptHeader; import uws.AcceptHeader;
import uws.UWSException; import uws.UWSException;
import uws.UWSExceptionFactory; import uws.UWSExceptionFactory;
import uws.UWSToolBox; import uws.UWSToolBox;
import uws.job.ErrorSummary; import uws.job.ErrorSummary;
import uws.job.ErrorType;
import uws.job.JobList; import uws.job.JobList;
import uws.job.JobThread; import uws.job.JobThread;
import uws.job.Result; import uws.job.Result;
...@@ -133,7 +132,7 @@ import uws.service.request.UploadFile; ...@@ -133,7 +132,7 @@ import uws.service.request.UploadFile;
* </p> * </p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 4.1 (12/2014) * @version 4.1 (01/2015)
*/ */
public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory { public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
...@@ -437,10 +436,14 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory ...@@ -437,10 +436,14 @@ public abstract class UWSServlet extends HttpServlet implements UWS, UWSFactory
}catch(UWSException ue){ }catch(UWSException ue){
sendError(ue, req, reqID, user, uwsAction, resp); sendError(ue, req, reqID, user, uwsAction, resp);
}catch(ClientAbortException cae){
logger.logHttp(LogLevel.INFO, req, reqID, "HTTP " + UWSException.OK + " - Action \"" + uwsAction + "\" aborted by the client! [Client abort => ClientAbortException]", cae);
}catch(Throwable t){ }catch(Throwable t){
sendError(t, req, reqID, user, uwsAction, resp); if (t.getClass().getName().endsWith("ClientAbortException")){
// Log the client abortion:
logger.logHttp(LogLevel.INFO, req, reqID, "HTTP " + UWSException.ACCEPTED_BUT_NOT_COMPLETE + " - Action \"" + uwsAction + "\" aborted by the client! [Client abort => " + t.getClass().getName() + "]", t);
// Notify the client abortion in a TAP error:
errorWriter.writeError("The client aborts this HTTP request! It may happen due to a client timeout or to an interruption of the response waiting process.", ErrorType.TRANSIENT, UWSException.ACCEPTED_BUT_NOT_COMPLETE, resp, req, reqID, user, uwsAction);
}else
sendError(t, req, reqID, user, uwsAction, resp);
}finally{ }finally{
// Free resources about uploaded files ; only unused files will be deleted: // Free resources about uploaded files ; only unused files will be deleted:
UWSToolBox.deleteUploads(req); UWSToolBox.deleteUploads(req);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment