package org.bonitasoft.livingapps;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.bonitasoft.console.common.server.page.CustomPageService;
import org.bonitasoft.console.common.server.page.PageRenderer;
import org.bonitasoft.console.common.server.page.ResourceRenderer;
import org.bonitasoft.console.common.server.page.extension.PageResourceProviderImpl;
import org.bonitasoft.console.common.server.utils.BonitaHomeFolderAccessor;
import org.bonitasoft.engine.business.application.ApplicationPageNotFoundException;
import org.bonitasoft.engine.exception.BonitaException;
import org.bonitasoft.engine.page.PageNotFoundException;
import org.bonitasoft.engine.session.APISession;
import org.bonitasoft.livingapps.exception.CreationException;
public class ApplicationRouter {
private final ApplicationModelFactory applicationModelFactory;
protected BonitaHomeFolderAccessor bonitaHomeFolderAccessor = new BonitaHomeFolderAccessor();
protected final String THEME_TOKEN = "theme";
public ApplicationRouter(final ApplicationModelFactory applicationModelFactory) {
this.applicationModelFactory = applicationModelFactory;
}
public void route(final HttpServletRequest hsRequest, final HttpServletResponse hsResponse, final APISession session, final PageRenderer pageRenderer,
final ResourceRenderer resourceRenderer, final BonitaHomeFolderAccessor bonitaHomeFolderAccessor)
throws CreationException, BonitaException, IOException, ServletException, IllegalAccessException, InstantiationException {
final ParsedRequest parsedRequest = parse(hsRequest.getContextPath(), hsRequest.getRequestURI());
//Test if url contain at least application name
final List<String> pathSegments = resourceRenderer.getPathSegments(hsRequest.getPathInfo());
if (pathSegments.isEmpty()) {
hsResponse.sendError(HttpServletResponse.SC_BAD_REQUEST,
"The name of the application is required.");
return;
}
if ("API".equals(parsedRequest.getPageToken())) {
//Support relative calls to the REST API from the application page using ../API/
hsRequest.getRequestDispatcher("/" + getResourcePathWithoutApplicationToken(hsRequest.getPathInfo(), parsedRequest.getApplicationName())).forward(
hsRequest, hsResponse);
} else if ("GET".equals(hsRequest.getMethod())) {
displayPageOrResource(hsRequest, hsResponse, session, pageRenderer, resourceRenderer, bonitaHomeFolderAccessor, parsedRequest, pathSegments);
} else {
hsResponse.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "http.method_" + hsRequest.getMethod().toLowerCase() + "_not_supported");
}
}
protected void displayPageOrResource(final HttpServletRequest hsRequest, final HttpServletResponse hsResponse, final APISession session,
final PageRenderer pageRenderer, final ResourceRenderer resourceRenderer, final BonitaHomeFolderAccessor bonitaHomeFolderAccessor,
final ParsedRequest parsedRequest, final List<String> pathSegments) throws IOException,
ApplicationPageNotFoundException, InstantiationException, IllegalAccessException, BonitaException, PageNotFoundException, CreationException {
final ApplicationModel application = applicationModelFactory.createApplicationModel(parsedRequest.getApplicationName());
if (!application.hasProfileMapped()) {
hsResponse.sendError(HttpServletResponse.SC_NOT_FOUND, "No profile mapped to living application");
return;
}
//If no page name, redirect to Home page
if (parsedRequest.getPageToken() == null) {
hsResponse.sendRedirect(application.getApplicationHomePage());
return;
}
if (isApplicationPageRequest(pathSegments)) {
//Application page request
if (application.hasPage(parsedRequest.getPageToken()) && application.authorize(session)) {
pageRenderer.displayCustomPage(hsRequest, hsResponse, session, application.getApplicationLayoutName());
} else {
hsResponse.sendError(HttpServletResponse.SC_FORBIDDEN,
"Unauthorized access for the page " + parsedRequest.getPageToken() + " of the application " + parsedRequest.getApplicationName());
}
} else {
//Layout or theme resource file request
final File resourceFile = getResourceFile(pageRenderer, hsRequest.getPathInfo(), pathSegments, application, session, bonitaHomeFolderAccessor);
pageRenderer
.ensurePageFolderIsPresent(session, pageRenderer.getPageResourceProvider(getPageName(pathSegments, application), session.getTenantId()));
resourceRenderer.renderFile(hsRequest, hsResponse, resourceFile, session);
}
}
private boolean isApplicationPageRequest(final List<String> pathSegments) {
return pathSegments.size() == 2;
}
private File getResourceFile(final PageRenderer pageRenderer, final String resourcePath, final List<String> pathSegments,
final ApplicationModel application, final APISession apiSession, final BonitaHomeFolderAccessor bonitaHomeFolderAccessor) throws IOException,
BonitaException {
final String pageName = getPageName(pathSegments, application);
final PageResourceProviderImpl pageResourceProvider = pageRenderer.getPageResourceProvider(pageName, apiSession.getTenantId());
final File resourceFile = new File(pageResourceProvider.getPageDirectory(), CustomPageService.RESOURCES_PROPERTY + File.separator
+ getResourcePath(resourcePath, pathSegments.get(0), pathSegments.get(1)));
if (!bonitaHomeFolderAccessor.isInFolder(resourceFile, pageResourceProvider.getPageDirectory())) {
throw new BonitaException("Unauthorized access to the file " + resourcePath);
}
return resourceFile;
}
private String getPageName(final List<String> pathSegments, final ApplicationModel application) throws PageNotFoundException {
String pageName;
if (THEME_TOKEN.equals(pathSegments.get(1))) {
pageName = application.getApplicationThemeName();
} else {
pageName = application.getApplicationLayoutName();
}
return pageName;
}
private String getResourcePath(final String fullResourcePath, final String applicationName, final String pageToken) {
//resource path match "/applicationName/pageName/{resourcePath}"
// or "/applicationName/theme/{resourcePath}"
String resourcePath = getResourcePathWithoutApplicationToken(fullResourcePath, applicationName);
resourcePath = getResourcePathWithoutPageToken(resourcePath, pageToken);
return resourcePath;
}
private String getResourcePathWithoutApplicationToken(final String resourcePath, final String applicationName) {
//resource path match "/applicationName/{resourcePath}"
return resourcePath.substring(applicationName.length() + 2);
}
private String getResourcePathWithoutPageToken(final String resourcePath, final String pageToken) {
return resourcePath.substring(pageToken.length() + 1);
}
private ParsedRequest parse(final String context, final String uri) {
final Pattern pattern = Pattern.compile("^" + context + "/apps/(.*)$");
final Matcher matcher = pattern.matcher(uri);
if (!matcher.find()) {
throw new RuntimeException("URI badly formed.");
}
final String[] fragments = matcher.group(1).split("/");
String pageToken = null;
if (fragments.length > 1) {
pageToken = fragments[1];
}
return new ParsedRequest(fragments[0], pageToken);
}
private class ParsedRequest {
private final String applicationToken;
private final String pageToken;
public ParsedRequest(final String applicationToken, final String pageToken) {
this.applicationToken = applicationToken;
this.pageToken = pageToken;
}
public String getApplicationName() {
return applicationToken;
}
public String getPageToken() {
return pageToken;
}
}
}