/*******************************************************************************
* Copyright (c) 2012 OpenLegacy Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* OpenLegacy Inc. - initial API and implementation
*******************************************************************************/
package org.openlegacy.terminal.mvc.web;
import flexjson.JSONSerializer;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openlegacy.OpenLegacyProperties;
import org.openlegacy.modules.menu.Menu.MenuEntity;
import org.openlegacy.terminal.ScreenEntity;
import org.openlegacy.terminal.TerminalSession;
import org.openlegacy.terminal.actions.TerminalActions;
import org.openlegacy.terminal.definitions.ScreenEntityDefinition;
import org.openlegacy.terminal.json.JsonSerializationUtil;
import org.openlegacy.terminal.layout.ScreenPageBuilder;
import org.openlegacy.terminal.modules.table.ScrollableTableUtil;
import org.openlegacy.terminal.providers.TablesDefinitionProvider;
import org.openlegacy.terminal.services.ScreenEntitiesRegistry;
import org.openlegacy.terminal.utils.ScreenEntityUtils;
import org.openlegacy.utils.ProxyUtil;
import org.openlegacy.utils.ReflectionUtil;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mobile.device.site.SitePreference;
import org.springframework.mobile.device.site.SitePreferenceUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* OpenLegacy default web controller for a terminal session. Handles GET/POST requests of a web application. Works closely with
* generic.jspx / composite.jspx. Saves the need for a dedicated controller and page for each screen API, if such doesn't exists.
*
* @author Roi Mor
*
*/
@Controller
public class DefaultGenericController {
private static final String ACTION = "action";
private final static Log logger = LogFactory.getLog(DefaultGenericController.class);
private static final String PAGE = "page";
@Inject
private TerminalSession terminalSession;
@Inject
private ScreenEntitiesRegistry screenEntitiesRegistry;
@Inject
private ScreenEntityUtils screenEntityUtils;
@Inject
private ScreenPageBuilder pageBuilder;
@Inject
private TablesDefinitionProvider tablesDefinitionProvider;
@Inject
private ServletContext servletContext;
private String mobileViewsPath = "/WEB-INF/mobile/views";
private String webViewsPath = "/WEB-INF/web/views";
private String viewsSuffix = ".jspx";
@Inject
private OpenLegacyProperties openlegacyProperties;
@RequestMapping(value = "/{screen}", method = RequestMethod.GET)
public String getScreenEntity(@PathVariable("screen") String screenEntityName,
@RequestParam(value = "partial", required = false) String partial, Model uiModel, HttpServletRequest request)
throws IOException {
ScreenEntity screenEntity = (ScreenEntity)terminalSession.getEntity(screenEntityName);
return prepareView(screenEntity, uiModel, partial != null, request);
}
@RequestMapping(value = "/{screen}/{key:\\d+}", method = RequestMethod.GET)
public String getScreenEntityWithKey(@PathVariable("screen") String screenEntityName, @PathVariable("key") String key,
@RequestParam(value = "partial", required = false) String partial, Model uiModel, HttpServletRequest request)
throws IOException {
ScreenEntity screenEntity = (ScreenEntity)terminalSession.getEntity(screenEntityName, key);
return prepareView(screenEntity, uiModel, partial != null, request);
}
@RequestMapping(value = "/{screen}", method = RequestMethod.POST)
public String postScreenEntity(@PathVariable("screen") String screenEntityName,
@RequestParam(defaultValue = "", value = ACTION) String action,
@RequestParam(value = "partial", required = false) String partial, HttpServletRequest request,
HttpServletResponse response, Model uiModel) throws IOException {
Class<?> entityClass = findAndHandleNotFound(screenEntityName, response);
if (entityClass == null) {
return null;
}
ScreenEntity screenEntity = (ScreenEntity)terminalSession.getEntity(screenEntityName);
ServletRequestDataBinder binder = new ServletRequestDataBinder(screenEntity);
binder.bind(request);
screenEntityUtils.sendScreenEntity(terminalSession, screenEntity, action);
screenEntity = terminalSession.getEntity();
if (request.getParameter("partial") != null) {
return returnPartialPage(screenEntityName, uiModel, partial, request);
} else {
if (screenEntity == null) {
Assert.notNull(openlegacyProperties.getFallbackUrl(), "No fallback URL defined");
return MvcConstants.REDIRECT + openlegacyProperties.getFallbackUrl();
}
String resultEntityName = ProxyUtil.getOriginalClass(screenEntity.getClass()).getSimpleName();
return MvcConstants.REDIRECT + resultEntityName;
}
}
private String returnPartialPage(String screenEntityName, Model uiModel, String partial, HttpServletRequest request)
throws MalformedURLException {
ScreenEntity resultEntity = (ScreenEntity)terminalSession.getEntity(screenEntityName);
return prepareView(resultEntity, uiModel, true, request);
}
private String prepareView(ScreenEntity screenEntity, Model uiModel, boolean partial, HttpServletRequest request)
throws MalformedURLException {
String screenEntityName = ProxyUtil.getOriginalClass(screenEntity.getClass()).getSimpleName();
uiModel.addAttribute(StringUtils.uncapitalize(screenEntityName), screenEntity);
ScreenEntityDefinition entityDefinition = screenEntitiesRegistry.get(screenEntityName);
uiModel.addAttribute(PAGE, pageBuilder.build(entityDefinition));
SitePreference sitePreference = SitePreferenceUtils.getCurrentSitePreference(request);
boolean isComposite = entityDefinition.getChildEntitiesDefinitions().size() > 0 && !partial;
String suffix = isComposite ? MvcConstants.COMPOSITE_SUFFIX : "";
String viewName = entityDefinition.getEntityName() + suffix;
String viewsPath = sitePreference == SitePreference.MOBILE ? mobileViewsPath : webViewsPath;
if (servletContext.getResource(MessageFormat.format("{0}/{1}{2}", viewsPath, viewName, viewsSuffix)) == null) {
if (isComposite) {
viewName = MvcConstants.COMPOSITE;
} else if (entityDefinition.getType() == MenuEntity.class && entityDefinition.getNavigationDefinition() == null) {
viewName = MvcConstants.ROOTMENU_VIEW;
} else if (partial) {
viewName = MvcConstants.GENERIC_VIEW;
} else {
viewName = MvcConstants.GENERIC;
}
}
return viewName;
}
/**
* Look for the given screen entity in the registry, and return HTTP 400 (BAD REQUEST) in case it's not found
*
* @param screenEntityName
* @param response
* @return
* @throws IOException
*/
private Class<?> findAndHandleNotFound(String screenEntityName, HttpServletResponse response) throws IOException {
Class<?> entityClass = screenEntitiesRegistry.getEntityClass(screenEntityName);
if (entityClass == null) {
String message = MessageFormat.format("Screen entity {0} not found", screenEntityName);
response.sendError(HttpServletResponse.SC_BAD_REQUEST, message);
logger.error(message);
}
return entityClass;
}
/**
* handle ajax request for load more
*
* @return
*/
@RequestMapping(value = "/{screen}/more", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<String> more(@PathVariable("screen") String entityName) {
ScreenEntity nextScreen = terminalSession.doAction(TerminalActions.PAGEDOWN());
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/text; charset=utf-8");
List<?> records = ScrollableTableUtil.getSingleScrollableTable(tablesDefinitionProvider, nextScreen);
String result = new JSONSerializer().serialize(records);
return new ResponseEntity<String>(result, headers, HttpStatus.OK);
}
/**
* handle Ajax request for auto compete fields
*
* @param screenEntityName
* @param fieldName
* @return JSON content
*/
@RequestMapping(value = "/{screen}/{field}Values", method = RequestMethod.GET, headers = "X-Requested-With=XMLHttpRequest")
@ResponseBody
public ResponseEntity<String> autoCompleteJson(@PathVariable("screen") String screenEntityName,
@PathVariable("field") String fieldName) {
ScreenEntity screenEntity = (ScreenEntity)terminalSession.getEntity(screenEntityName);
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/text; charset=utf-8");
@SuppressWarnings("unchecked")
Map<Object, Object> fieldValues = (Map<Object, Object>)ReflectionUtil.invoke(screenEntity,
MessageFormat.format("get{0}Values", StringUtils.capitalize(fieldName)));
String result = JsonSerializationUtil.toDojoFormat(fieldValues);
return new ResponseEntity<String>(result, headers, HttpStatus.OK);
}
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
public void setWebViewsPath(String webViewsPath) {
this.webViewsPath = webViewsPath;
}
public void setMobileViewsPath(String mobileViewsPath) {
this.mobileViewsPath = mobileViewsPath;
}
public void setViewsSuffix(String viewsSuffix) {
this.viewsSuffix = viewsSuffix;
}
}