/*
* Copyright 2013 eXo Platform SAS
*
* 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 juzu.impl.bridge.spi.portlet;
import juzu.PropertyMap;
import juzu.PropertyType;
import juzu.Response;
import juzu.Scope;
import juzu.asset.AssetLocation;
import juzu.impl.asset.Asset;
import juzu.impl.bridge.Bridge;
import juzu.impl.bridge.spi.servlet.ServletScopedContext;
import juzu.impl.common.JUL;
import juzu.impl.common.Logger;
import juzu.impl.common.RunMode;
import juzu.impl.inject.spi.InjectorProvider;
import juzu.impl.request.ControllerHandler;
import juzu.io.UndeclaredIOException;
import juzu.request.ClientContext;
import juzu.request.RequestParameter;
import juzu.request.ResponseParameter;
import juzu.impl.bridge.spi.DispatchBridge;
import juzu.impl.common.MimeType;
import juzu.impl.common.MethodHandle;
import juzu.impl.common.Tools;
import juzu.impl.plugin.controller.ControllerService;
import juzu.impl.plugin.controller.ControllerResolver;
import juzu.impl.bridge.spi.ScopedContext;
import juzu.impl.request.Request;
import juzu.impl.bridge.spi.RequestBridge;
import juzu.bridge.portlet.JuzuPortlet;
import juzu.request.ApplicationContext;
import juzu.request.HttpContext;
import juzu.request.Phase;
import juzu.request.SecurityContext;
import juzu.request.UserContext;
import juzu.request.WindowContext;
import javax.portlet.BaseURL;
import javax.portlet.MimeResponse;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletMode;
import javax.portlet.PortletModeException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.PortletSession;
import javax.portlet.PortletURL;
import javax.portlet.WindowState;
import javax.portlet.WindowStateException;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;
/** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */
public abstract class PortletRequestBridge<Rq extends PortletRequest, Rs extends PortletResponse> implements RequestBridge {
/** . */
protected final Bridge bridge;
/** . */
protected final Rq req;
/** . */
protected final Rs resp;
/** . */
protected final ControllerHandler<?> target;
/** . */
protected final Map<String ,RequestParameter> requestParameters;
/** . */
protected final PortletHttpContext httpContext;
/** . */
protected final PortletSecurityContext securityContext;
/** . */
protected final PortletWindowContext windowContext;
/** . */
protected final PortletUserContext userContext;
/** . */
protected final PortletApplicationContext applicationContext;
/** . */
private final Phase phase;
/** . */
protected Request request;
/** . */
protected Response response;
PortletRequestBridge(Bridge bridge, Phase phase, Rq req, Rs resp, PortletConfig config) {
String methodId = null;
Map<String, String[]> parameters = new HashMap<String, String[]>(req.getParameterMap());
Map<String ,RequestParameter> requestParameters = Collections.emptyMap();
for (Iterator<Map.Entry<String, String[]>> i = parameters.entrySet().iterator();i.hasNext();) {
Map.Entry<String, String[]> parameter = i.next();
String name = parameter.getKey();
if (name.startsWith("juzu.")) {
if (name.equals("juzu.op")) {
methodId = parameter.getValue()[0];
}
i.remove();
} else {
if (requestParameters.isEmpty()) {
requestParameters = new HashMap<String, RequestParameter>();
}
requestParameters.put(name, RequestParameter.create(parameter));
}
}
//
ControllerResolver<ControllerHandler> resolver = bridge.getApplication().resolveBean(ControllerService.class).getResolver();
ControllerHandler<?> target;
if (methodId != null) {
target = resolver.resolveMethod(phase, methodId, parameters.keySet());
} else {
target = resolver.resolve(phase, parameters.keySet());
}
//
this.bridge = bridge;
this.req = req;
this.resp = resp;
this.target = target;
this.httpContext = new PortletHttpContext(req);
this.securityContext = new PortletSecurityContext(req);
this.windowContext = new PortletWindowContext(this);
this.userContext = new PortletUserContext(req);
this.applicationContext = new PortletApplicationContext(config);
this.requestParameters = requestParameters;
this.phase = phase;
}
PortletRequestBridge(Bridge bridge, Phase phase, Rq req, Rs resp, PortletConfig config, ControllerHandler<?> target, Map<String, String[]> parameters) {
//
Map<String, RequestParameter> requestParameters = Collections.emptyMap();
for (Map.Entry<String, String[]> parameter : parameters.entrySet()) {
if (requestParameters.isEmpty()) {
requestParameters = new HashMap<String, RequestParameter>();
}
RequestParameter.create(parameter).appendTo(requestParameters);
}
//
this.phase = phase;
this.bridge = bridge;
this.req = req;
this.resp = resp;
this.target = target;
this.requestParameters = requestParameters;
this.httpContext = new PortletHttpContext(req);
this.securityContext = new PortletSecurityContext(req);
this.windowContext = new PortletWindowContext(this);
this.userContext = new PortletUserContext(req);
this.applicationContext = new PortletApplicationContext(config);
}
@Override
public Charset getDefaultRequestEncoding() {
return bridge.getConfig().requestEncoding;
}
@Override
public ClientContext getClientContext() {
return null;
}
@Override
public RunMode getRunMode() {
return bridge.getRunMode();
}
public final Logger getLogger(String name) {
return JUL.getLogger(name);
}
public final Phase getPhase() {
return phase;
}
public Map<String, RequestParameter> getRequestArguments() {
return requestParameters;
}
public <T> T getProperty(PropertyType<T> propertyType) {
Object propertyValue = null;
if (JuzuPortlet.PORTLET_MODE.equals(propertyType)) {
propertyValue = req.getPortletMode();
}
else if (JuzuPortlet.WINDOW_STATE.equals(propertyType)) {
propertyValue = req.getWindowState();
}
if (RunMode.PROPERTY.equals(propertyType)) {
propertyValue = bridge.getRunMode();
} else if (InjectorProvider.PROPERTY.equals(propertyType)) {
return propertyType.cast(bridge.getConfig().injectorProvider);
}
return propertyValue == null ? null : propertyType.cast(propertyValue);
}
public MethodHandle getTarget() {
return target.getHandle();
}
public final HttpContext getHttpContext() {
return httpContext;
}
public final SecurityContext getSecurityContext() {
return securityContext;
}
public final WindowContext getWindowContext() {
return windowContext;
}
public final UserContext getUserContext() {
return userContext;
}
public final ApplicationContext getApplicationContext() {
return applicationContext;
}
public void execute(Runnable runnable) throws RejectedExecutionException {
throw new RejectedExecutionException();
}
public void close() {
}
public final ScopedContext getScopedContext(Scope scope, boolean create) {
Logger log = bridge.context.getLogger(ServletScopedContext.class.getName());
ScopedContext context;
switch (scope) {
case REQUEST:
context = (ScopedContext)req.getAttribute("juzu.request_scope");
if (context == null && create) {
req.setAttribute("juzu.request_scope", context = new ServletScopedContext(log));
}
break;
case FLASH:
PortletSession session = req.getPortletSession(create);
if (session != null) {
context = (ScopedContext)session.getAttribute("juzu.flash_scope");
if (context == null && create) {
session.setAttribute("juzu.flash_scope", context = new ServletScopedContext(log));
}
} else {
context = null;
}
break;
case SESSION:
session = req.getPortletSession(create);
if (session != null) {
context = (ScopedContext)session.getAttribute("juzu.session_scope");
if (context == null && create) {
session.setAttribute("juzu.session_scope", context = new ServletScopedContext(log));
}
} else {
context = null;
}
break;
default:
throw new UnsupportedOperationException("Unsupported scope " + scope);
}
return context;
}
public final void setResponse(Response response) throws IllegalArgumentException, IOException {
this.response = response;
}
public void begin(Request request) {
this.request = request;
}
public void end() {
this.request = null;
}
public void invoke() throws Exception {
try {
bridge.getApplication().resolveBean(ControllerService.class).invoke(this);
} finally {
Tools.safeClose(this);
}
}
public abstract void send() throws IOException, PortletException;
private <T> String _checkPropertyValidity(Phase phase, PropertyType<T> propertyType, T propertyValue) {
if (propertyType == JuzuPortlet.PORTLET_MODE) {
if (phase == Phase.RESOURCE) {
return "Resource URL don't have portlet modes";
}
PortletMode portletMode = (PortletMode)propertyValue;
for (Enumeration<PortletMode> e = req.getPortalContext().getSupportedPortletModes();e.hasMoreElements();) {
PortletMode next = e.nextElement();
if (next.equals(portletMode)) {
return null;
}
}
return "Unsupported portlet mode " + portletMode;
}
else if (propertyType == JuzuPortlet.WINDOW_STATE) {
if (phase == Phase.RESOURCE) {
return "Resource URL don't have windwo state";
}
WindowState windowState = (WindowState)propertyValue;
for (Enumeration<WindowState> e = req.getPortalContext().getSupportedWindowStates();e.hasMoreElements();) {
WindowState next = e.nextElement();
if (next.equals(windowState)) {
return null;
}
}
return "Unsupported window state " + windowState;
}
else {
// For now we ignore other properties
return null;
}
}
public DispatchBridge createDispatch(final Phase phase, final MethodHandle target, final Map<String, ResponseParameter> parameters) throws NullPointerException, IllegalArgumentException {
return new DispatchBridge() {
public MethodHandle getTarget() {
return target;
}
public Map<String, ResponseParameter> getParameters() {
return parameters;
}
public <T> String checkPropertyValidity(PropertyType<T> propertyType, T propertyValue) {
return _checkPropertyValidity(phase, propertyType, propertyValue);
}
public void renderURL(PropertyMap properties, MimeType mimeType, Appendable appendable) throws IOException {
if (resp instanceof MimeResponse) {
MimeResponse mimeResp = (MimeResponse)resp;
//
ControllerHandler handler = bridge.getApplication().resolveBean(ControllerService.class).getDescriptor().getMethodByHandle(target);
//
BaseURL url;
if (handler.getPhase() == Phase.ACTION) {
url = mimeResp.createActionURL();
} else if (handler.getPhase() == Phase.VIEW) {
url = mimeResp.createRenderURL();
} else if (handler.getPhase() == Phase.RESOURCE) {
url = mimeResp.createResourceURL();
} else {
throw new AssertionError();
}
// Set generic parameters
for (ResponseParameter parameter : parameters.values()) {
url.setParameter(parameter.getName(), parameter.toArray());
}
//
boolean escapeXML = false;
if (properties != null) {
Boolean escapeXMLProperty = properties.getValue(PropertyType.ESCAPE_XML);
if (escapeXMLProperty != null && Boolean.TRUE.equals(escapeXMLProperty)) {
escapeXML = true;
}
// Handle portlet mode
PortletMode portletModeProperty = properties.getValue(JuzuPortlet.PORTLET_MODE);
if (portletModeProperty != null) {
if (url instanceof PortletURL) {
try {
((PortletURL)url).setPortletMode(portletModeProperty);
}
catch (PortletModeException e) {
throw new IllegalArgumentException(e);
}
}
else {
throw new IllegalArgumentException();
}
}
// Handle window state
WindowState windowStateProperty = properties.getValue(JuzuPortlet.WINDOW_STATE);
if (windowStateProperty != null) {
if (url instanceof PortletURL) {
try {
((PortletURL)url).setWindowState(windowStateProperty);
}
catch (WindowStateException e) {
throw new IllegalArgumentException(e);
}
}
else {
throw new IllegalArgumentException();
}
}
// Set method id
url.setParameter("juzu.op", handler.getId());
}
//
if (escapeXML) {
StringWriter writer = new StringWriter();
url.write(writer, true);
appendable.append(writer.toString());
}
else {
appendable.append(url.toString());
}
} else {
throw new IllegalStateException("Cannot render an URL during phase " + phase);
}
}
};
}
public PortletRequest getPortletRequest() {
return req;
}
public PortletResponse getPortletResponse() {
return resp;
}
public void renderAssetURL(AssetLocation location, String uri, Appendable appendable) throws NullPointerException, UnsupportedOperationException, IOException {
switch (location) {
case SERVER:
if (!uri.startsWith("/")) {
appendable.append(req.getContextPath());
appendable.append('/');
}
appendable.append(uri);
break;
case APPLICATION:
appendable.append(req.getContextPath()).append("/assets");
if (!uri.startsWith("/")) {
appendable.append('/');
appendable.append(bridge.getApplication().getDescriptor().getPackageName().replace('.', '/'));
appendable.append("/assets/");
}
appendable.append(uri);
break;
case URL:
appendable.append(uri);
break;
default:
throw new AssertionError();
}
}
String getAssetURL(Asset asset) {
StringBuilder sb = new StringBuilder();
try {
renderAssetURL(asset.getLocation(), asset.resolveURI(bridge.getRunMode().getMinifyAssets()), sb);
}
catch (IOException e) {
// Should not happen
throw new UndeclaredIOException(e);
}
return sb.toString();
}
}