/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.capedwarf.tasks;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.jms.BytesMessage;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.servlet.ReadListener;
import javax.servlet.ServletContext;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.appengine.api.NamespaceManager;
import org.jboss.capedwarf.common.config.CapedwarfEnvironment;
import org.jboss.capedwarf.common.jms.ServletExecutorProducer;
import org.jboss.capedwarf.shared.compatibility.Compatibility;
import org.jboss.capedwarf.shared.components.AppIdFactory;
import org.jboss.capedwarf.shared.components.SimpleAppIdFactory;
import org.jboss.capedwarf.shared.components.SimpleKey;
import org.jboss.capedwarf.shared.config.QueueXml;
import org.jboss.capedwarf.shared.jms.AbstractServletRequestCreator;
import org.jboss.capedwarf.shared.jms.MessageConstants;
import org.jboss.capedwarf.shared.servlet.AbstractHttpServletRequest;
/**
* Tasks servlet request creator.
*
* @author <a href="mailto:ales.justin@jboss.org">Ales Justin</a>
* @author <a href="mailto:mluksa@redhat.com">Marko Luksa</a>
*/
public class TasksServletRequestCreator extends AbstractServletRequestCreator {
private static final String COPY = "_copy";
static final String DELIMITER = "||";
static final String METHOD = "task_option_method";
static final String HEADERS = "task_option_headers_";
static final String PARAMS = "task_option_params_";
private static final String JMSX_DELIVERY_COUNT = "JMSXDeliveryCount";
private static final String REGEX_SAFE_DELIMITER = Pattern.quote(DELIMITER);
public HttpServletRequest createServletRequest(ServletContext context, Message message) throws Exception {
final RolesHttpServletRequest request;
if (message instanceof BytesMessage) {
request = new BytesServletRequest(context, (BytesMessage) message);
} else {
request = new TasksServletRequest(context);
}
String path = ServletExecutorProducer.getString(message, MessageConstants.PATH);
applyPaths(context, request, path);
request.setMethod(message.getStringProperty(METHOD));
request.addHeaders(get(message, HEADERS, false));
request.addParameters(get(message, PARAMS, true));
int deliveryCount = message.getIntProperty(JMSX_DELIVERY_COUNT);
String executionCount = String.valueOf(deliveryCount - 1);
request.addHeader(TasksMessageCreator.TASK_EXECUTION_COUNT, executionCount);
request.addHeader(TasksMessageCreator.TASK_RETRY_COUNT, executionCount);
Object eta = request.getHeader(TasksMessageCreator.TASK_ETA);
if (eta == null) {
request.addHeader(TasksMessageCreator.TASK_ETA, String.valueOf(System.currentTimeMillis()));
}
return request;
}
public void prepare(HttpServletRequest request, String appId, String module) {
AppIdFactory.setCurrentFactory(new SimpleAppIdFactory(appId, module));
try {
CapedwarfEnvironment.createThreadLocalInstance();
try {
String namespace = request.getHeader(TasksMessageCreator.CURRENT_NAMESPACE);
NamespaceManager.set(namespace);
handleRoles(request, appId, module);
} catch (Throwable t) {
CapedwarfEnvironment.clearThreadLocalInstance();
throw t;
}
} catch (Throwable t) {
AppIdFactory.resetCurrentFactory();
throw new RuntimeException(t);
}
}
public void finish() {
try {
CapedwarfEnvironment.clearThreadLocalInstance();
} finally {
AppIdFactory.resetCurrentFactory();
}
}
@Override
public boolean isValid(HttpServletRequest request, HttpServletResponse response) {
boolean result = super.isValid(request, response);
if (result == false) {
String queueName = request.getHeader(TasksMessageCreator.QUEUE_NAME_HEADER);
result = QueueXml.INTERNAL.equals(queueName) && isStatusInRange(response, 403, 404);
}
return result;
}
private void handleRoles(HttpServletRequest request, String appId, String module) {
RolesHttpServletRequest rhsr = RolesHttpServletRequest.class.cast(request);
Compatibility instance = Compatibility.getInstance(new SimpleKey<Compatibility>(appId, module, Compatibility.class));
String roles = (String) instance.toObject(Compatibility.Feature.TASKQUEUE_ROLES);
for (String role : roles.split(",")) {
rhsr.doAddRole(role);
}
}
private static class RolesHttpServletRequest extends AbstractHttpServletRequest {
private RolesHttpServletRequest(ServletContext context) {
super(context);
}
protected void doAddRole(String role) {
addRole(role);
}
}
private static class TasksServletRequest extends RolesHttpServletRequest {
private TasksServletRequest(ServletContext context) {
super(context);
}
}
private static class BytesServletRequest extends RolesHttpServletRequest {
private final BytesMessage msg;
private final byte[] buf = new byte[1];
private BytesServletRequest(ServletContext context, BytesMessage msg) {
super(context);
this.msg = msg;
}
@Override
public ServletInputStream getInputStream() throws IOException {
return new ServletInputStream() {
public int read() throws IOException {
try {
final int rc = msg.readBytes(buf, 1);
return (rc != -1) ? (buf[0] & 0xFF) : -1;
} catch (JMSException e) {
throw new IOException(e);
}
}
@Override
public void close() throws IOException {
reset(); // reset on close, so it can be reused
}
@Override
public synchronized void reset() throws IOException {
try {
msg.reset();
} catch (JMSException e) {
throw new IOException(e);
}
}
public boolean isFinished() {
return false; // TODO
}
public boolean isReady() {
return false; // TODO
}
public void setReadListener(ReadListener readListener) {
}
};
}
}
static void put(final Message msg, final String prefix, final Map<String, String> map) throws JMSException {
int offset = 0;
for (Map.Entry<String, String> entry : map.entrySet()) {
put(msg, prefix, entry.getKey(), entry.getValue(), offset++);
}
}
private static void put(final Message msg, final String prefix, final String key, final String value, final int offset) throws JMSException {
msg.setStringProperty(prefix + offset, key);
msg.setStringProperty(prefix + offset + COPY, value);
}
static Map<String, Set<String>> get(Message msg, String prefix, boolean urlDecode) throws JMSException {
Map<String, Set<String>> map = new HashMap<String, Set<String>>();
Enumeration names = msg.getPropertyNames();
while (names.hasMoreElements()) {
String name = names.nextElement().toString();
String value = msg.getStringProperty(name);
if (name.startsWith(prefix) && name.endsWith(COPY) == false) {
String property = msg.getStringProperty(name + COPY);
if (property != null) {
String[] values = property.split(REGEX_SAFE_DELIMITER);
if (urlDecode) {
urlDecode(values);
}
map.put(value, new LinkedHashSet<String>(Arrays.asList(values)));
}
}
}
return map;
}
private static void urlDecode(String[] values) throws JMSException {
try {
for (int i = 0; i < values.length; i++) {
values[i] = URLDecoder.decode(values[i], "UTF-8");
}
} catch (UnsupportedEncodingException e) {
throw new JMSException("UTF-8 not supported on this platform");
}
}
}