/*
* � Copyright IBM Corp. 2011, 2016
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.ibm.domino.das.servlet;
import static com.ibm.domino.das.service.RestService.URL_PARAM_OWNER;
import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_STREAMING;
import static com.ibm.domino.services.rest.RestParameterConstants.PARAM_VALUE_TRUE;
import static com.ibm.domino.services.rest.RestServiceConstants.ATTR_CODE;
import static com.ibm.domino.services.rest.RestServiceConstants.ATTR_TEXT;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import lotus.domino.Session;
import org.apache.wink.server.utils.RegistrationUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import com.ibm.commons.log.Log;
import com.ibm.commons.log.LogMgr;
import com.ibm.commons.util.StringUtil;
import com.ibm.commons.util.io.json.JsonException;
import com.ibm.commons.util.io.json.JsonGenerator.Generator;
import com.ibm.commons.util.io.json.JsonGenerator.StringBuilderGenerator;
import com.ibm.commons.util.io.json.JsonJavaFactory;
import com.ibm.domino.commons.RequestContext;
import com.ibm.domino.commons.model.Customer;
import com.ibm.domino.commons.model.ICustomerProvider;
import com.ibm.domino.commons.model.IGatekeeperProvider;
import com.ibm.domino.commons.model.IStatisticsProvider;
import com.ibm.domino.commons.model.ProviderFactory;
import com.ibm.domino.das.service.CoreService;
import com.ibm.domino.das.service.DataService;
import com.ibm.domino.das.service.IRestServiceExt;
import com.ibm.domino.das.service.RestService;
import com.ibm.domino.das.servlet.DasStats.MutableDouble;
import com.ibm.domino.das.servlet.DasStats.MutableInteger;
import com.ibm.domino.das.utils.ErrorHelper;
import com.ibm.domino.das.utils.ScnContext;
import com.ibm.domino.das.utils.StatsContext;
import com.ibm.domino.osgi.core.context.ContextInfo;
import com.ibm.domino.services.AbstractRestServlet;
import com.ibm.domino.services.util.JsonWriter;
import com.ibm.xsp.acl.NoAccessSignal;
// Servlet created within the correct class loader
// This is a workaround to set the correct context class loader
@SuppressWarnings("serial") // $NON-NLS-1$
public class DasServlet extends AbstractRestServlet {
public static final LogMgr DAS_LOGGER = Log.load("com.ibm.domino.das"); //$NON-NLS-1$
/**
* The variable name [RestWebServices] that could be set in either Internet Site document for a domain,
* or a Server document for a specific server
* This variable will return a list of services that are enabled or "" if no rest services are enabled.
*
*/
private static final String CONFIG_RESTWEBSERVICES = "RestWebServices"; //$NON-NLS-1$
private static final String DATA_SERVICE_NAME = "Data";//$NON-NLS-1$
private static final String DATA_SERVICE_PATH = "data";//$NON-NLS-1$
private static final String VERSION_ZERO = "0.0.0"; //$NON-NLS-1$
private static final String DATA_SERVICE_VERSION = "9.0.1"; //$NON-NLS-1$
private static final String CORE_SERVICE_NAME = "Core";//$NON-NLS-1$
private static final String CORE_SERVICE_PATH = "core";//$NON-NLS-1$
private static final String CORE_SERVICE_VERSION = "9.0.1"; //$NON-NLS-1$
private static final int CORE_SERVICE_GKF = 427; // Defined by core SAAS code
private static final long STATS_TIMER_INTERVAL = 30000;
private static final String STATS_FACILITY = "API"; //$NON-NLS-1$
private static final String STATS_REQUESTS = "Requests"; //$NON-NLS-1$
private static final String STATS_TIME = "RequestTime"; //$NON-NLS-1$
private static final String STATS_AVG_TIME = "AvgRequestTime"; //$NON-NLS-1$
private static final String STATS_TOTAL_REQUESTS = "Total." + STATS_REQUESTS; //$NON-NLS-1$
private static final String STATS_TOTAL_TIME = "Total." + STATS_TIME; //$NON-NLS-1$
private static final String STATS_TOTAL_AVG_TIME = "Total." + STATS_AVG_TIME; //$NON-NLS-1$
private static Boolean s_initialized = new Boolean(false);
private static Timer s_statsTimer = null;
private static TimerTask s_statsTimerTask = new TimerTask() {
@Override
public void run() {
// Write all stats to Domino. We do this on a timer
// to avoid unneccessary churn.
IStatisticsProvider provider = ProviderFactory.getStatisticsProvider();
if ( provider != null ) {
Set<Map.Entry<String, Object>> set = DasStats.get().getEntries();
for ( Map.Entry<String, Object> entry : set ) {
Object value = entry.getValue();
if ( value instanceof MutableInteger ) {
int iValue = ((MutableInteger)value).getValue();
provider.UpdateInt(STATS_FACILITY, entry.getKey(), false, iValue);
}
else if ( value instanceof MutableDouble ) {
double dValue = ((MutableDouble)value).getValue();
provider.UpdateNumber(STATS_FACILITY, entry.getKey(), dValue);
}
}
}
}
};
private static Map<String, DasService> s_services = new HashMap<String, DasService>();
private static String s_enabledServices = null;
private static Pattern s_acceptLanguagePattern = Pattern.compile("(\\w{1,8})(?:\\-(\\w{1,8}))?(?:\\;q=(\\d(?:\\.\\d)?))?"); // $NON-NLS-1$
/**
* Private class used to keep track of what services are enabled.
*/
private class DasService {
private String _name;
private String _path;
private boolean _enabled = false;
private String _version;
private int _gkf; // Gatekeeper feature #
private boolean _allowSstUsers;
private Application _application;
private boolean _initialized= false;
public DasService(String name, String path, String version, int gkf,
boolean allowSstUsers, Application application) {
_name = name;
_path = path;
_version = version;
_gkf = gkf;
_allowSstUsers = allowSstUsers;
_application = application;
}
public boolean isEnabled() {
return _enabled;
}
public void setEnabled(boolean enabled) {
_enabled = enabled;
}
public String getName() {
return _name;
}
public String getPath() {
return _path;
}
public String getVersion() {
return _version;
}
public Application getApplication() {
return _application;
}
public void setInitialized(boolean initialized) {
_initialized = initialized;
}
public boolean isInitialized() {
return _initialized;
}
public int getGkf() {
return _gkf;
}
public boolean isAllowSstUsers() {
return _allowSstUsers;
}
}
public void doInit() throws ServletException {
synchronized(s_initialized) {
if ( !s_initialized ) {
super.doInit();
// Initialize the core service
initCoreService();
// Initialize the data service
initDataService();
// Initialize resources from other plugins
initDynamicResources();
// Schedule the timer task to run every 30 seconds
IStatisticsProvider provider = ProviderFactory.getStatisticsProvider();
if ( provider != null ) {
s_statsTimer = new Timer(true);
s_statsTimer.schedule(s_statsTimerTask, STATS_TIMER_INTERVAL, STATS_TIMER_INTERVAL);
}
s_initialized = true;
DAS_LOGGER.getLogger().fine("DasServlet initialized."); // $NON-NLS-1$
}
}
}
public void doDestroy() {
if ( s_initialized ) {
Iterator<String> iterator = s_services.keySet().iterator();
while(iterator.hasNext()) {
String key = iterator.next();
DasService service = s_services.get(key);
Application application = service.getApplication();
if ( application instanceof RestService ) {
try {
((RestService)application).destroy();
}
catch(Throwable e) {
DAS_LOGGER.warn(e, e.getMessage());
}
}
}
// Clean up Domino stats
IStatisticsProvider provider = ProviderFactory.getStatisticsProvider();
if ( provider != null ) {
Set<Map.Entry<String, Object>> set = DasStats.get().getEntries();
for ( Map.Entry<String, Object> entry : set ) {
provider.Delete(STATS_FACILITY, entry.getKey());
}
}
}
super.doDestroy();
}
/**
* Override the service to do the access control check before processing the REST request
*/
@Override
public void doService(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
StatsContext.init();
try {
// CSRF protection
if (!verifyDasTokens(request)){
handleError(response, Response.Status.FORBIDDEN, null);
return;
}
// Set the SCN context
setScnContext(request);
// Get an instance of DasService for this request. The
// instance may be null.
DasService service = getService(request);
Application app = null;
if ( service != null ) {
app = service.getApplication();
}
// Make sure the service is enabled for this server or internet site
if ( ! serviceEnabled(service) ) {
handleError(response, Response.Status.FORBIDDEN, null);
return;
}
// Set the request context
setRequestContext(request);
//Wrap the http request for X-HTTP-Method-Override header manipulation
DasHttpRequestWrapper requestWrapper = new DasHttpRequestWrapper(request);
//Wrap the http response for Gzip/Deflate the output stream
DasHttpResponseWrapper responseWrapper = new DasHttpResponseWrapper(request, response);
// Enable streaming.
String param = requestWrapper.getParameter(PARAM_STREAMING);
if (param != null) {
boolean preventCache = param.contentEquals(PARAM_VALUE_TRUE);
responseWrapper.setPreventCache(preventCache);
}
try {
if (app instanceof IRestServiceExt) {
if (((IRestServiceExt) app).beforeDoService(request)) {
super.doService(requestWrapper, responseWrapper);
((IRestServiceExt) app).afterDoService(request);
}
else {
handleError(responseWrapper, Response.Status.FORBIDDEN, null);
}
}
else {
super.doService(requestWrapper, responseWrapper);
}
}
catch (ServletException e) {
Throwable cause = e.getCause();
if ( cause instanceof NoAccessSignal ) {
throw (NoAccessSignal)cause;
}
else {
handleUnknownException(app, request, responseWrapper, e);
}
}
catch (Throwable e) {
// Avoid throwing unknown exceptions to the container
handleUnknownException(app, request, responseWrapper, e);
}
}
finally {
DasStats stats = DasStats.get();
Date now = new Date();
long elapsed = now.getTime() - StatsContext.getCurrentInstance().getStartTime().getTime();
int requests = stats.addInteger(STATS_TOTAL_REQUESTS, 1);
double requestTime = stats.addNumber(STATS_TOTAL_TIME, elapsed);
// The average time is an approximation because we are not synchronizing threads.
// When multiple threads update the same stat at the same time, the calculation could
// be off, but it's not worth keeping a thread waiting for better precision.
if ( requests != 0 ) {
stats.setInteger(STATS_TOTAL_AVG_TIME, (int)(requestTime/requests));
}
String serviceName = StatsContext.getCurrentInstance().getServiceName();
if ( StringUtil.isNotEmpty(serviceName) ) {
requests = stats.addInteger(serviceName + "." + STATS_TOTAL_REQUESTS, 1);
requestTime = stats.addNumber(serviceName + "." + STATS_TOTAL_TIME, elapsed);
if ( requests != 0 ) {
stats.setInteger(serviceName + "." + STATS_TOTAL_AVG_TIME, (int)(requestTime/requests));
}
String category = StatsContext.getCurrentInstance().getRequestCategory();
if ( StringUtil.isNotEmpty(category) ) {
requests = stats.addInteger(serviceName + "." + category + "." + STATS_REQUESTS, 1);
requestTime = stats.addNumber(serviceName + "." + category + "." + STATS_TIME, elapsed);
if ( requests != 0 ) {
stats.setInteger(serviceName + "." + category + "." + STATS_AVG_TIME, (int)(requestTime/requests));
}
}
}
}
}
/**
* @param request
* @return
*/
private boolean verifyDasTokens(HttpServletRequest request) {
String CSRFCookieName = "DasToken"; // $NON-NLS-1$
String CSRFHeaderName = "X-DAS-Token"; // $NON-NLS-1$
boolean verified = true;
Cookie[] cookies = request.getCookies();
if (cookies == null)
return verified;
for (Cookie cookie : cookies)
{
String name = cookie.getName();
if (name.equals(CSRFCookieName)){
String csrfHeader = request.getHeader(CSRFHeaderName);
if( csrfHeader == null || !cookie.getValue().equals(csrfHeader)){
verified = false;
}
break;
}
}
return verified;
}
public static String getServicesResponse(String baseUrl) throws IOException, JsonException {
refreshServiceMap();
StringBuilder sb = new StringBuilder();
Generator generator = new StringBuilderGenerator(JsonJavaFactory.instanceEx, sb, false);
generator.out("{");
generator.nl();
generator.incIndent();
generator.indent();
generator.outPropertyName("services"); // $NON-NLS-1$
generator.out(":[");
generator.nl();
generator.incIndent();
Iterator<String> iterator = s_services.keySet().iterator();
while(iterator.hasNext()) {
String key = iterator.next();
DasService service = s_services.get(key);
generator.indent();
generator.out("{");
generator.nl();
generator.incIndent();
generator.indent();
generator.outPropertyName("name"); // $NON-NLS-1$
generator.out(":");
generator.outLiteral(service.getName());
generator.out(",");
generator.nl();
generator.indent();
generator.outPropertyName("enabled"); // $NON-NLS-1$
generator.out(":");
generator.outBooleanLiteral(service.isEnabled());
generator.out(",");
generator.nl();
generator.indent();
generator.outPropertyName("version"); // $NON-NLS-1$
generator.out(":");
generator.outLiteral(service.getVersion());
generator.out(",");
generator.nl();
generator.indent();
generator.outPropertyName("href"); // $NON-NLS-1$
generator.out(":");
generator.outLiteral(baseUrl + service.getPath());
generator.nl();
generator.decIndent();
generator.indent();
generator.out("}");
if ( iterator.hasNext() ) {
generator.out(",");
}
generator.nl();
}
generator.decIndent();
generator.indent();
generator.out("]");
generator.decIndent();
generator.nl();
generator.indent();
generator.out("}");
return sb.toString();
}
private void initCoreService() {
try {
Application application = new CoreService();
// Add the service to our map
DasService service = new DasService(CORE_SERVICE_NAME, CORE_SERVICE_PATH,
CORE_SERVICE_VERSION, CORE_SERVICE_GKF,
false, application);
s_services.put(CORE_SERVICE_PATH, service);
DAS_LOGGER.getLogger().fine("Registered the core DAS service"); // $NON-NLS-1$
}
catch (Throwable e) {
DAS_LOGGER.warn(e, e.getMessage());
}
}
private void initDataService() {
try {
Application application = new DataService();
// Add the service to our map
DasService service = new DasService(DATA_SERVICE_NAME, DATA_SERVICE_PATH,
DATA_SERVICE_VERSION, 0, false, application);
s_services.put(DATA_SERVICE_PATH, service);
DAS_LOGGER.getLogger().fine("Registered the data service"); // $NON-NLS-1$
}
catch (Throwable e) {
DAS_LOGGER.warn(e, e.getMessage());
}
}
/**
* Initialize dynamic resources from other plugins.
*/
private void initDynamicResources() {
// Get a list of all registered extensions
IExtension extensions[] = null;
final IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
if (extensionRegistry != null) {
final IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint("com.ibm.domino.das.service"); // $NON-NLS-1$
if (extensionPoint != null) {
extensions = extensionPoint.getExtensions();
}
}
if (extensions == null) {
return;
}
// Walk through each extension in the list
for (final IExtension extension : extensions) {
final IConfigurationElement configElements[] = extension.getConfigurationElements();
if (configElements == null) {
continue;
}
for (final IConfigurationElement configElement : configElements) {
try {
// We only handle serviceResources elements for now
DAS_LOGGER.getLogger().fine("Config element: " + configElement.getName()); // $NON-NLS-1$
if ( !("serviceResources".equalsIgnoreCase(configElement.getName())) ) { // $NON-NLS-1$
continue;
}
String serviceName = configElement.getAttribute("name"); // $NON-NLS-1$
String servicePath = configElement.getAttribute("path"); // $NON-NLS-1$
String serviceVersion = configElement.getAttribute("version"); // $NON-NLS-1$
String serviceGkf = configElement.getAttribute("gkf"); // $NON-NLS-1$
if ( serviceName == null || servicePath == null ) {
DAS_LOGGER.getLogger().warning(StringUtil.format("DAS service {0} ignored. Both name and path must be defined.", serviceName)); // $NLW-DasServlet.DASservice0ignoredBothnameandpath-1$
continue;
}
// Parse the gatekeeper feature #
int iGkf = 0;
if ( StringUtil.isNotEmpty(serviceGkf)) {
try {
iGkf = Integer.parseInt(serviceGkf);
}
catch (Throwable e) {
// Ignore parser exception
}
}
// Parse the SST option
String serviceAllowSstUsers = configElement.getAttribute("allowSstUsers"); // $NON-NLS-1$
boolean allowSstUsers = "true".equalsIgnoreCase(serviceAllowSstUsers); // $NON-NLS-1$
DAS_LOGGER.getLogger().fine(StringUtil.format("Found a DAS service extension: {0} (/{1})", serviceName, servicePath)); // $NON-NLS-1$
final Object object = configElement.createExecutableExtension("class"); // $NON-NLS-1$
if ( ! (object instanceof Application) ) {
// Class was constructed but it is the wrong type
DAS_LOGGER.getLogger().warning(StringUtil.format("DAS service {0} ignored. Class is the wrong type.", serviceName)); // $NLW-DasServlet.DASservice0ignoredClassisthewrong-1$
continue;
}
// This is a critical section. Things can go wrong inside registerApplication
// (e.g. NoClassDefFound). So catch all exceptions and log them, but continue
// to the next service.
try {
Application application = (Application) object;
// Add the service to our map
DasService service = new DasService(serviceName, servicePath,
(serviceVersion != null) ? serviceVersion : VERSION_ZERO,
iGkf, allowSstUsers, application);
s_services.put(servicePath.toLowerCase(), service);
DAS_LOGGER.getLogger().fine("Registered a DAS service extension"); // $NON-NLS-1$
}
catch (Throwable e) {
DAS_LOGGER.warn(e, e.getMessage());
}
} catch (final CoreException e) {
DAS_LOGGER.error(e, e.getMessage());
}
}
}
}
/**
* Tests whether the service corresponding to this request is enabled.
*
* @param request
* @return
*/
private boolean serviceEnabled(DasService service) {
if ( !isDominoServer() ) {
// We only handle requests on the domino server
return false;
}
if ( service == null ) {
// We don't have an instance of DasService. Let
// Wink handle the request.
return true;
}
// Initialize stats context for the request
StatsContext.getCurrentInstance().setServiceName(service.getName());
// Make sure service is enabled on this server
boolean enabled = service.isEnabled();
if ( !enabled ) {
if ( DAS_LOGGER.getLogger().isLoggable(Level.FINE)) {
DAS_LOGGER.getLogger().fine(StringUtil.format(
"The {0} service is disabled on this server.", // $NON-NLS-1$
service.getName()));
}
}
// Do some SAAS-only checks
if ( enabled && ScnContext.getCurrentInstance().isScn() ) {
String customerId = ScnContext.getCurrentInstance().getCustomerId();
// Service is enabled on this server. Now check the
// gatekeeper feature. When running in SAAS, this makes sure
// the service is enabled for the customer.
if ( service.getGkf() != 0 ) {
String userId = ScnContext.getCurrentInstance().getUserId();
IGatekeeperProvider provider = ProviderFactory.getGatekeeperProvider();
enabled = provider.isFeatureEnabled(service.getGkf(),
customerId, userId);
if ( !enabled && DAS_LOGGER.getLogger().isLoggable(Level.FINE)) {
DAS_LOGGER.getLogger().fine(StringUtil.format(
"The {0} service is disabled by the gatekeeper for customer {1}.", // $NON-NLS-1$
service.getName(),
customerId));
}
}
// Even if the service is enabled thru gatekeeper, prevent
// self service trial customers from making API requests.
if ( enabled && StringUtil.isNotEmpty(customerId) && !service.isAllowSstUsers()) {
try {
ICustomerProvider provider = ProviderFactory.getCustomerProvider();
if ( provider != null ) {
Customer customer = provider.getCustomer(customerId);
if ( customer.isSelfTrial() ) {
enabled = false;
}
}
}
catch (Throwable e) {
// Do nothing. Assume the request is NOT for a self service trial customer.
}
}
}
// Just in time initialization
if (enabled && !service.isInitialized()) {
synchronized(service) {
try {
RegistrationUtils.registerApplication(service.getApplication(), getServletContext());
service.setInitialized(true);
}
catch (Throwable e) {
enabled = false;
service.setEnabled(false);
DAS_LOGGER.warn(e, StringUtil.format(
"Automatically disabling {0} service because there was an error registering its resources.", // $NLW-DasServlet.Automaticallydisablingaservicebec-1$
service.getName()));
}
}
}
return enabled;
}
/**
* Gets an instance of <code>DasService</code> from a request
*
* @param request
* @return
*/
private DasService getService(HttpServletRequest request) {
String requestPath = null;
DasService service = null;
if (request.getPathInfo() != null) {
StringTokenizer tokenizer = new StringTokenizer(request.getPathInfo(), "/");
try {
requestPath = tokenizer.nextToken();
if (requestPath != null) {
requestPath.toLowerCase();
}
}
catch (NoSuchElementException e) {
// Ignore this
}
}
if (requestPath != null) {
refreshServiceMap();
service = s_services.get(requestPath);
}
return service;
}
/**
* Refreshes the service map.
*
* <p>This method enables all the services listed in the Internet Site document
* or server document.
*/
private static void refreshServiceMap() {
String enabledServices = ContextInfo.getServerVariable(CONFIG_RESTWEBSERVICES);
if ( enabledServices.equals(s_enabledServices) ) {
// No change in the list of services. We're done.
return;
}
synchronized(s_services) {
// Disable all services except the core service
Iterator<String> iterator = s_services.keySet().iterator();
while (iterator.hasNext()) {
DasService service = s_services.get(iterator.next());
if ( CORE_SERVICE_NAME.equals(service.getName()) ) {
service.setEnabled(true);
}
else {
service.setEnabled(false);
}
}
// Enable just the services in the list
StringTokenizer tokenizer = new StringTokenizer(enabledServices, ", ");
while ( tokenizer.hasMoreTokens() ) {
String serviceName = tokenizer.nextToken();
// Enable the service
iterator = s_services.keySet().iterator();
while (iterator.hasNext()) {
DasService service = s_services.get(iterator.next());
if ( serviceName.equalsIgnoreCase(service.getName())) {
service.setEnabled(true);
break;
}
}
}
// Remember the last value
s_enabledServices = enabledServices;
}
}
private boolean isDominoServer() {
boolean isServer = false;
Session session = null;
try {
session = ContextInfo.getUserSession();
isServer = session.isOnServer();
}
catch (Throwable e) {
// Ignore the exception. Just assume we are not on the server.
}
return isServer;
}
/**
* Handles an unknown exception.
*
* <p>When a service throws <code>WebApplicationException</code>, the Wink
* framework catches it and creates the correct error response. This method
* handles unexpected excecptions (e.g. NullPointerException).
*
* @param app
* @param request
* @param response
* @param t
* @throws ServletException
* @throws IOException
*/
private void handleUnknownException(Application app, HttpServletRequest request,
HttpServletResponse response, Throwable t)
throws ServletException, IOException {
// Share exception with the relevant service
if (app instanceof IRestServiceExt) {
try {
((IRestServiceExt) app).onUnknownError(request, t);
}
catch (Throwable e) {
// Ignore exceptions
}
}
// Create an error response
handleError(response, Response.Status.INTERNAL_SERVER_ERROR, t);
}
/**
* Writes the error to the HTTP response.
*
* @param response
* @param status
* @param t
* @throws ServletException
* @throws IOException
*/
private void handleError(HttpServletResponse response, Response.Status status, Throwable t) throws ServletException, IOException {
String message = status.getReasonPhrase();
try {
response.sendError(status.getStatusCode(), message);
response.setContentType(MediaType.APPLICATION_JSON);
StringWriter writer = new StringWriter();
Boolean compact = false;
JsonWriter jWriter = new JsonWriter(writer, compact);
try {
jWriter.startObject();
ErrorHelper.writeProperty(jWriter, ATTR_CODE, status.getStatusCode());
ErrorHelper.writeProperty(jWriter, ATTR_TEXT, message);
if ( t != null ) {
ErrorHelper.writeException(jWriter, t);
}
}
finally {
jWriter.endObject();
}
StringBuffer buffer = writer.getBuffer();
ServletOutputStream os = response.getOutputStream();
os.print(buffer.toString());
os.flush();
}
catch (IOException e) {
DAS_LOGGER.warn(e, "Error creating response."); //$NLW-DasServlet.Errorcreatingresponse-1$
}
}
/**
* Sets the request context for lower level utilities -- like
* string resouce handlers.
*
* @param request
*/
private void setRequestContext(HttpServletRequest request) {
Locale userLocale = null;
List<Locale> locales = parseLocales(request);
if ( locales != null && locales.size() > 0 ) {
userLocale = locales.get(0);
}
RequestContext ctx = RequestContext.getCurrentInstance();
ctx.setUserLocale(userLocale);
ctx.setCustomerId(ScnContext.getCurrentInstance().getCustomerId());
}
/**
* Sets the SCN context.
*
* @param request
*/
private void setScnContext(HttpServletRequest request) {
ScnContext ctx = ScnContext.init();
String dominoDn = request.getHeader("X-DominoDN"); // $NON-NLS-1$
String dominoSamlTo = request.getHeader("X-DominoSAMLTo"); // $NON-NLS-1$
if ( StringUtil.isNotEmpty(dominoDn) && StringUtil.isNotEmpty(dominoSamlTo) ) {
ctx.setScn(true);
}
String ownerId = request.getParameter(URL_PARAM_OWNER);
ctx.setOwnerId(ownerId);
String customerId = request.getHeader("X-DominoCustomerID"); // $NON-NLS-1$
if ( StringUtil.isNotEmpty(customerId) ) {
ctx.setCustomerId(customerId);
}
String userId = request.getHeader("X-DominoUserID"); // $NON-NLS-1$
if ( StringUtil.isNotEmpty(userId) ) {
ctx.setUserId(userId);
}
}
/**
* Parses all Accept-Language headers.
*
* @param request
* @return
*/
private List<Locale> parseLocales(HttpServletRequest request)
{
List<Locale> locales = null;
try {
Enumeration values = request.getHeaders("accept-language"); // $NON-NLS-1$
while(values.hasMoreElements())
{
if ( locales == null ) {
locales = new ArrayList<Locale>();
}
String value = values.nextElement().toString();
parseLocales(locales, value);
}
}
catch (Throwable t) {
// Shouldn't happen, but just in case, ignore parser errors
// and other unchecked exceptions
}
return locales;
}
/**
* Parse a single Accept-Language header.
*
* @param locales
* @param headerValue
*/
private void parseLocales(List<Locale> locales, String headerValue)
{
// Portions of this method were stolen from XspCmdHttpServletRequest. The
// following comment is from there:
//
// Http Header parsing
// Accept-Language = "Accept-Language" ":"
// 1#( language-range [ ";" "q" "=" qvalue ] )
// qvalue = ( "0" [ "." 0*3DIGIT ] )
// | ( "1" [ "." 0*3("0") ] )
// language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
// Each language-range MAY be given an associated quality value which represents an estimate of the user's
// preference for the languages specified by that range. The quality value defaults to "q=1". For example,
//
// Accept-Language: da, en-gb;q=0.8, en;q=0.7
String[] s = StringUtil.splitString(headerValue, ',', true);
for(int i = 0; i < s.length; i++)
{
Matcher matcher = s_acceptLanguagePattern.matcher(s[i]);
if(matcher.find())
{
String l1 = matcher.group(1);
if(l1 == null)
{
l1 = "";
}
String l2 = matcher.group(2);
if(l2 == null)
{
l2 = "";
}
Locale l = new Locale(l1, l2);
// TODO: Handle quality. Although we are parsing it, we
// are throwing it away for now.
double q = 1.0;
String qs = matcher.group(3);
if(qs != null)
{
q = Double.parseDouble(qs);
}
locales.add(l);
}
}
}
}