de.tsl2.nano.h5.NanoH5Session Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.h5 Show documentation
Show all versions of tsl2.nano.h5 Show documentation
TSL2 Framework Html5 Extensions (WebServer, Html5Presentation, RuleCover, BeanConfigurator, LogicTable-Sheet, Expression-Descriptors for Actions, Rules, URLs, Queries)
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Tom, Thomas Schneider
* created on: 11.10.2013
*
* Copyright: (c) Thomas Schneider 2013, all rights reserved
*/
package de.tsl2.nano.h5;
import static de.tsl2.nano.bean.def.BeanPresentationHelper.KEY_FILTER_FROM_LABEL;
import static de.tsl2.nano.bean.def.BeanPresentationHelper.KEY_FILTER_TO_LABEL;
import static de.tsl2.nano.bean.def.IBeanCollector.MODE_CREATABLE;
import static de.tsl2.nano.bean.def.IBeanCollector.MODE_EDITABLE;
import static de.tsl2.nano.bean.def.IBeanCollector.MODE_SEARCHABLE;
import static de.tsl2.nano.h5.HtmlUtil.BTN_ASSIGN;
import static de.tsl2.nano.h5.HtmlUtil.BTN_CANCEL;
import static de.tsl2.nano.h5.HtmlUtil.BTN_SUBMIT;
import static de.tsl2.nano.h5.HtmlUtil.beanID;
import static de.tsl2.nano.h5.NanoH5.OFFSET_FILTERLINES;
import static de.tsl2.nano.h5.NanoHTTPD.MIME_HTML;
import java.io.File;
import java.io.Serializable;
import java.lang.Thread.UncaughtExceptionHandler;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javax.net.ssl.SSLContext;
import org.apache.commons.logging.Log;
import org.java_websocket.server.DefaultSSLWebSocketServerFactory;
import de.tsl2.nano.action.IAction;
import de.tsl2.nano.bean.BeanContainer;
import de.tsl2.nano.bean.BeanUtil;
import de.tsl2.nano.bean.Context;
import de.tsl2.nano.bean.def.Bean;
import de.tsl2.nano.bean.def.BeanCollector;
import de.tsl2.nano.bean.def.BeanDefinition;
import de.tsl2.nano.bean.def.BeanModifier;
import de.tsl2.nano.bean.def.BeanPresentationHelper;
import de.tsl2.nano.bean.def.BeanValue;
import de.tsl2.nano.bean.def.IBeanCollector;
import de.tsl2.nano.bean.def.IBeanFinder;
import de.tsl2.nano.bean.def.IPageBuilder;
import de.tsl2.nano.bean.def.IPresentable;
import de.tsl2.nano.bean.def.MethodAction;
import de.tsl2.nano.collection.CollectionUtil;
import de.tsl2.nano.collection.MapEntrySet;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.ISession;
import de.tsl2.nano.core.Main;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.exception.ExceptionHandler;
import de.tsl2.nano.core.exception.Message;
import de.tsl2.nano.core.execution.Profiler;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.messaging.EMessage;
import de.tsl2.nano.core.messaging.IListener;
import de.tsl2.nano.core.secure.Crypt;
import de.tsl2.nano.core.secure.PKI;
import de.tsl2.nano.core.util.ConcurrentUtil;
import de.tsl2.nano.core.util.DateUtil;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.ListSet;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.NetUtil;
import de.tsl2.nano.core.util.NumberUtil;
import de.tsl2.nano.core.util.ObjectUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
import de.tsl2.nano.h5.NanoHTTPD.Method;
import de.tsl2.nano.h5.NanoHTTPD.Response;
import de.tsl2.nano.h5.NanoHTTPD.Response.Status;
import de.tsl2.nano.h5.collector.Controller;
import de.tsl2.nano.h5.configuration.BeanConfigurator;
import de.tsl2.nano.h5.navigation.EntityBrowser;
import de.tsl2.nano.h5.navigation.IBeanNavigator;
import de.tsl2.nano.h5.navigation.Parameter;
import de.tsl2.nano.h5.navigation.Workflow;
import de.tsl2.nano.h5.plugin.INanoPlugin;
import de.tsl2.nano.h5.websocket.NanoWebSocketServer;
import de.tsl2.nano.h5.websocket.WebSocketExceptionHandler;
import de.tsl2.nano.h5.websocket.chat.ChatMessage;
import de.tsl2.nano.persistence.Persistence;
import de.tsl2.nano.plugin.Plugins;
import de.tsl2.nano.service.util.BeanContainerUtil;
import de.tsl2.nano.serviceaccess.Authorization;
import de.tsl2.nano.serviceaccess.IAuthorization;
import de.tsl2.nano.util.XmlGenUtil;
import de.tsl2.nano.util.operation.IRange;
/**
* user session for nano.h5 server
*
* @author Tom, Thomas Schneider
* @version $Revision$
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class NanoH5Session extends BeanModifier implements ISession, Serializable, IListener {
/** serialVersionUID */
private static final long serialVersionUID = -8299446546343086394L;
private static final Log LOG = LogFactory.getLog(NanoH5Session.class);
transient String id;
transient NanoH5 server;
transient IPageBuilder, String> builder;
/** workflow or bean navigator */
transient IBeanNavigator nav;
/** html response */
transient Response response;
/** concatencation of database-name+schema+beans.jar */
Context context;
/** sessions classloader */
transient ClassLoader sessionClassloader;
/** requests user internet adress */
transient InetAddress inetAddress;
/** port of websocket, if used */
transient int websocketPort;
/** sessions exceptionHandler */
transient ExceptionHandler exceptionHandler;
/** logs all user actions to be given on error-handling */
transient List actionLog;
/** for profiling in status-line (current work-time of last request) */
transient long startTime;
/** session start */
private long sessionStart;
/** session access */
private long lastAccess = System.currentTimeMillis();
transient int requests;
transient private IAuthorization authorization;
transient private BeanContainer beanContainer;
transient private BeanConfigurator beanConfigurator;
/** workaround if nano sends the same page twice */
boolean cacheReloaded;
private String key;
private WebSecurity webSec = new WebSecurity();
private String requestId;
public static final String PREFIX_STATUS_LINE = "@";
public static final String PREFIX_CONTEXT_RANGE = "range:";
// public static final String KEY_WEBSOCKET_PORT = "websocket.port";
public static final NanoH5Session createSession(NanoH5 server,
InetAddress inetAddress,
IBeanNavigator navigator,
ClassLoader appstartClassloader,
IAuthorization authorization,
Map context) {
//TODO: respect the bean-set (bean-jar-file!)
File storedSessionFile = getSessionFile(authorization);
NanoH5Session session = null;
if (storedSessionFile != null && storedSessionFile.exists()) {
try {
//try to load a temp stored session
session = ENV.get(XmlGenUtil.class).loadXml(storedSessionFile.getPath(), NanoH5Session.class);
session.init(server, inetAddress, navigator, appstartClassloader, authorization, context);
} catch (Exception e) {
//on error we create a new session object
LOG.error(e);
}
}
if (session == null) {
session = new NanoH5Session(server, inetAddress, navigator, appstartClassloader, authorization, context);
}
return session;
}
private static File getSessionFile(IAuthorization authorization) {
return authorization != null ? new File(ENV.getTempPathRel() + "session-" + authorization.getUser()) : null;
}
/**
* constructor
*
* @param server
* @param inetAddress
* @param navigation
* @param appstartClassloader
* @param authorization
*/
protected NanoH5Session(NanoH5 server,
InetAddress inetAddress,
IBeanNavigator navigator,
ClassLoader appstartClassloader,
IAuthorization authorization,
Map context) {
super();
init(server, inetAddress, navigator, appstartClassloader, authorization, context);
}
/**
* init
*
* @param server
* @param inetAddress
* @param navigator
* @param appstartClassloader
* @param authorization
* @param context
*/
protected void init(NanoH5 server,
InetAddress inetAddress,
IBeanNavigator navigator,
ClassLoader appstartClassloader,
IAuthorization authorization,
Map context) {
this.server = server;
this.server.getEventController().addListener(this);
this.server.getEventController().addListener((IListener)this, ChatMessage.class);
this.inetAddress = inetAddress;
this.builder = server.builder;
this.nav = navigator;
this.sessionClassloader = appstartClassloader;
createExceptionHandler();
this.actionLog = new LinkedList<>();
this.authorization = authorization;
initContext(authorization, context);
this.sessionStart = System.currentTimeMillis();
Persistence p = Persistence.current(); //only for id-creation
this.id = inetAddress + p.getConnectionUrl() + "&" + p.getConnectionUserName() + "&" + p.getJarFile() + "&" + sessionStart;
}
/**
* initContext
*
* @param authorization
* @param context
*/
void initContext(IAuthorization authorization, Map context) {
this.context = Context.create(authorization != null ? authorization.getUser().toString() : null, true);
this.context.putAll(context);
}
/**
* injectFromContext
*
* @param beandefs
* @param type
* @param range
* @param i
*/
BeanDefinition injectContext(BeanDefinition beandef) {
//inject search filter only on first time - before first search...
if (beandef != null && beandef.isMultiValue() && beandef instanceof BeanCollector && !((BeanCollector) beandef).wasActivated()) {
//fill search parameters...
Iterator ranges = this.context.get(IRange.class);
Class type;
IRange range = null;
while (ranges.hasNext()) {
range = ranges.next();
type = range.getFrom() != null ? range.getFrom().getClass() : null;
IBeanFinder beanFinder;
if (beandef.getDeclaringClass().equals(type)) {
beanFinder = ((BeanCollector) beandef).getBeanFinder();
if (beanFinder != null) {
Bean brange = beanFinder.getFilterRange();
brange.getAttribute("from").setValue(BeanContainer.attachEntities(range.getFrom()));
brange.getAttribute("to").setValue(BeanContainer.attachEntities(range.getTo()));
break;
}
}
}
}
return beandef;
}
/**
* createExceptionHandler
*/
private void createExceptionHandler() {
if (ENV.get("websocket.use", true)) {
NanoWebSocketServer socketServer = createWebSocketServer(
ENV.get("app.ssl.keystore.file", "nanoh5.pks"),
ENV.get("app.ssl.keystore.password", "nanoh5"));
this.exceptionHandler =
(ExceptionHandler) ENV.addService(UncaughtExceptionHandler.class,
new WebSocketExceptionHandler(socketServer));
socketServer.start();
} else {
this.exceptionHandler =
(ExceptionHandler) ENV.addService(UncaughtExceptionHandler.class, new ExceptionHandler());
}
Thread.currentThread().setUncaughtExceptionHandler(exceptionHandler);
ConcurrentUtil.setCurrent(exceptionHandler);
}
private NanoWebSocketServer createWebSocketServer(String keystoreName, String password) {
final NanoWebSocketServer socketServer =
new NanoWebSocketServer(this, createSocketAddress());
if (ENV.get("app.ssl.activate", false)) {
try {
SSLContext sslContext = SSLContext.getInstance(ENV.get("app.ssl.wss.protocol", "TLSv1.2"));//SSLv3
KeyStore keyStore = PKI.createKeyStore(KeyStore.getDefaultType(), keystoreName, password.toCharArray());
sslContext.init(PKI.getKeyManagerFactory(keyStore, password).getKeyManagers(), null, null);
socketServer.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext));
} catch (NoSuchAlgorithmException | KeyManagementException e) {
//Don't stop the application, if websocket fails!
LOG.error(e);
}
}
websocketPort = socketServer.getPort();
return socketServer;
}
private InetSocketAddress createSocketAddress() {
URL url = NanoH5.getServiceURL(null);
//workaround - see doc of NetUtil.getFreePort()
return new InetSocketAddress(url.getHost(), NetUtil.getFreePort());
}
/**
* setAuthorization
*
* @param authorization
*/
@Override
public void setUserAuthorization(Object authorization) {
this.authorization = (IAuthorization) authorization;
initContext((IAuthorization) authorization, context);
// <-- out-comment: we use the session-id on first request!
// server.sessions.remove(inetAddress, this);
// server.sessions.put(getKey(), this);
}
void setBeanContainer(BeanContainer beanContainer) {
this.beanContainer = beanContainer;
}
/**
* main session serve method. requests of type 'GET' and file-links are handled by the application class (NanoH5).
*
* @param uri url, name of a bean, or number of selected item in a beancollector
* @param method 'POST'
* @param header request header
* @param parms request
* @param files
* @return html response
*/
public Response serve(String uri,
String method,
Map header,
Map parms,
Map files) {
String msg = "No Usersession available - Please log in again!";
ManagedException ex = null;
try {
logRequest(uri, method, header, parms, files);
cacheReloaded = false;
webSec.checkSession(this, method, header, parms);
//refresh session values on the current thread
assignSessionToCurrentThread(true, MapUtil.filter(header, "User-Agent"));
//WORKAROUND for uri-problem
String referer = header.get("referer");
if (parms.containsKey(IAction.CANCELED)
|| (method.equals("POST") && referer != null && uri.length() > 1 && referer.contains(uri))) {
uri = "/";
}
//extract bean-specific prefix
BeanDefinition> linkToModel = getUserAuthorization() != null ? nav.fromUrl(uri) : null;
Object userResponse = null;
/*
* uri:
* - file-system links is already handled in parent class.
* - direct link to another page/bean
* - selection-link-number in beancollector
* - bean action of Html5PresentationHelper
*/
Number uriLinkNumber = linkToModel != null
? null
: getUserAuthorization() != null
? NumberUtil.extractNumber(uri.substring(1))
: null;
//form-button clicked - or first page
if (!parms.isEmpty() || linkToModel != null || uriLinkNumber != null || response == null
|| uri.contains(Html5Presentation.PREFIX_BEANREQUEST)) {
if (linkToModel != null) {
userResponse = linkToModel;
} else {
userResponse = processInput(uri, parms, uriLinkNumber);
}
Message.send("trying to load response " + userResponse);
if (userResponse instanceof String && !userResponse.equals(IAction.CANCELED)) {
msg = (String) userResponse;
if (HtmlUtil.isURI(msg)) {
return server.serve(msg, Method.GET, header, parms, files);
} else if (!HtmlUtil.containsHtml(msg)) {
msg = HtmlUtil.createMessagePage(ENV.translate("tsl2nano.info", true), msg);
}
} else {
// if (!exceptionHandler.hasExceptions()) {
// }
msg = getNextPage(userResponse);
}
response = server.createResponse(msg);
} else {
close();
return webSec.addSessionHeader(this, server.createResponse(Html5Presentation.createMessagePage("start.template",
ENV.getName() + "
" + "Restart Session", server.serviceURL)));
}
} catch (Throwable e /*respect errors like NoClassDefFound...the application should continue!*/) {
LOG.error(e);
if (nav == null) // -> session closed
return server.createResponse(Status.NOT_FOUND, MIME_HTML, e.getLocalizedMessage() != null ? e.getLocalizedMessage() : "Please login again!");
ex = new ManagedException(e) {
/** serialVersionUID */
private static final long serialVersionUID = 1L;
@Override
public String getMessage() {
if (ENV.get("app.login.secure", false))
return super.getMessage();
else
return super.getMessage() + "\n\nAction-Stack:\n"
+ StringUtil.toFormattedString(actionLog, 1000, true);
}
};
Plugins.process(INanoPlugin.class).exceptionHandler(ex);
if (nav.current() != null)
msg = refreshPage(ex);
String user = getUserAuthorization() != null ? (String)getUserAuthorization().getUser() : "unauthorized";
try {// only a try, don't throw a new exception in this catch block
FileUtil.writeBytes(msg.getBytes(), ENV.getTempPath() + "page-failed-"
+ "-" + DateUtil.getFormattedTimeStamp() + ".html", false);
} catch (Exception e1) {
LOG.error(e1);
}
response = server.createResponse(Status.BAD_REQUEST, MIME_HTML, msg);
actionLog.clear();
//don't forget that there was an exception. to be seen on the next exception ;-)
logaction(ex.toString(), parms);
EMessage.broadcast(this,
ENV.translate("nanoh5.error", false,
user,
nav.current() != null ? nav.current() : "UNDEFINED", ex.getLocalizedMessage()),
"*");
}
//TODO: eliminate bug in NanoHTTPD not resetting uri...
// header.clear();
// response.header.remove(uri);
Message.send(exceptionHandler, createStatusText(startTime));
if (ex != null)
Message.send(exceptionHandler, ex.toString());
webSec.addSessionHeader(this, response);
return Plugins.process(INanoPlugin.class).handleResponse(response);
}
private void logRequest(String uri, String method, Map header, Map parms,
Map files) {
if (LOG.isDebugEnabled()) {
LOG.debug(
String.format("serving request:\n\turi: %s\n\tmethod: %s\n\theader: %s\n\tparms: %s\n\tfiles: %s",
uri,
method,
header,
parms,
files));
} else {
LOG.info(String.format("serving request " + requests + " : uri: %s, method: %s, %s, parms: %s",
uri,
method,
MapUtil.filter(header, "http-client-ip", "User-Agent"),
parms));
}
}
public String createAntiCSRFToken() {
return webSec.createAntiCSRFToken(this);
}
/**
* assignSessionToCurrentThread
*/
public void assignSessionToCurrentThread(boolean newRequest, Map properties) {
if (newRequest) {
lastAccess = System.currentTimeMillis();
requests++;
if (nav != null && nav.current() instanceof Bean && ((Bean) nav.current()).getInstance() instanceof BeanConfigurator) {
this.beanConfigurator = (BeanConfigurator) ((Bean) nav.current()).getInstance();
}
if (properties != null && !properties.isEmpty()) {
ConcurrentUtil.setCurrent(properties);
}
}
Thread.currentThread().setContextClassLoader(sessionClassloader);
Thread.currentThread().setUncaughtExceptionHandler(exceptionHandler);
ConcurrentUtil.setCurrent(getUserAuthorization(), beanContainer, beanConfigurator, nav, context);
}
/**
* isMobile
* @return true, if client request comes from mobile device
*/
public boolean isMobile() {
Map props = ConcurrentUtil.getCurrent(Map.class);
if (props == null)
return false;
Object userAgent = props.get("User-Agent");
return userAgent != null && userAgent.toString().contains("Mobile");
}
/**
* defines, which values have to be provided to the current thread. each thread lives only for one request! this is
* needed because the app instance provides most of this values and is open for all sessions.
*
* @return types to be provided to the current thread
*/
static final Class[] getThreadLocalTypes() {
return new Class[] { BeanContainer.class, Authorization.class, BeanConfigurator.class, ExceptionHandler.class,
Context.class, EntityBrowser.class, Workflow.class };
}
@Override
public void close() {
LOG.debug("closing session " + this);
server.removeSession(this);
nav = null;
response = null;
authorization = null;
beanContainer = null;
builder = null;
if (sessionClassloader instanceof URLClassLoader) {
if (!sessionClassloader.equals(server.appstartClassloader)) {
Util.trY(() -> ((URLClassLoader)sessionClassloader).close());
if (Thread.currentThread().getContextClassLoader().equals(sessionClassloader))
Thread.currentThread().setContextClassLoader(server.appstartClassloader);
}
}
sessionClassloader = null;
if (exceptionHandler instanceof WebSocketExceptionHandler) {
Util.trY(() -> ((WebSocketExceptionHandler) exceptionHandler).close());
}
exceptionHandler = null;
context = null;
ConcurrentUtil.removeCurrent(getThreadLocalTypes());
// server = null;
}
String createStatusText(long startTime) {
String user =
authorization != null ? ENV.translate("tsl2nano.login.user", true) + ": "
+ authorization.getUser() + "§"
+ StringUtil.toHexString((getUserAuthorization() + "\n" + "RequestID: " + requests).getBytes())
+ ", " + "Online: "
+ DateUtil.getFormattedMinutes(getDuration()) + " min, " : "";
String workflow = /*nav instanceof Workflow
? "" + ENV.translate(nav.getName(), true) + ""
: */nav != null ? ENV.translate(nav.getName(), true) : "unknown";
return PREFIX_STATUS_LINE + user
+ ENV.translate("tsl2nano.time", true)
+ ": " + DateUtil.getFormattedTime(new Date()) + ", "
+ (nav != null ? ENV.translate("tsl2nano.session", true)
+ ": " + workflow + "§"
+ StringUtil.toHexString(getContext().toString().getBytes()) : "")
+ ", "
+ ENV.translate("tsl2nano.request", true) + ": "
+ (int)DateUtil.seconds(System.currentTimeMillis() - startTime) + " sec "
+ (int)(response.getContentLength() / 1024) + " KB"
+ (LOG.isDebugEnabled() ? ", " + "Memory: " + (Profiler.getUsedMem() / (1024 * 1024)) + " MB" : "")
+ (LOG.isDebugEnabled() ? ", " + "working sessions: " + server.sessions.size() : "");
}
private String refreshPage(Object message) {
try {
return builder.build(this, nav.current(), message, true, nav.toArray());
} catch (Exception e) {
LOG.error(e);
return message.toString();
}
}
/**
* pops the next model from navigation stack (see {@link #getNextModel(Object)} and asks the pagebuilder to create a
* full html page for the given model.
*
* @param returnCode return code of last request
* @return html string
*/
private String getNextPage(Object returnCode) {
String msg = "";
if (exceptionHandler.hasExceptions()) {
msg = StringUtil.toFormattedString(exceptionHandler.clearExceptions(), 200, false);
}
if (returnCode instanceof BeanCollector) {
BeanCollector collector = (BeanCollector) returnCode;
Map preAdjustContext = collector.preAdjustContext(getContext());
if (preAdjustContext != null) {
nav.add(collector);
returnCode = preAdjustContext;
}
}
BeanDefinition> model = injectContext(nav.next(returnCode));
if (model != null)
model.onActivation(getContextParameter());
return model != null ? builder.build(this, model, msg, true, nav.toArray()) : server.createStartPage();
}
/**
* process user input
*
* @param uri page uri
* @param parms response parameter
* @param uriLinkNumber if navigator.current() is a bean-collector, it is the selected element number
* @return user response object. may be {@link IAction#CANCELED} any saved or selected object or null.
*/
private Object processInput(String uri, Map parms, Number uriLinkNumber) {
// if (parms.containsKey(PageBuilder.COMMAND_RESTART)) {
// stop();
// main(null);
// }
convertIDs(parms);
Object responseObject = null;
if (parms.containsKey(IAction.CANCELED)) {
logaction(IAction.CANCELED, null);
if (nav.current() != null) {
((BeanDefinition) nav.current()).onDeactivation(getContextParameter());
//perhaps remove configuration bean
BeanConfigurator configurator = ConcurrentUtil.getCurrent(BeanConfigurator.class);
if (configurator != null
&& ((BeanDefinition) nav.current()).getDeclaringClass().equals(BeanConfigurator.class)) {
ConcurrentUtil.removeCurrent(BeanConfigurator.class);
}
}
return IAction.CANCELED;
}
refreshValues(nav.current(), parms);
requestId = createRequestID();
Plugins.process(INanoPlugin.class).handleSessionRequest(this, parms);
if (nav.current() instanceof Controller) {
Controller ctrl = (Controller) nav.current();
Set keys = parms.keySet();
for (String k : keys) {
if (k != null && k.startsWith(Controller.PREFIX_CTRLACTION)) {
return ctrl.doAction(k, getContextParameter());
}
}
}
//follow links or fill selected items
if (nav.current() instanceof BeanCollector) {
BeanCollector collector = (BeanCollector) nav.current();
//follow given link
if (uriLinkNumber != null) {
Collection data = collector.getCurrentData();
if (data.isEmpty() && !ENV.get("app.mode.strict", false)) {
if (ENV.get("session.onemptycollector.create.newitem", true)) {
Message.send("navigation error: empty collector -> creating new item...");
} else {
Message.send("navigation error: empty collector -> going back to " + nav.current());
return nav.current();
}
} else {
ListSet listSet = CollectionUtil.asListSet(data);
int selectedIndex = uriLinkNumber.intValue()
- (!collector.isSimpleList() && ENV.get("layout.grid.searchrow.show", true) && collector.hasMode(MODE_SEARCHABLE)
&& collector.hasFilter() ? 2 : 0);
Object selectedItem = listSet.get(selectedIndex);
boolean isTypeList = BeanCollector.class.isAssignableFrom(collector.getClazz());
responseObject = isTypeList ? selectedItem : Bean.getBean(selectedItem);
}
} else {
if (!isCanceled(parms)
&& (/*isNewAction(parms, (BeanCollector) nav.current()) || */provideSelection(
(BeanCollector) nav.current(), parms))) {
if (isReturn(parms)) {
responseObject = null;
} else if (isOpenAction(parms, (BeanCollector) nav.current())) {
//normally, after a selection the navigation object will be hold on stack
if (ENV.get("app.edit.multiple", true)) {
responseObject = putSelectionOnStack((BeanCollector) nav.current());
} else {
responseObject = nav.current();
}
}
}
}
setNavigationGimmicks(responseObject, parms);
} else if (nav.current() instanceof Bean) {//detail bean
//on database models with composite-ids, these ids should be synchronized with standard values.
Bean bean = (Bean) nav.current();
if (bean.isPersistable()) {
BeanContainerUtil.synchronizeEmbeddedCompositeID(bean.getInstance());
}
}
//collect available actions
if (nav.current() != null) {
responseObject = performAction(uri, nav.current(), parms, responseObject);
}
return responseObject;
}
private String createRequestID() {
return Crypt.hashHex("" + new Random().nextDouble() + System.currentTimeMillis());
}
private IAction> setNavigationGimmicks(Object responseObject, Map parms) {
BeanCollector collector;
IAction> action = null;
if (responseObject instanceof BeanCollector) {
collector = (BeanCollector) responseObject;
if (collector.hasMode(MODE_CREATABLE) && collector.getActionByName(BeanCollector.ACTION_NEW).isEnabled()) {
if (collector.getCurrentData().isEmpty() && collector.doAutomaticSearch() && ENV.get("session.navigation.gimmick.onemptycollector.create.newitem", true)) {
if (collector.getCurrentData().isEmpty()) {
Message.send("empty collector -> creating new item...");
action = collector.getActionByName(BeanCollector.ACTION_NEW);
parms.put(action.getId(), "");
}
}
} else if (collector.hasMode(MODE_EDITABLE) && collector.getActionByName(BeanCollector.ACTION_OPEN).isEnabled()) {
if (collector.getCurrentData().size() == 1 && ENV.get("session.navigation.gimmick.ononeitemincollector.select.first", false)) {
Message.send("collector with one item-> select that item...");
action = collector.getActionByName(BeanCollector.ACTION_OPEN);
parms.put(action.getId(), "");
}
}
}
return action;
}
/**
* performAction
* @param uri
* @param current
* @param parms
* @param responseObject
* @return
*/
Object performAction(String uri, BeanDefinition> current, Map parms, Object responseObject) {
Collection actions = evaluateActionsFor(current);
Collection responseObjectActions = responseObject instanceof BeanDefinition ? evaluateActionsFor((BeanDefinition>) responseObject) : null;
//start the actions
//respect action-call through menu-link (with method GET but starting with '!!!'
Set