package com.owera.xaps.tr069;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.owera.common.db.ConnectionMetaData;
import com.owera.common.db.ConnectionPoolData;
import com.owera.common.db.ConnectionProperties;
import com.owera.common.db.ConnectionProvider;
import com.owera.common.util.Sleep;
import com.owera.xaps.Properties.Module;
import com.owera.xaps.base.BaseCache;
import com.owera.xaps.base.Log;
import com.owera.xaps.base.db.DBAccess;
import com.owera.xaps.base.http.Authenticator;
import com.owera.xaps.base.http.ThreadCounter;
import com.owera.xaps.dbi.ScriptExecutions;
import com.owera.xaps.dbi.SyslogConstants;
import com.owera.xaps.dbi.Unit;
import com.owera.xaps.dbi.XAPS;
import com.owera.xaps.dbi.XAPSUnit;
import com.owera.xaps.tr069.background.BackgroundProcesses;
import com.owera.xaps.tr069.background.ScheduledKickTask;
import com.owera.xaps.tr069.exception.TR069Exception;
import com.owera.xaps.tr069.exception.TR069ExceptionShortMessage;
import com.owera.xaps.tr069.methods.DecisionMaker;
import com.owera.xaps.tr069.methods.HTTPRequestProcessor;
import com.owera.xaps.tr069.methods.HTTPResponseCreator;
import com.owera.xaps.tr069.methods.TR069Method;
import com.owera.xaps.tr069.test.system1.TestDatabase;
import com.owera.xaps.tr069.test.system1.TestDatabaseObject;
import com.owera.xaps.tr069.test.system2.Util;
/**
* This is the "main-class" of TR069 Provisioning. It receives the HTTP-request
* from the CPE and returns an HTTP-response. The content of the request/reponse
* can be both TR-069 request/response.
*
* @author morten
*
*/
public class Provisioning extends HttpServlet {
private static final long serialVersionUID = -3020450686422484143L;
public static final String VERSION = "3.1.2";
// private static BackgroundProcesses backgroundProcesses = new BackgroundProcesses();
private static ScriptExecutions executions;
/**
* Starts background processes, initializes logging system
*/
static {
DBAccess.init(Module.TR069, SyslogConstants.FACILITY_TR069, VERSION);
com.owera.common.log.Log.initialize("xaps-tr069-logs.properties");
Log.notice(Provisioning.class, "Server starts...");
try {
BackgroundProcesses.initiate(DBAccess.getDBI());
} catch (Throwable t) {
Log.fatal(Provisioning.class, "Couldn't start BackgroundProcesses correctly ", t);
}
try {
executions = new ScriptExecutions(DBAccess.getXAPSProperties());
} catch (Throwable t) {
Log.fatal(Provisioning.class, "Couldn't initialize ScriptExecutions - not possible to run SHELL-jobs", t);
}
}
/**
* doGet prints some information about the server, focus on database connections and memory usage
*/
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
if (req.getParameter("clearCache") != null)
BaseCache.clearCache();
PrintWriter pw = res.getWriter();
String html = "";
html += "<title>xAPS TR-069 Server Monitoring Page</title>";
html += "<h1>Monitoring of the TR-069 Server v. " + VERSION + "</h1>";
ConnectionProperties props = DBAccess.getXAPSProperties();
ConnectionPoolData poolData = ConnectionProvider.getConnectionPoolData(props);
if (poolData != null) {
html += "<h1>Database connection</h1>\n";
html += "This server is connected to " + poolData.getProps().getUrl() + " with user " + poolData.getProps().getUser() + "<br>\n";
ConnectionMetaData metaData = poolData.getMetaData().clone();
html += "<ul>Accessed : " + metaData.getAccessed() + "<br>\n";
html += "Retries : " + metaData.getRetries() + "<br>\n";
html += "Denied : " + metaData.getDenied() + "<br>\n";
html += "Denied % : " + metaData.calculateDeniedPercent() + "<br>\n";
html += "Free : " + poolData.getFreeConn().size() + "<br>\n";
html += "Currently used : " + poolData.getUsedConn().size() + "<br>\n";
html += "Used % : " + metaData.calculateUsedPercent() + "<br>\n<ul>";
int[] accessedSim = metaData.getAccessedSim();
for (int i = 1; i < accessedSim.length; i++) {
if (accessedSim[i] == 0 && accessedSim[i + 1] == 0 && accessedSim[i + 2] == 0)
break;
float percent = ((float) accessedSim[i] / metaData.getAccessed()) * 100f;
html += String.format("Used " + i + " connection(s) simultaneously: %8.5f", percent);
html += "% (accessed: " + accessedSim[i] + ")<br>\n";
}
html += "</ul>\n</ul><br>\n";
}
long total = Runtime.getRuntime().totalMemory();
long free = Runtime.getRuntime().freeMemory();
long used = total - free;
html += "<h1>Memory Usage</h1>\n";
html += "The JVM uses " + getBytesFormatted(used) + " of memory. " + getBytesFormatted(free) + " of memory available on the heap.<br>";
pw.print(html);
}
/**
* Reads the XML input into a string and store it in the SessionData object
* @param reqRes
* @return
* @throws TR069Exception
* @throws IOException
*/
private static long extractRequest(HTTPReqResData reqRes) throws TR069Exception {
try {
long tms = System.currentTimeMillis();
InputStreamReader isr = new InputStreamReader(reqRes.getReq().getInputStream());
BufferedReader br = new BufferedReader(isr);
StringBuilder requestSB = new StringBuilder(1000);
while (true) {
String line = br.readLine();
if (line == null)
break;
requestSB.append(line + "\n");
}
reqRes.getRequest().setXml(requestSB.toString());
return System.currentTimeMillis() - tms;
} catch (IOException e) {
throw new TR069Exception("TR-069 client aborted (not possible to read more input)", TR069ExceptionShortMessage.IOABORTED, e);
}
}
/**
* Some devices may send a CONTINUE header - server always reply "yes" - do continue
* @param req
* @param res
* @return
* @throws IOException
*/
@SuppressWarnings("unused")
private static boolean hasContinueHeader(HttpServletRequest req, HttpServletResponse res) throws IOException {
// Support 100 Continue header - always YES - CONTINUE!
if (req.getHeader("Expect") != null && req.getHeader("Expect").indexOf("100-continue") > -1) {
res.setStatus(HttpServletResponse.SC_CONTINUE);
res.getWriter().print("");
return true;
}
return false;
}
/**
* This is the entry point for TR-069 Clients - everything starts here!!!
*
* A TR-069 session consists of many rounds of HTTP request/responses, however
* each request/response non-the-less follows a standard pattern:
*
* 1. Check special HTTP headers for a "early return" (CONTINUE)
* 2. Check authentication - challenge client if necessary. If not authenticated - return
* 3. Check concurrent sessions from same unit - if detected: return
* 4. Extract XML from request - store in sessionData object
* 5. Process HTTP Request (xml-parsing, find methodname, test-verification)
* 6. Decide upon next step - may contain logic that processes the request and decide response
* 7. Produce HTTP Response (xml-creation)
* 8. Some details about the xml-response like content-type/Empty response
* 9. Return response to TR-069 client
*
* At the end we have error handling, to make sure that no matter what, we do return
* an EMTPY response to the client - to signal end of conversation/TR-069-session.
*
* In the finally loop we check if a TR-069 Session is in-fact completed (one way
* or the other) and if so, logging is performed. Also, if unit-parameters are queued
* up for writing, those will be written now (instead of writing some here and some there
* along the entire TR-069 session).
*
* In special cases the server will kick the device to "come back" and continue testing a new test case.
*
*/
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
// 1. If HTTP CONTINUE header present, return "yes" and return - should be correct behavior - the client will return
// if (hasContinueHeader(req, res))
// return;
HTTPReqResData reqRes = null;
try {
// Create the main object which contains all objects concerning the entire
// session. This object also contains the SessionData object
reqRes = new HTTPReqResData(req, res);
// 2. Authenticate the client (first issue challenge, then authenticate)
if (!Authenticator.authenticate(reqRes))
return;
// 3. Do not continue if concurrent sessions from the same unit is on going
if (reqRes.getSessionData() != null && !ThreadCounter.isRequestAllowed(reqRes.getSessionData()))
return;
// 4. Read the request from the client - store in reqRes object
extractRequest(reqRes);
// 5.Process request (parsing xml/data)
HTTPRequestProcessor.processRequest(reqRes);
// 6. Decide next step in TR-069 session (sometimes trivial, sometimes complex)
DecisionMaker.process(reqRes);
// 7. Create TR-069 response
HTTPResponseCreator.createResponse(reqRes);
// 8. Set correct headers in response
if (reqRes.getResponse().getXml() != null && reqRes.getResponse().getXml().length() > 0) {
res.setHeader("SOAPAction", "");
res.setContentType("text/xml");
}
// 8. No need to send Content-length as it will only be informational for 204 HTTP messages
if (reqRes.getResponse().getMethod().equals("Empty"))
res.setStatus(HttpServletResponse.SC_NO_CONTENT);
// 9. Print response to output
res.getWriter().print(reqRes.getResponse().getXml());
} catch (Throwable t) {
// Make sure we return an EMPTY response to the TR-069 client
if (t instanceof TR069Exception) {
TR069Exception tex = (TR069Exception) t;
Throwable stacktraceThrowable = t;
if (tex.getCause() != null)
stacktraceThrowable = tex.getCause();
if (tex.getShortMsg() == TR069ExceptionShortMessage.MISC || tex.getShortMsg() == TR069ExceptionShortMessage.DATABASE)
Log.error(Provisioning.class, "An error ocurred: " + t.getMessage(), stacktraceThrowable);
if (tex.getShortMsg() == TR069ExceptionShortMessage.IOABORTED)
Log.warn(Provisioning.class, t.getMessage());
else
Log.error(Provisioning.class, t.getMessage()); // No stacktrace printed to log
}
if (reqRes != null)
reqRes.setThrowable(t);
res.setStatus(HttpServletResponse.SC_NO_CONTENT);
res.getWriter().print("");
} finally {
// Run at end of every TR-069 session
if (reqRes != null && endOfSession(reqRes)) {
Log.debug(Provisioning.class, "End of session is reached, will write queued unit parameters if unit (" + reqRes.getSessionData().getUnit() + ") is not null");
// Logging of the entire session, both to tr069-event.log and syslog
if (reqRes.getSessionData().getUnit() != null) {
// reqRes.getSessionData().getUnit().toWriteQueue(SystemParameters.PROVISIONING_STATE, ProvisioningState.READY.toString());
writeQueuedUnitParameters(reqRes);
}
SessionLogging.log(reqRes);
if (Util.testEnabled(reqRes, true))
initiateNewTestSession(reqRes);
else if (reqRes.getSessionData().isTestMode()) {
String row = TestDatabase.database.select(reqRes.getSessionData().getUnitId());
if (row != null && new TestDatabaseObject(row).getRun().equals("true"))
initiateNewTestSession(reqRes);
}
BaseCache.removeSessionData(reqRes.getSessionData().getUnitId());
BaseCache.removeSessionData(reqRes.getSessionData().getId());
res.setHeader("Connection", "close");
}
}
if (reqRes != null && reqRes.getSessionData() != null)
ThreadCounter.responseDelivered(reqRes.getSessionData());
}
private static void initiateNewTestSession(HTTPReqResData reqRes) {
try {
List<HTTPReqResData> reqResList = reqRes.getSessionData().getReqResList();
boolean deviceHasAlreadyBooted = false;
for (HTTPReqResData rr : reqResList) {
String method = rr.getResponse().getMethod();
// No need to kick device if a reboot or reset has been part of the test-flow
if (method != null && (method.equals(TR069Method.FACTORY_RESET) || method.equals(TR069Method.REBOOT)))
deviceHasAlreadyBooted = true;
}
if (!deviceHasAlreadyBooted) {
ScheduledKickTask.addUnit(reqRes.getSessionData().getUnit());
}
} catch (Throwable t) {
Log.warn(Provisioning.class, "Could not initiate kick after completed session in test mode", t);
}
}
private static void writeQueuedUnitParameters(HTTPReqResData reqRes) {
try {
Unit unit = reqRes.getSessionData().getUnit();
if (unit != null) {
XAPS xaps = reqRes.getSessionData().getDbAccess().getXaps();
XAPSUnit xapsUnit = DBAccess.getXAPSUnit(xaps);
xapsUnit.addOrChangeQueuedUnitParameters(unit);
}
} catch (Throwable t) {
Log.error(Provisioning.class, "An error occured when writing queued unit parameters to Fusion. May affect provisioning", t);
}
}
private static boolean endOfSession(HTTPReqResData reqRes) {
try {
SessionData sessionData = reqRes.getSessionData();
HTTPReqData reqData = reqRes.getRequest();
HTTPResData resData = reqRes.getResponse();
if (reqRes.getThrowable() != null)
return true;
if (reqData.getMethod() != null && resData != null && resData.getMethod().equals(TR069Method.EMPTY)) {
boolean terminationQuirk = Properties.isTerminationQuirk(sessionData);
if (terminationQuirk && reqData.getMethod().equals(TR069Method.EMPTY))
return true;
if (!terminationQuirk)
return true;
}
return false;
} catch (Throwable t) {
Log.warn(Provisioning.class, "An error occured when determining endOfSession. Does not affect provisioning", t);
return false;
}
}
private static String getBytesFormatted(long bytes) {
if (bytes > 1024 * 1024 * 1024)
return bytes / (1024 * 1024 * 1024) + " GB";
else if (bytes > 1024 * 1024)
return bytes / (1024 * 1024) + " MB";
else if (bytes > 1024)
return bytes / (1024) + " KB";
return bytes + " B";
}
public void destroy() {
Sleep.terminateApplication();
}
public static ScriptExecutions getExecutions() {
return executions;
}
}