/**
* Copyright 2010-2016 Ralph Schaer <ralphschaer@gmail.com>
*
* 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 ch.ralscha.extdirectspring.controller;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.convert.ConversionService;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import ch.ralscha.extdirectspring.annotation.ExtDirectMethod;
import ch.ralscha.extdirectspring.bean.BaseResponse;
import ch.ralscha.extdirectspring.bean.ExtDirectResponse;
import ch.ralscha.extdirectspring.bean.ExtDirectStoreResult;
import ch.ralscha.extdirectspring.util.JsonHandler;
/**
* Configuration class to configure different aspects of extdirectspring.
*/
public class Configuration {
private String defaultExceptionMessage = "Server Error";
private boolean sendExceptionMessage = false;
private boolean sendStacktrace = false;
private Map<Class<?>, String> exceptionToMessage;
private boolean alwaysWrapStoreResponse = false;
private boolean synchronizeOnSession = false;
private String apiNs = "Ext.app";
private String actionNs = null;
private String remotingApiVar = "REMOTING_API";
private String pollingUrlsVar = "POLLING_URLS";
private boolean fullRouterUrl = false;
private String baseRouterUrl = null;
private Integer timeout = null;
private Integer maxRetries = null;
private Object enableBuffer = null;
private Integer bufferLimit = null;
private boolean streamResponse = false;
private String jsContentType = "application/javascript";
private BatchedMethodsExecutionPolicy batchedMethodsExecutionPolicy = BatchedMethodsExecutionPolicy.SEQUENTIAL;
private ExecutorService batchedMethodsExecutorService = null;
private String providerType = "remoting";
private String frameDomain = null;
private String frameDomainScript = "<script type=\"text/javascript\">document.domain = '%s';</script>";
private JsonHandler jsonHandler;
private ConversionService conversionService;
public String getDefaultExceptionMessage() {
return this.defaultExceptionMessage;
}
/**
* Changes the default message when an exception occurred and there is no mapping
* found in {@link #getExceptionToMessage()} and {@link #isSendExceptionMessage()} is
* false.
* <p>
* Default value is "Server Error".
* <p>
* This value is set into {@link ExtDirectResponse#setMessage(String)} and sent to the
* client.
*
* @see #setExceptionToMessage(Map)
* @see #setDefaultExceptionMessage(String)
*
* @param defaultExceptionMessage new default exception message
*/
public void setDefaultExceptionMessage(String defaultExceptionMessage) {
this.defaultExceptionMessage = defaultExceptionMessage;
}
public boolean isSendExceptionMessage() {
return this.sendExceptionMessage;
}
/**
* Changes the way {@link ExtDirectResponse#setMessage(String)} is called.
* <p>
* If this flag is set to true and an exception occurred instead of
* {@link #getDefaultExceptionMessage()} {@link Throwable#getMessage()} is put into
* the message field of the response. Only if there is no mapping found in
* {@link #getExceptionToMessage()}.
* <p>
* Default value is false.
*
* @see #setExceptionToMessage(Map)
* @see #setDefaultExceptionMessage(String)
*
* @param sendExceptionMessage new flag
*/
public void setSendExceptionMessage(boolean sendExceptionMessage) {
this.sendExceptionMessage = sendExceptionMessage;
}
public boolean isSendStacktrace() {
return this.sendStacktrace;
}
/**
* If sendStacktrace is true, the library sends, in case of an exception, the full
* stacktrace in {@link BaseResponse#setWhere(String)} back to the client.
* <p>
* Should only set to true in development.
* <p>
* Default value is false
*
* @param sendStacktrace new flag
*/
public void setSendStacktrace(boolean sendStacktrace) {
this.sendStacktrace = sendStacktrace;
}
public Map<Class<?>, String> getExceptionToMessage() {
return this.exceptionToMessage;
}
/**
* Sets the new exception-to-message map.
* <p>
* If there is a mapping for the exception in {@link #getExceptionToMessage()} and the
* value is not null put this value in {@link ExtDirectResponse#setMessage(String)}.
* <p>
* If there is a mapping for the exception in {@link #getExceptionToMessage()} and the
* value is null use {@link Throwable#getMessage()}.
* <p>
* If there is no mapping and {@link #isSendExceptionMessage()} is true use
* {@link Throwable#getMessage()}.
* <p>
* If there is no mapping and {@link #isSendExceptionMessage()} is false use
* {@link #getDefaultExceptionMessage()}.
*
* @see #setDefaultExceptionMessage(String)
* @see #setSendExceptionMessage(boolean)
*
* @param exceptionToMessage new mapping from exception to message
*/
public void setExceptionToMessage(Map<Class<?>, String> exceptionToMessage) {
this.exceptionToMessage = exceptionToMessage;
}
public boolean isAlwaysWrapStoreResponse() {
return this.alwaysWrapStoreResponse;
}
/**
* If alwaysWrapStoreResponse is true, responses of STORE_READ and STORE_MODIFY
* methods are always wrapped in an {@link ExtDirectStoreResult} object.
*
* @param alwaysWrapStoreResponse new flag
*/
public void setAlwaysWrapStoreResponse(boolean alwaysWrapStoreResponse) {
this.alwaysWrapStoreResponse = alwaysWrapStoreResponse;
}
public boolean isSynchronizeOnSession() {
return this.synchronizeOnSession;
}
/**
* If synchronizeOnSession is true, execution of all methods is synchronized on the
* session object. To serialize parallel invocations from the same client and to
* prevent concurrency issues if the server accesses global or session resources.
* <p>
* Instead of globally enable this it's possible to set the flag on a per method basis
* with {@link ExtDirectMethod#synchronizeOnSession()}.
*
* @param synchronizeOnSession new flag
*/
public void setSynchronizeOnSession(boolean synchronizeOnSession) {
this.synchronizeOnSession = synchronizeOnSession;
}
public Integer getTimeout() {
return this.timeout;
}
/**
* Sets the timeout in milliseconds for remote calls. This parameter is part of the
* configuration object api.js sends to the client and configures the timeout property
* of the <a href=
* "http://docs.sencha.com/extjs/6.0/6.0.0-classic/#!/api/Ext.direct.RemotingProvider"
* > RemotingProvider</a>.
*
* @param timeout new timeout value
*/
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}
public Integer getMaxRetries() {
return this.maxRetries;
}
/**
* Sets the number of times the client will try to send a message to the server before
* throwing a failure. Default value is 1. This parameter is part of the configuration
* object api.js sends to the client and configures the maxRetries property of the
* <a href=
* "http://docs.sencha.com/extjs/6.0/6.0.0-classic/#!/api/Ext.direct.RemotingProvider"
* > RemotingProvider</a>.
*
* @param maxRetries new number of max retries
*/
public void setMaxRetries(Integer maxRetries) {
this.maxRetries = maxRetries;
}
public Object getEnableBuffer() {
return this.enableBuffer;
}
/**
* true or false to enable or disable combining of method calls. If a number is
* specified this is the amount of time in milliseconds to wait before sending a
* batched request. Calls which are received within the specified timeframe will be
* concatenated together and sent in a single request, optimizing the application by
* reducing the amount of round trips that have to be made to the server.
* <p>
* This parameter is part of the configuration object api.js sends to the client and
* configures the enableBuffer property of the <a href=
* "http://docs.sencha.com/extjs/6.0/6.0.0-classic/#!/api/Ext.direct.RemotingProvider"
* > RemotingProvider</a>.
* <p>
* Defaults to: 10
*
* @param enableBuffer new enableBuffer value
*/
public void setEnableBuffer(Object enableBuffer) {
this.enableBuffer = enableBuffer;
}
public Integer getBufferLimit() {
return this.bufferLimit;
}
/**
* The maximum number of requests to batch together. By default, an unlimited number
* of requests will be batched. This option will allow to wait only for a certain
* number of Direct method calls before dispatching a request to the server, even if
* {@link #enableBuffer} timeout has not yet expired.
* <p>
* Note that this option does nothing if {@link #enableBuffer} is set to `false`.
* <p>
* Defaults to: Number.MAX_VALUE
*
* @param bufferLimit new value for buffer limit
*/
@SuppressWarnings("javadoc")
public void setBufferLimit(Integer bufferLimit) {
this.bufferLimit = bufferLimit;
}
/**
* Returns an error message for the supplied exception and based on this
* configuration.
*
* @see #setDefaultExceptionMessage(String)
* @see #setSendExceptionMessage(boolean)
* @see #setExceptionToMessage(Map)
*
* @param exception the thrown exception
* @return exception message
*/
public String getMessage(Throwable exception) {
String message;
if (getExceptionToMessage() != null) {
message = getExceptionToMessage().get(exception.getClass());
if (StringUtils.hasText(message)) {
return message;
}
// map entry with a null value
if (getExceptionToMessage().containsKey(exception.getClass())) {
return exception.getMessage();
}
}
if (isSendExceptionMessage()) {
return exception.getMessage();
}
return getDefaultExceptionMessage();
}
public boolean isStreamResponse() {
return this.streamResponse;
}
/**
* If streamResponse is true, the JSON response will be directly written into the
* {@link HttpServletResponse#getOutputStream()} without setting the Content-Length
* header. The old ExtDirectSpring 1.0.x behavior.
* <p>
* If false the {@link RouterController} writes the JSON into an internal buffer, sets
* the Content-Length header in {@link HttpServletResponse} and writes the buffer into
* {@link HttpServletResponse#getOutputStream()}.
* <p>
* Instead of globally enable this it's possible to set the flag on a per method basis
* with {@link ExtDirectMethod#streamResponse()}.
* <p>
* Default value is false
*
* @param streamResponse new flag
*/
public void setStreamResponse(boolean streamResponse) {
this.streamResponse = streamResponse;
}
/**
* Specifies the Content-Type for api.js and api-debug.js.
* <p>
* Until version 1.2.1 extdirectspring sends "application/x-javascript". But according
* to <a href="http://www.rfc-editor.org/rfc/rfc4329.txt">RFC4329</a> the official
* mime type is 'application/javascript'.
* <p>
* Default value is "application/javascript"
*
* @param jsContentType new Content-type
*/
public void setJsContentType(String jsContentType) {
this.jsContentType = jsContentType;
}
public String getJsContentType() {
return this.jsContentType;
}
public BatchedMethodsExecutionPolicy getBatchedMethodsExecutionPolicy() {
return this.batchedMethodsExecutionPolicy;
}
/**
* Specifies how batched methods sent from the client should be executed on the
* server. {@link BatchedMethodsExecutionPolicy#SEQUENTIAL} executes methods one after
* the other. {@link BatchedMethodsExecutionPolicy#CONCURRENT} executes methods
* concurrently with the help of a thread pool.
*
* <p>
* Default value is {@link BatchedMethodsExecutionPolicy#SEQUENTIAL}
*
* @see #setBatchedMethodsExecutorService(ExecutorService)
* @param batchedMethodsExecutionPolicy new policy
*/
public void setBatchedMethodsExecutionPolicy(
BatchedMethodsExecutionPolicy batchedMethodsExecutionPolicy) {
Assert.notNull(batchedMethodsExecutionPolicy,
"batchedMethodsExecutionPolicy must not be null");
this.batchedMethodsExecutionPolicy = batchedMethodsExecutionPolicy;
}
public ExecutorService getBatchedMethodsExecutorService() {
return this.batchedMethodsExecutorService;
}
/**
* Sets the thread pool used for executing batched methods concurrently.
* <p>
* If batchedMethodsExecutionPolicy is set to
* {@link BatchedMethodsExecutionPolicy#CONCURRENT} but no
* batchedMethodsExecutorService is specified the library creates a
* {@link Executors#newFixedThreadPool(int)} with 5 threads.
*
* @see #setBatchedMethodsExecutionPolicy(BatchedMethodsExecutionPolicy)
* @param batchedMethodsExecutorService the new thread pool
*/
public void setBatchedMethodsExecutorService(
ExecutorService batchedMethodsExecutorService) {
this.batchedMethodsExecutorService = batchedMethodsExecutorService;
}
public String getProviderType() {
return this.providerType;
}
/**
* Sets the type of the provider. The type is sent to the client in the api
* configuration.
* <p>
* Default value is "remoting" and it creates an Ext.direct.RemotingProvider on the
* client side.
*
* @param providerType new provider type
*/
public void setProviderType(String providerType) {
this.providerType = providerType;
}
public String getFrameDomain() {
return this.frameDomain;
}
/**
* Sets the passed domain to be included in the file upload's temporary frame. This is
* used to grant the main document access to the POST response on the frame in a
* cross-domain environment.
*
* @param frameDomain the new domain to set the frame to
*/
public void setFrameDomain(String frameDomain) {
this.frameDomain = frameDomain;
}
public String getFrameDomainScript() {
return this.frameDomainScript;
}
/**
* Updates the script that is used to set the domain values on the file upload frame.
* This is useful for cross-browser compatibility. If other browsers require a
* modified script as workaround, frameDomainScript should allow for it.
*
* @param frameDomainScript the javascript code used to set the frame domain
*/
public void setFrameDomainScript(String frameDomainScript) {
this.frameDomainScript = frameDomainScript;
}
public String getApiNs() {
return this.apiNs;
}
/**
* Sets the name of the namespace in which the remotingApiVar variable will reside.
* <p>
* Defaults to Ext.app
*
* @param apiNs new namespace
*/
public void setApiNs(String apiNs) {
this.apiNs = apiNs;
}
public String getActionNs() {
return this.actionNs;
}
/**
* Sets the name of the namespace in which the actions will reside.
* <p>
* Defaults to none
*
* @param actionNs new namespace
*/
public void setActionNs(String actionNs) {
this.actionNs = actionNs;
}
public String getRemotingApiVar() {
return this.remotingApiVar;
}
/**
* Changes the name of the remoting api variable.
* <p>
* Defaults to REMOTING_API
*
* @param remotingApiVar new remoting api varaible name
*/
public void setRemotingApiVar(String remotingApiVar) {
this.remotingApiVar = remotingApiVar;
}
public String getPollingUrlsVar() {
return this.pollingUrlsVar;
}
/**
* Changes the name of the polling urls object variable
* <p>
* Defaults to POLLING_URLS
*
* @param pollingUrlsVar new polling urls object variable name
*/
public void setPollingUrlsVar(String pollingUrlsVar) {
this.pollingUrlsVar = pollingUrlsVar;
}
public boolean isFullRouterUrl() {
return this.fullRouterUrl;
}
/**
* Specifies if the router property should contain the full URL including protocol,
* server name, port number, and server path (true) or only the server path (false)
* <p>
* Defaults to false
*
* @param fullRouterUrl new flag value
*/
public void setFullRouterUrl(boolean fullRouterUrl) {
this.fullRouterUrl = fullRouterUrl;
}
public String getBaseRouterUrl() {
return this.baseRouterUrl;
}
/**
* If not null the {@link ApiController} does not use the url of the request to
* determine the router url instead he uses the value of this variable as the base and
* adds /router and /poll.<br>
* The fullRouterUrl setting is ignored when this variable is not null
* <p>
* Defaults to null.
*
* @param baseRouterUrl new base router url
*/
public void setBaseRouterUrl(String baseRouterUrl) {
this.baseRouterUrl = baseRouterUrl;
}
public ConversionService getConversionService() {
return this.conversionService;
}
public void setConversionService(ConversionService conversionService) {
this.conversionService = conversionService;
}
public JsonHandler getJsonHandler() {
return this.jsonHandler;
}
public void setJsonHandler(JsonHandler jsonHandler) {
this.jsonHandler = jsonHandler;
}
}