summaryrefslogtreecommitdiff
path: root/bkucommon/src/main/java/at/gv/egiz/bku/binding
diff options
context:
space:
mode:
Diffstat (limited to 'bkucommon/src/main/java/at/gv/egiz/bku/binding')
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/AbstractBindingProcessor.java89
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessor.java78
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManager.java107
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManagerImpl.java330
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrl.java111
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnection.java82
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionImpl.java539
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionSPI.java64
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlResponse.java98
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/ExpiryRemover.java67
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/FixedFormParameters.java28
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameter.java39
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterImpl.java93
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterStore.java146
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessor.java844
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/HttpUtil.java79
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/Id.java31
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/IdFactory.java106
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/IdImpl.java83
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoder.java41
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoderFactory.java89
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/MultiPartFormDataInputDecoder.java133
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/ProcessingContext.java59
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/RemovalStrategy.java26
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/SLCommandInvokerImpl.java95
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputDecoder.java58
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputIterator.java386
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/InputStreamPartSource.java61
-rw-r--r--bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/SLResultPart.java71
29 files changed, 4033 insertions, 0 deletions
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/AbstractBindingProcessor.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/AbstractBindingProcessor.java
new file mode 100644
index 00000000..23f62134
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/AbstractBindingProcessor.java
@@ -0,0 +1,89 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import at.gv.egiz.bku.conf.Configuration;
+import java.io.InputStream;
+import java.util.Date;
+
+import at.gv.egiz.bku.slcommands.SLCommandInvoker;
+import at.gv.egiz.stal.STAL;
+
+public abstract class AbstractBindingProcessor implements BindingProcessor {
+ protected Id id;
+ protected Configuration config;
+ protected STAL stal;
+ protected SLCommandInvoker commandInvoker;
+ protected long lastAccessedTime = System.currentTimeMillis();
+
+ public AbstractBindingProcessor(String idString) {
+ this.id = IdFactory.getInstance().createId(idString);
+ }
+
+ /**
+ * @see java.lang.Thread#run()
+ */
+ public abstract void run();
+
+ /**
+ * The caller is advised to check the result in case an error occurred.
+ *
+ * @see #getResult()
+ */
+ public abstract void consumeRequestStream(InputStream aIs);
+
+ public Id getId() {
+ return id;
+ }
+
+ public STAL getSTAL() {
+ return stal;
+ }
+
+ public SLCommandInvoker getCommandInvoker() {
+ return commandInvoker;
+ }
+
+ public void updateLastAccessTime() {
+ lastAccessedTime = System.currentTimeMillis();
+ }
+
+ public Date getLastAccessTime() {
+ return new Date(lastAccessedTime);
+ }
+
+ /**
+ * To be called after object creation.
+ *
+ * @param aStal
+ * must not be null
+ * @param aCommandInvoker
+ * must not be null
+ */
+ public void init(STAL aStal, SLCommandInvoker aCommandInvoker, Configuration conf) {
+ if (aStal == null) {
+ throw new NullPointerException("STAL must not be set to null");
+ }
+ if (aCommandInvoker == null) {
+ throw new NullPointerException("Commandinvoker must not be set to null");
+ }
+ config = conf;
+ stal = aStal;
+ commandInvoker = aCommandInvoker;
+ Thread.currentThread().setName("BPID#"+getId().toString());
+ }
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessor.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessor.java
new file mode 100644
index 00000000..0d978992
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessor.java
@@ -0,0 +1,78 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import at.gv.egiz.bku.conf.Configuration;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.Locale;
+
+import at.gv.egiz.bku.slcommands.SLCommandInvoker;
+import at.gv.egiz.stal.STAL;
+
+/**
+ * Represents an single instance of a SL HTTP binding.
+ *
+ * @author wbauer
+ *
+ */
+public interface BindingProcessor extends Runnable {
+
+ /**
+ * The stream must be read completely within this method.
+ *
+ * The caller is advised to check the result in case an error occurred.
+ *
+ * @see #getResult()
+ */
+ public void consumeRequestStream(InputStream aIs);
+
+ /**
+ * The unique Id of this http binding instance.
+ * @return
+ */
+ public Id getId();
+
+ /**
+ * The used underlying STAL instance
+ * @return
+ */
+ public STAL getSTAL();
+
+ public SLCommandInvoker getCommandInvoker();
+
+ public Date getLastAccessTime();
+
+ public void updateLastAccessTime();
+
+ public String getResultContentType();
+
+ public void writeResultTo(OutputStream os, String encoding) throws IOException;
+
+ public void init(STAL aStal, SLCommandInvoker aCommandInvoker, Configuration config);
+
+ /**
+ * Sets the preferred locale for userinteraction.
+ * If the locale is not set the default locale will be used.
+ * @param locale must not be null.
+ */
+ public void setLocale(Locale locale);
+
+ public boolean isFinished();
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManager.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManager.java
new file mode 100644
index 00000000..9cad95a4
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManager.java
@@ -0,0 +1,107 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.net.MalformedURLException;
+import java.util.Locale;
+import java.util.Set;
+
+import at.gv.egiz.bku.slcommands.SLCommandInvoker;
+import at.gv.egiz.stal.STALFactory;
+
+/**
+ * Central player that handles the protocol binding.
+ *
+ * @author wbauer
+ *
+ */
+public interface BindingProcessorManager {
+
+ /**
+ * FactoryMethod creating a new BindingProcessor object.
+ * The created binding processor must be passed to the process method to execute.
+ *
+ * @param urlString
+ * the source url
+ * @param aSessionId
+ * optional an external sessionId (e.g. http session) could be
+ * provided. This parameter may be null.
+ * @param locale the locale used for user interaction, may be null
+ */
+ public BindingProcessor createBindingProcessor(String urlString,
+ String aSessionId, Locale locale) throws MalformedURLException;
+
+ /**
+ * FactoryMethod creating a new BindingProcessor object.
+ * The created binding processor must be passed to the process method to execute.
+ *
+ * @param protcol
+ * the source url
+ * @param aSessionId
+ * optional an external sessionId (e.g. http session) could be
+ * provided. This parameter may be null.
+ */
+ public BindingProcessor createBindingProcessor(String urlString,
+ String aSessionId) throws MalformedURLException;
+
+
+ /**
+ * Gets the binding processor with a certain id. The binding processor must be passed to the
+ * process method before it is managed and thus returned by this method.
+ * @param aId must not be null
+ * @return null if the binding processor was not "processed" before.
+ */
+ public BindingProcessor getBindingProcessor(Id aId);
+
+ /**
+ * Sets the STAL factory that is used for creating STAL objects that are used by BindingProcessor objects.
+ * For each new BindingProcessor a new STAL object is created.
+ * @param aStalFactory the factory to be used. Must not be null.
+ */
+ public void setSTALFactory(STALFactory aStalFactory);
+
+ /**
+ * Sets the invoker to be used.
+ * @param invoker
+ */
+ public void setSLCommandInvoker(SLCommandInvoker invoker);
+
+ /**
+ * Creates a processing context,
+ * schedules the provided binding processor for processing and
+ * immediately returns the context.
+ *
+ * @param aBindingProcessor
+ */
+ public ProcessingContext process(BindingProcessor aBindingProcessor);
+
+ /**
+ * Removes a formerly added (by calling the process method) binding processor.
+ * @param bindingProcessor must not be null
+ */
+ public void removeBindingProcessor(Id sessionId);
+
+ /**
+ * A set of all managed binding processors.
+ * @return
+ */
+ public Set<Id> getManagedIds();
+
+ public void shutdown();
+
+ public void shutdownNow();
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManagerImpl.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManagerImpl.java
new file mode 100644
index 00000000..bf9a63e2
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManagerImpl.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * 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 at.gv.egiz.bku.binding;
+
+import at.gv.egiz.bku.conf.Configuration;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.bku.slcommands.SLCommandInvoker;
+import at.gv.egiz.bku.slexceptions.SLRuntimeException;
+import at.gv.egiz.bku.utils.binding.Protocol;
+import at.gv.egiz.stal.STAL;
+import at.gv.egiz.stal.STALFactory;
+
+/**
+ * This class maintains all active BindingProcessor Objects. Currently, only
+ * HTTPBinding is supported.
+ */
+public class BindingProcessorManagerImpl implements BindingProcessorManager {
+
+ public final static Protocol[] SUPPORTED_PROTOCOLS = { Protocol.HTTP,
+ Protocol.HTTPS };
+
+ private static Log log = LogFactory.getLog(BindingProcessorManagerImpl.class);
+
+ /** spring injected config
+ * Passed to created bindingprocessors, to replace their configuration */
+ protected Configuration config;
+
+ protected STALFactory stalFactory;
+ protected SLCommandInvoker commandInvokerClass;
+
+ private RemovalStrategy removalStrategy;
+ private ExecutorService executorService;
+ private Map<Id, ProcessingContext> contextMap = Collections.synchronizedMap(new HashMap<Id, ProcessingContext>());
+// private Map<Id, MapEntityWrapper> bindingProcessorMap = Collections
+// .synchronizedMap(new HashMap<Id, MapEntityWrapper>());
+
+ /**
+ * Container to hold a Future and Bindingprocessor object as map value.
+ *
+ * @author wbauer
+ * @see BindingProcessorManagerImpl#bindingProcessorMap
+ */
+// static class MapEntityWrapper {
+// private Future<?> future;
+// private BindingProcessor bindingProcessor;
+//
+// public MapEntityWrapper(Future<?> future, BindingProcessor bindingProcessor) {
+// if ((bindingProcessor == null) || (future == null)) {
+// throw new NullPointerException("Argument must not be null");
+// }
+// this.bindingProcessor = bindingProcessor;
+// this.future = future;
+// }
+//
+// public Future<?> getFuture() {
+// return future;
+// }
+//
+// public BindingProcessor getBindingProcessor() {
+// return bindingProcessor;
+// }
+//
+// public int hashCode() {
+// return bindingProcessor.getId().hashCode();
+// }
+//
+// public boolean equals(Object other) {
+// if (other instanceof MapEntityWrapper) {
+// MapEntityWrapper o = (MapEntityWrapper) other;
+// return (o.bindingProcessor.getId().equals(bindingProcessor.getId()));
+// } else {
+// return false;
+// }
+// }
+// }
+
+ /**
+ *
+ * @param fab
+ * must not be null
+ * @param ci
+ * must not be null (prototype to generate new instances)
+ */
+ public BindingProcessorManagerImpl(STALFactory fab, SLCommandInvoker ci, Configuration conf) {
+ if (fab == null) {
+ throw new NullPointerException("STALFactory must not be null");
+ }
+ stalFactory = fab;
+ if (ci == null) {
+ throw new NullPointerException("SLCommandInvoker must not be null");
+ }
+ commandInvokerClass = ci;
+ config = conf;
+ executorService = Executors.newCachedThreadPool();
+ }
+
+ /**
+ *
+ * @return the STALFactory currently used.
+ */
+ public STALFactory getStalFactory() {
+ return stalFactory;
+ }
+
+ /**
+ * Sets the STALFactory to be used.
+ *
+ * @param stalFactory
+ */
+ public void setStalFactory(STALFactory stalFactory) {
+ this.stalFactory = stalFactory;
+ }
+
+ /**
+ * Could be used to setup a new executor service during application stratup.
+ *
+ * @param executorService
+ */
+ public void setExecutorService(ExecutorService executorService) {
+ this.executorService = executorService;
+ }
+
+ public void setRemovalStrategy(RemovalStrategy aStrategy) {
+ removalStrategy = aStrategy;
+ }
+
+ public RemovalStrategy getRemovlaStrategy() {
+ return removalStrategy;
+ }
+
+ public void shutdown() {
+ log.info("Shutting down the BindingProcessorManager");
+ executorService.shutdown();
+ }
+
+ public void shutdownNow() {
+ log.info("Shutting down the BindingProcessorManager NOW!");
+ executorService.shutdownNow();
+ log.debug("Number of binding contexts currently managed: "
+ + contextMap.size());
+// + bindingProcessorMap.size());
+ if (log.isDebugEnabled()) {
+ for (ProcessingContext ctx : contextMap.values()) {
+ Id bpId = ctx.getBindingProcessor().getId();
+ Future future = ctx.getFuture();
+ log.debug(bpId + " cancelled: " + future.isCancelled());
+ log.debug(bpId + " done: " + future.isDone());
+ }
+// for (Iterator<MapEntityWrapper> it = bindingProcessorMap.values()
+// .iterator(); it.hasNext();) {
+// MapEntityWrapper entry = it.next();
+// log.debug(entry.getBindingProcessor().getId() + ": isDone: "
+// + entry.getFuture().isDone());
+// log.debug(entry.getBindingProcessor().getId() + ": isCanceled: "
+// + entry.getFuture().isCancelled());
+// }
+ }
+ }
+
+ /**
+ * Uses the default locale
+ */
+ public BindingProcessor createBindingProcessor(String srcUrl,
+ String aSessionId) throws MalformedURLException {
+ return createBindingProcessor(srcUrl, aSessionId, null);
+ }
+
+ /**
+ * FactoryMethod creating a new BindingProcessor object.
+ *
+ * @param protocol
+ * must not be null
+ * @throws MalformedURLException
+ */
+ public BindingProcessor createBindingProcessor(String srcUrl,
+ String aSessionId, Locale locale) throws MalformedURLException {
+ URL url = new URL(srcUrl);
+ String low = url.getProtocol().toLowerCase();
+ Protocol proto = null;
+ for (int i = 0; i < SUPPORTED_PROTOCOLS.length; i++) {
+ if (SUPPORTED_PROTOCOLS[i].toString().equals(low)) {
+ proto = SUPPORTED_PROTOCOLS[i];
+ break;
+ }
+ }
+ if (proto == null) {
+ throw new UnsupportedOperationException();
+ }
+ BindingProcessor bindingProcessor = new HTTPBindingProcessor(aSessionId,
+ commandInvokerClass.newInstance(), url);
+ stalFactory.setLocale(locale);
+ STAL stal = stalFactory.createSTAL();
+ bindingProcessor.init(stal, commandInvokerClass.newInstance(), config);
+ if (locale != null) {
+ bindingProcessor.setLocale(locale);
+// stal.setLocale(locale);
+ }
+ return bindingProcessor;
+ }
+
+ /**
+ * @return the bindingprocessor object for this id or null if no
+ * bindingprocessor was found.
+ */
+ @Override
+ public BindingProcessor getBindingProcessor(Id aId) {
+// if (bindingProcessorMap.get(aId) != null) {
+// return bindingProcessorMap.get(aId).getBindingProcessor();
+ ProcessingContext ctx = contextMap.get(aId);
+ if (ctx != null) {
+ return ctx.getBindingProcessor();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ *
+ */
+ @Override
+ public void setSTALFactory(STALFactory aStalFactory) {
+ if (aStalFactory == null) {
+ throw new NullPointerException("Cannot set STALFactory to null");
+ }
+ stalFactory = aStalFactory;
+ }
+
+ /**
+ * Causes the BindingProcessorManager to manage the provided BindingProcessor
+ * Creates a processing context,
+ * schedules the provided binding processor for processing and
+ * immediately returns the context.
+ *
+ * @param aBindingProcessor
+ * must not be null
+ */
+ @Override
+ public ProcessingContext process(BindingProcessor aBindingProcessor) {
+ if (contextMap.containsKey(aBindingProcessor.getId())) {
+// if (bindingProcessorMap.containsKey(aBindingProcessor.getId())) {
+ log.fatal("Clashing ids, cannot process bindingprocessor with id:"
+ + aBindingProcessor.getId());
+ throw new SLRuntimeException(
+ "Clashing ids, cannot process bindingprocessor with id:"
+ + aBindingProcessor.getId());
+ }
+ log.debug("processing bindingprocessor: " + aBindingProcessor.getId());
+ Future<?> f = executorService.submit(aBindingProcessor);
+ ProcessingContext ctx = new ProcessingContext(aBindingProcessor, f);
+ contextMap.put(aBindingProcessor.getId(), ctx);
+// bindingProcessorMap.put(aBindingProcessor.getId(), new MapEntityWrapper(f,
+// aBindingProcessor));
+ return ctx;
+ }
+
+ @Override
+ public void setSLCommandInvoker(SLCommandInvoker invoker) {
+ commandInvokerClass = invoker;
+ }
+
+ @Override
+ public void removeBindingProcessor(Id sessionId) {
+ log.debug("Removing binding processor: " + sessionId);
+ ProcessingContext ctx = contextMap.get(sessionId);
+ if (ctx == null) {
+ log.warn("no processing context to remove for session " + sessionId);
+ return;
+ }
+ Future f = ctx.getFuture();
+
+// MapEntityWrapper wrapper = bindingProcessorMap.get(sessionId);
+// if (wrapper == null) {
+// return;
+// }
+// Future<?> f = wrapper.getFuture();
+
+ if (!f.isDone()) {
+ log.trace("canceling " + sessionId);
+ f.cancel(true);
+ }
+ contextMap.remove(sessionId);
+// bindingProcessorMap.remove(sessionId);
+ }
+
+ @Override
+ public Set<Id> getManagedIds() {
+ Set<Id> result = new HashSet<Id>();
+ synchronized (contextMap) {
+ for (Id id : contextMap.keySet()) {
+ result.add(id);
+ }
+ }
+// synchronized (bindingProcessorMap) {
+// for (Iterator<Id> it = bindingProcessorMap.keySet().iterator(); it
+// .hasNext();) {
+// result.add(it.next());
+// }
+// }
+ return result;
+ }
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrl.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrl.java
new file mode 100644
index 00000000..d3945253
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrl.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * 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 at.gv.egiz.bku.binding;
+
+import at.gv.egiz.bku.conf.Configurator;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.bku.slexceptions.SLRuntimeException;
+
+/**
+ * Used to handle DataUrl connections as specified in the CCE's HTTP protocol binding.
+ *
+ */
+public class DataUrl {
+
+ private static Log log = LogFactory.getLog(DataUrl.class);
+ private static DataUrlConnectionSPI connection;
+ private static Properties configuration;
+ private static SSLSocketFactory sslSocketFactory;
+ private static HostnameVerifier hostNameVerifier;
+ private URL url;
+
+ /** spring injected config, to replace configuration */
+ //private Configuration config;
+
+ /**
+ * Sets the default DataUrlConnection implementation
+ * @param aClass must not be null
+ */
+ static void setDataUrlConnectionImpl(DataUrlConnectionSPI conn) {
+ if (conn != null) {
+ connection = conn;
+ }
+ }
+
+ public DataUrl(String aUrlString) throws MalformedURLException {
+ url = new URL(aUrlString);
+ if (connection == null) {
+ log.debug("Using default DataURLConnection class");
+ connection = new DataUrlConnectionImpl();
+ }
+ connection.setConfiguration(configuration);
+ connection.setSSLSocketFactory(sslSocketFactory);
+ connection.setHostnameVerifier(hostNameVerifier);
+ }
+
+ public DataUrlConnection openConnection() {
+ try {
+ log.debug("Opening dataurl connection");
+ DataUrlConnectionSPI retVal = connection.newInstance();
+ retVal.init(url);
+ return retVal;
+ } catch (Exception e) {
+ log.error(e);
+ throw new SLRuntimeException("Cannot instantiate a dataurlconnection:", e);
+ }
+ }
+
+
+ /**
+ * set configuration for all subsequently instantiated DataURL objects
+ * @param props
+ */
+ public static void setConfiguration(Properties props) {
+ configuration = props;
+ if (configuration != null) {
+ String className = configuration.getProperty(Configurator.DATAURLCONNECTION_CONFIG_P);
+ if (className != null) {
+ log.warn("Set DataURLConnection class not supported!");
+ }
+ }
+ }
+
+ /**
+ * set SSLSocketFactory for all subsequently instantiated DataURL objects
+ * @param socketFactory
+ */
+ public static void setSSLSocketFactory(SSLSocketFactory socketFactory) {
+ sslSocketFactory = socketFactory;
+ }
+
+ /**
+ * set HostnameVerifier for all subsequently instantiated DataURL objects
+ * @param hostNameVerifier
+ */
+ public static void setHostNameVerifier(HostnameVerifier hostNameVerifier) {
+ DataUrl.hostNameVerifier = hostNameVerifier;
+ }
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnection.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnection.java
new file mode 100644
index 00000000..384cf71c
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnection.java
@@ -0,0 +1,82 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+
+import at.gv.egiz.bku.slcommands.SLResult;
+
+/**
+ * Transmit a security layer result to DataURL via HTTP POST, encoded as multipart/form-data.
+ * The HTTP header user-agent is set to <em>citizen-card-environment/1.2 BKU2 1.0</em>.
+ * The form-parameter ResponseType is set to <em>HTTP-Security-Layer-RESPONSE</em>.
+ * All other headers/parameters are set by the caller.
+ *
+ * @author clemens
+ */
+public interface DataUrlConnection {
+
+ public static final String FORMPARAM_RESPONSETYPE = "ResponseType";
+ public static final String DEFAULT_RESPONSETYPE = "HTTP-Security-Layer-RESPONSE";
+ public static final String FORMPARAM_XMLRESPONSE = "XMLResponse";
+ public static final String FORMPARAM_BINARYRESPONSE = "BinaryResponse";
+
+ public static final String XML_RESPONSE_ENCODING = "UTF-8";
+
+
+ public String getProtocol();
+
+ public URL getUrl();
+
+ /**
+ * Set a HTTP Header.
+ * @param key
+ * @param value multiple values are assumed to have the correct formatting (comma-separated list)
+ */
+ public void setHTTPHeader(String key, String value);
+
+ /**
+ * Set a form-parameter.
+ * @param name
+ * @param data
+ * @param contentType may be null
+ * @param charSet may be null
+ * @param transferEncoding may be null
+ */
+ public void setHTTPFormParameter(String name, InputStream data, String contentType, String charSet, String transferEncoding);
+
+ /**
+ * @pre httpHeaders != null
+ * @throws java.net.SocketTimeoutException
+ * @throws java.io.IOException
+ */
+ public void connect() throws SocketTimeoutException, IOException;
+
+ public X509Certificate getServerCertificate();
+
+ /**
+ * @pre connection != null
+ * @throws java.io.IOException
+ */
+ public void transmit(SLResult slResult) throws IOException;
+
+ public DataUrlResponse getResponse() throws IOException;
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionImpl.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionImpl.java
new file mode 100644
index 00000000..82c1be53
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionImpl.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * 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 at.gv.egiz.bku.binding;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSocketFactory;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.httpclient.methods.multipart.FilePart;
+import org.apache.commons.httpclient.methods.multipart.Part;
+import org.apache.commons.httpclient.methods.multipart.StringPart;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.bku.binding.multipart.InputStreamPartSource;
+import at.gv.egiz.bku.binding.multipart.SLResultPart;
+import at.gv.egiz.bku.conf.Configurator;
+import at.gv.egiz.bku.slcommands.SLResult;
+import at.gv.egiz.bku.slcommands.SLResult.SLResultType;
+import at.gv.egiz.bku.slexceptions.SLRuntimeException;
+import at.gv.egiz.bku.utils.URLEncodingWriter;
+import at.gv.egiz.bku.utils.binding.Protocol;
+
+/**
+ * An implementation of the DataUrlConnectionSPI that supports
+ * <code>multipart/form-data</code> encoding and
+ * <code>application/x-www-form-urlencoded</code> for compatibility with legacy
+ * systems.
+ *
+ */
+public class DataUrlConnectionImpl implements DataUrlConnectionSPI {
+
+ private final static Log log = LogFactory.getLog(DataUrlConnectionImpl.class);
+
+ public static final byte[] B_DEFAULT_RESPONSETYPE = DEFAULT_RESPONSETYPE.getBytes(Charset.forName("UTF-8"));
+
+ /**
+ * Supported protocols are HTTP and HTTPS.
+ */
+ public final static Protocol[] SUPPORTED_PROTOCOLS = { Protocol.HTTP,
+ Protocol.HTTPS };
+
+ /**
+ * The X509 certificate of the DataURL server.
+ */
+ protected X509Certificate serverCertificate;
+
+ /**
+ * The protocol of the DataURL.
+ */
+ protected Protocol protocol;
+
+ /**
+ * Use <code>application/x-www-form-urlencoded</code> instead of
+ * standard conform <code>application/x-www-form-urlencoded</code>.
+ */
+ protected boolean urlEncoded = true;
+
+ /**
+ * The value of the DataURL.
+ */
+ protected URL url;
+
+ /**
+ * The URLConnection used for communication with the DataURL server.
+ */
+ private HttpURLConnection connection;
+
+ /**
+ * The HTTP request headers.
+ */
+ protected Map<String, String> requestHttpHeaders;
+
+ /**
+ * The HTTP form parameters.
+ */
+ protected ArrayList<HTTPFormParameter> httpFormParameter;
+
+ /**
+ * The boundary for multipart/form-data requests.
+ */
+ protected String boundary;
+
+ /**
+ * The configuration properties.
+ */
+ protected Properties config = null;
+
+ /**
+ * The SSLSocketFactory for HTTPS connections.
+ */
+ protected SSLSocketFactory sslSocketFactory;
+
+ /**
+ * The HostnameVerifier for HTTPS connections.
+ */
+ protected HostnameVerifier hostnameVerifier;
+
+ /**
+ * The response of the DataURL server.
+ */
+ protected DataUrlResponse result;
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.bku.binding.DataUrlConnection#getProtocol()
+ */
+ public String getProtocol() {
+ if (protocol == null) {
+ return null;
+ }
+ return protocol.toString();
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.bku.binding.DataUrlConnection#connect()
+ */
+ public void connect() throws SocketTimeoutException, IOException {
+ connection = (HttpURLConnection) url.openConnection();
+ if (connection instanceof HttpsURLConnection) {
+ log.trace("Detected ssl connection");
+ HttpsURLConnection https = (HttpsURLConnection) connection;
+ if (sslSocketFactory != null) {
+ log.debug("Setting custom ssl socket factory for ssl connection");
+ https.setSSLSocketFactory(sslSocketFactory);
+ } else {
+ log.trace("No custom socket factory set");
+ }
+ if (hostnameVerifier != null) {
+ log.debug("Setting custom hostname verifier");
+ https.setHostnameVerifier(hostnameVerifier);
+ }
+ } else {
+ log.trace("No secure connection with: " + url + " class="
+ + connection.getClass());
+ }
+ connection.setDoOutput(true);
+ // Transfer-Encoding: chunked is problematic ...
+ // e.g. https://issues.apache.org/bugzilla/show_bug.cgi?id=37794
+ // ... therefore disabled.
+ // connection.setChunkedStreamingMode(5*1024);
+ if (urlEncoded) {
+ log.debug("Setting DataURL Content-Type to "
+ + HttpUtil.APPLICATION_URL_ENCODED);
+ connection.addRequestProperty(HttpUtil.HTTP_HEADER_CONTENT_TYPE,
+ HttpUtil.APPLICATION_URL_ENCODED);
+ } else {
+ log.debug("Setting DataURL Content-Type to "
+ + HttpUtil.MULTIPART_FOTMDATA_BOUNDARY);
+ connection.addRequestProperty(HttpUtil.HTTP_HEADER_CONTENT_TYPE,
+ HttpUtil.MULTIPART_FOTMDATA + HttpUtil.SEPERATOR[0]
+ + HttpUtil.MULTIPART_FOTMDATA_BOUNDARY + "=" + boundary);
+ }
+ Set<String> headers = requestHttpHeaders.keySet();
+ Iterator<String> headerIt = headers.iterator();
+ while (headerIt.hasNext()) {
+ String name = headerIt.next();
+ connection.setRequestProperty(name, requestHttpHeaders.get(name));
+ }
+ log.trace("Connecting to: " + url);
+ connection.connect();
+ if (connection instanceof HttpsURLConnection) {
+ HttpsURLConnection ssl = (HttpsURLConnection) connection;
+ X509Certificate[] certs = (X509Certificate[]) ssl.getServerCertificates();
+ if ((certs != null) && (certs.length >= 1)) {
+ log.trace("Server certificate: " + certs[0]);
+ serverCertificate = certs[0];
+ }
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.bku.binding.DataUrlConnection#getServerCertificate()
+ */
+ public X509Certificate getServerCertificate() {
+ return serverCertificate;
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.bku.binding.DataUrlConnection#setHTTPHeader(java.lang.String, java.lang.String)
+ */
+ public void setHTTPHeader(String name, String value) {
+ if (name != null && value != null) {
+ requestHttpHeaders.put(name, value);
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.bku.binding.DataUrlConnection#setHTTPFormParameter(java.lang.String, java.io.InputStream, java.lang.String, java.lang.String, java.lang.String)
+ */
+ public void setHTTPFormParameter(String name, InputStream data,
+ String contentType, String charSet, String transferEncoding) {
+ // if a content type is specified we have to switch to multipart/formdata encoding
+ if (contentType != null && contentType.length() > 0) {
+ urlEncoded = false;
+ }
+ httpFormParameter.add(new HTTPFormParameter(name, data, contentType,
+ charSet, transferEncoding));
+ }
+
+
+
+ /* (non-Javadoc)
+ * @see at.gv.egiz.bku.binding.DataUrlConnection#transmit(at.gv.egiz.bku.slcommands.SLResult)
+ */
+ public void transmit(SLResult slResult) throws IOException {
+ log.trace("Sending data");
+ if (urlEncoded) {
+ //
+ // application/x-www-form-urlencoded (legacy, SL < 1.2)
+ //
+
+ OutputStream os = connection.getOutputStream();
+ OutputStreamWriter streamWriter = new OutputStreamWriter(os, HttpUtil.DEFAULT_CHARSET);
+
+ // ResponseType
+ streamWriter.write(FORMPARAM_RESPONSETYPE);
+ streamWriter.write("=");
+ streamWriter.write(URLEncoder.encode(DEFAULT_RESPONSETYPE, "UTF-8"));
+ streamWriter.write("&");
+
+ // XMLResponse / Binary Response
+ if (slResult.getResultType() == SLResultType.XML) {
+ streamWriter.write(DataUrlConnection.FORMPARAM_XMLRESPONSE);
+ } else {
+ streamWriter.write(DataUrlConnection.FORMPARAM_BINARYRESPONSE);
+ }
+ streamWriter.write("=");
+ streamWriter.flush();
+ URLEncodingWriter urlEnc = new URLEncodingWriter(streamWriter);
+ slResult.writeTo(new StreamResult(urlEnc), false);
+ urlEnc.flush();
+
+ // transfer parameters
+ char[] cbuf = new char[512];
+ int len;
+ for (HTTPFormParameter formParameter : httpFormParameter) {
+ streamWriter.write("&");
+ streamWriter.write(URLEncoder.encode(formParameter.getName(), "UTF-8"));
+ streamWriter.write("=");
+ InputStreamReader reader = new InputStreamReader(formParameter.getData(),
+ (formParameter.getCharSet() != null)
+ ? formParameter.getCharSet()
+ : "UTF-8");
+ // Note, using UTF-8 as fallback for decoding is safe.
+ // If the request was x-www-form-urlencoded,
+ // UTF-8 has been used for encoding of non-ASCII characters.
+ // If the request was multipart/form-data and contains any transfer parameters,
+ // the data URL request is going to be multipart/form-data encoded (see below).
+ while ((len = reader.read(cbuf)) != -1) {
+ urlEnc.write(cbuf, 0, len);
+ }
+ urlEnc.flush();
+ }
+ streamWriter.close();
+
+ } else {
+ //
+ // multipart/form-data (conforming to SL 1.2)
+ //
+
+ ArrayList<Part> parts = new ArrayList<Part>();
+
+ // ResponseType
+ StringPart responseType = new StringPart(FORMPARAM_RESPONSETYPE,
+ DEFAULT_RESPONSETYPE, "UTF-8");
+ responseType.setTransferEncoding(null);
+ parts.add(responseType);
+
+ // XMLResponse / Binary Response
+ SLResultPart slResultPart = new SLResultPart(slResult,
+ XML_RESPONSE_ENCODING);
+ if (slResult.getResultType() == SLResultType.XML) {
+ slResultPart.setTransferEncoding(null);
+ slResultPart.setContentType(slResult.getMimeType());
+ slResultPart.setCharSet(XML_RESPONSE_ENCODING);
+ } else {
+ slResultPart.setTransferEncoding(null);
+ slResultPart.setContentType(slResult.getMimeType());
+ }
+ parts.add(slResultPart);
+
+ // transfer parameters
+ for (HTTPFormParameter formParameter : httpFormParameter) {
+ InputStreamPartSource source = new InputStreamPartSource(null,
+ formParameter.getData());
+ FilePart part = new FilePart(formParameter.getName(), source,
+ formParameter.getContentType(), formParameter.getCharSet());
+ part.setTransferEncoding(formParameter.getTransferEncoding());
+ parts.add(part);
+ }
+
+ OutputStream os = connection.getOutputStream();
+ Part.sendParts(os, parts.toArray(new Part[parts.size()]), boundary.getBytes());
+ os.close();
+
+ }
+
+ // MultipartRequestEntity PostMethod
+ InputStream is = null;
+ try {
+ is = connection.getInputStream();
+ } catch (IOException iox) {
+ log.info(iox);
+ }
+ log.trace("Reading response");
+ result = new DataUrlResponse(url.toString(), connection.getResponseCode(),
+ is);
+ Map<String, String> responseHttpHeaders = new HashMap<String, String>();
+ Map<String, List<String>> httpHeaders = connection.getHeaderFields();
+ for (Iterator<String> keyIt = httpHeaders.keySet().iterator(); keyIt
+ .hasNext();) {
+ String key = keyIt.next();
+ StringBuffer value = new StringBuffer();
+ for (String val : httpHeaders.get(key)) {
+ value.append(val);
+ value.append(HttpUtil.SEPERATOR[0]);
+ }
+ String valString = value.substring(0, value.length() - 1);
+ if ((key != null) && (value.length() > 0)) {
+ responseHttpHeaders.put(key, valString);
+ }
+ }
+ result.setResponseHttpHeaders(responseHttpHeaders);
+ }
+
+ @Override
+ public DataUrlResponse getResponse() throws IOException {
+ return result;
+ }
+
+ /**
+ * inits protocol, url, httpHeaders, formParams
+ *
+ * @param url
+ * must not be null
+ */
+ @Override
+ public void init(URL url) {
+
+ for (int i = 0; i < SUPPORTED_PROTOCOLS.length; i++) {
+ if (SUPPORTED_PROTOCOLS[i].toString().equalsIgnoreCase(url.getProtocol())) {
+ protocol = SUPPORTED_PROTOCOLS[i];
+ break;
+ }
+ }
+ if (protocol == null) {
+ throw new SLRuntimeException("Protocol " + url.getProtocol()
+ + " not supported for data url");
+ }
+ this.url = url;
+ boundary = "--" + IdFactory.getInstance().createId().toString();
+ requestHttpHeaders = new HashMap<String, String>();
+
+ if (config != null) {
+ String version = config.getProperty(Configurator.SIGNATURE_LAYOUT);
+ if ((version != null) && (!"".equals(version.trim()))) {
+ log.debug("setting SignatureLayout header to " + version);
+ requestHttpHeaders.put(Configurator.SIGNATURE_LAYOUT, version);
+ } else {
+ log.debug("do not set SignatureLayout header");
+ }
+ String userAgent = config.getProperty(Configurator.USERAGENT_CONFIG_P, Configurator.USERAGENT_DEFAULT);
+ requestHttpHeaders.put(HttpUtil.HTTP_HEADER_USER_AGENT, userAgent);
+ } else {
+ requestHttpHeaders
+ .put(HttpUtil.HTTP_HEADER_USER_AGENT, Configurator.USERAGENT_DEFAULT);
+
+ }
+
+ httpFormParameter = new ArrayList<HTTPFormParameter>();
+
+ }
+
+ @Override
+ public DataUrlConnectionSPI newInstance() {
+ DataUrlConnectionSPI uc = new DataUrlConnectionImpl();
+ uc.setConfiguration(config);
+ uc.setSSLSocketFactory(sslSocketFactory);
+ uc.setHostnameVerifier(hostnameVerifier);
+ return uc;
+ }
+
+ @Override
+ public URL getUrl() {
+ return url;
+ }
+
+ @Override
+ public void setConfiguration(Properties config) {
+ this.config = config;
+ }
+
+ @Override
+ public void setSSLSocketFactory(SSLSocketFactory socketFactory) {
+ this.sslSocketFactory = socketFactory;
+ }
+
+ @Override
+ public void setHostnameVerifier(HostnameVerifier hostnameVerifier) {
+ this.hostnameVerifier = hostnameVerifier;
+ }
+
+ public class HTTPFormParameter {
+
+ private String name;
+
+ private InputStream data;
+
+ private String contentType;
+
+ private String charSet;
+
+ private String transferEncoding;
+
+ /**
+ * @param name
+ * @param data
+ * @param contentType
+ * @param charSet
+ * @param transferEncoding
+ */
+ public HTTPFormParameter(String name, InputStream data, String contentType,
+ String charSet, String transferEncoding) {
+ super();
+ this.name = name;
+ this.data = data;
+ this.contentType = contentType;
+ this.charSet = charSet;
+ this.transferEncoding = transferEncoding;
+ }
+
+ /**
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * @param name the name to set
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return the data
+ */
+ public InputStream getData() {
+ return data;
+ }
+
+ /**
+ * @param data the data to set
+ */
+ public void setData(InputStream data) {
+ this.data = data;
+ }
+
+ /**
+ * @return the contentType
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /**
+ * @param contentType the contentType to set
+ */
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ /**
+ * @return the charSet
+ */
+ public String getCharSet() {
+ return charSet;
+ }
+
+ /**
+ * @param charSet the charSet to set
+ */
+ public void setCharSet(String charSet) {
+ this.charSet = charSet;
+ }
+
+ /**
+ * @return the transferEncoding
+ */
+ public String getTransferEncoding() {
+ return transferEncoding;
+ }
+
+ /**
+ * @param transferEncoding the transferEncoding to set
+ */
+ public void setTransferEncoding(String transferEncoding) {
+ this.transferEncoding = transferEncoding;
+ }
+
+
+
+ }
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionSPI.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionSPI.java
new file mode 100644
index 00000000..f838b919
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionSPI.java
@@ -0,0 +1,64 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.net.URL;
+import java.util.Properties;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Prototype of a DataurlconnectionSPI
+ * @author wbauer
+ *
+ */
+public interface DataUrlConnectionSPI extends DataUrlConnection {
+
+ /**
+ * Returns a new instance of this class to handle a dataurl.
+ * Called by the factory each time the openConnection method is called.
+ * @return
+ */
+ public DataUrlConnectionSPI newInstance();
+
+ /**
+ * Initializes the DataUrlConnection
+ * @param url
+ */
+ public void init(URL url);
+
+ /**
+ * Sets configuration parameters for this connection
+ * @param config
+ */
+ public void setConfiguration(Properties config);
+
+ /**
+ * Sets the socketfactory to be used for ssl connections.
+ * @param socketFactory if null the socket factory will not be set explicitly
+ */
+ public void setSSLSocketFactory(SSLSocketFactory socketFactory);
+
+ /**
+ * Sets the hostname verifier to be used,
+ * @param hostnameVerifier if null the default hostname verifier will be used
+ */
+ public void setHostnameVerifier(HostnameVerifier hostnameVerifier);
+
+
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlResponse.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlResponse.java
new file mode 100644
index 00000000..b75cb0f3
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlResponse.java
@@ -0,0 +1,98 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.util.Iterator;
+import java.util.Map;
+
+import at.gv.egiz.bku.utils.urldereferencer.StreamData;
+
+/**
+ * The response of a dataurl server.
+ * Additionally holds return code and response headers.
+ */
+public class DataUrlResponse extends StreamData {
+
+ public final static String OK = "<ok/>";
+
+ protected Map<String, String> responseHttpHeaders;
+
+ protected int responseCode = -1;
+
+ public DataUrlResponse(String url, int responseCode, InputStream stream) {
+ super(url, null, new PushbackInputStream(stream, 10));
+ this.responseCode = responseCode;
+ }
+
+ public String getContentType() {
+ if (contentType != null) {
+ return contentType;
+ }
+ if (responseHttpHeaders == null) {
+ return null;
+ }
+ for (Iterator<String> keyIt = responseHttpHeaders.keySet().iterator(); keyIt
+ .hasNext();) {
+ String key = keyIt.next();
+ if (HttpUtil.HTTP_HEADER_CONTENT_TYPE.equalsIgnoreCase(key)) {
+ contentType = responseHttpHeaders.get(key);
+ return contentType;
+ }
+ }
+ return contentType;
+ }
+
+ public void setResponseHttpHeaders(Map<String, String> responseHttpHeaders) {
+ this.responseHttpHeaders = responseHttpHeaders;
+ }
+
+ public Map<String, String> getResponseHeaders() {
+ return responseHttpHeaders;
+ }
+
+ public int getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * Checks if the http response equals "<ok/>"
+ *
+ * @throws IOException
+ */
+ public boolean isHttpResponseXMLOK() throws IOException {
+ String charset = HttpUtil.getCharset(contentType, true);
+ byte[] buffer = new byte[10];
+ int i = 0;
+ int read = 0;
+ while ((i < 10) && (read != -1)) {
+ read = inputStream.read(buffer, i, 10 - i);
+ if (read != -1) {
+ i += read;
+ }
+ }
+ PushbackInputStream pbis = (PushbackInputStream) inputStream;
+ pbis.unread(buffer, 0, i);
+ if (i < 5) {
+ return false;
+ }
+ String ok = new String(buffer, 0, i, charset);
+ return (OK.equals(ok));
+ }
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/ExpiryRemover.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/ExpiryRemover.java
new file mode 100644
index 00000000..d17a27c2
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/ExpiryRemover.java
@@ -0,0 +1,67 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This class can be used to check the BindingProcessorManager for expired entries and remove them.
+ * Should be run periodically.
+ *
+ */
+public class ExpiryRemover implements RemovalStrategy {
+
+ private static Log log = LogFactory.getLog(ExpiryRemover.class);
+
+ protected BindingProcessorManager bindingProcessorManager;
+ // keep max 5 min.
+ protected long maxAcceptedAge = 1000 * 60 * 5;
+
+ @Override
+ public void execute() {
+ log.debug("Triggered Expiry Remover");
+ if (bindingProcessorManager == null) {
+ log.warn("Bindingprocessor not set, skipping removal");
+ return;
+ }
+ Set<Id> managedIds = bindingProcessorManager.getManagedIds();
+ for (Iterator<Id> it = managedIds.iterator(); it.hasNext();) {
+ Id bindId = it.next();
+ BindingProcessor bp = bindingProcessorManager.getBindingProcessor(bindId);
+ if (bp != null) {
+ if (bp.getLastAccessTime().getTime() < (System.currentTimeMillis() - maxAcceptedAge)) {
+ log.debug("Removing binding processor: " + bp.getId());
+ bindingProcessorManager.removeBindingProcessor(bp.getId());
+ }
+ }
+ }
+ }
+
+ public void setMaxAcceptedAge(long maxAcceptedAge) {
+ this.maxAcceptedAge = maxAcceptedAge;
+ }
+
+ @Override
+ public void setBindingProcessorManager(BindingProcessorManager bp) {
+ bindingProcessorManager = bp;
+ }
+
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/FixedFormParameters.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/FixedFormParameters.java
new file mode 100644
index 00000000..cce3d720
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/FixedFormParameters.java
@@ -0,0 +1,28 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+/**
+ * Form parameters with special meaning as defined in the CCE's http binding.
+ *
+ */
+public interface FixedFormParameters {
+ String XMLREQUEST = "XMLRequest";
+ String REDIRECTURL = "RedirectURL";
+ String DATAURL = "DataURL";
+ String STYLESHEETURL = "StylesheetURL";
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameter.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameter.java
new file mode 100644
index 00000000..93339451
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameter.java
@@ -0,0 +1,39 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.InputStream;
+import java.util.Iterator;
+
+/**
+ * Interface to access form control contents from the http request.
+ * It's designed to be used for URL encoded and multipart-formdata requests.
+ * @author wbauer
+ *
+ */
+public interface FormParameter {
+
+ String getFormParameterName();
+
+ InputStream getFormParameterValue();
+
+ String getFormParameterContentType();
+
+ Iterator<String> getHeaderNames();
+
+ String getHeaderValue(String headerName);
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterImpl.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterImpl.java
new file mode 100644
index 00000000..45aa9be6
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterImpl.java
@@ -0,0 +1,93 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.commons.fileupload.FileItemHeaders;
+
+/**
+ * Simple wrapper to read data while consuming an stream within the http
+ * processor.
+ *
+ *
+ */
+public class FormParameterImpl implements FormParameter {
+
+ protected InputStream dataStream;
+ protected String contentType;
+ protected String formName;
+ protected FileItemHeaders headers;
+
+ public FormParameterImpl(String contentType, String formName, InputStream is,
+ FileItemHeaders header) {
+ this.contentType = contentType;
+ this.formName = formName;
+ this.dataStream = is;
+ this.headers = header;
+ }
+
+ @Override
+ public String getFormParameterContentType() {
+ return contentType;
+ }
+
+ @Override
+ public String getFormParameterName() {
+ return formName;
+ }
+
+ @Override
+ public InputStream getFormParameterValue() {
+ return dataStream;
+ }
+
+ @Override
+ public String getHeaderValue(String headerName) {
+ if (headers == null) {
+ return null;
+ }
+ return headers.getHeader(headerName);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Iterator<String> getHeaderNames() {
+ if (headers == null) {
+ return Collections.EMPTY_LIST.iterator();
+ }
+ return headers.getHeaderNames();
+ }
+
+ public FileItemHeaders getHeaders() {
+ return headers;
+ }
+
+ public boolean equals(Object other) {
+ if (other instanceof FormParameter) {
+ FormParameter fp = (FormParameter) other;
+ return fp.getFormParameterName().equals(getFormParameterName());
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return getFormParameterName().hashCode();
+ }
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterStore.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterStore.java
new file mode 100644
index 00000000..8b6cd4b2
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterStore.java
@@ -0,0 +1,146 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.commons.fileupload.FileItemHeaders;
+import org.apache.commons.fileupload.util.FileItemHeadersImpl;
+
+import at.gv.egiz.bku.slexceptions.SLRuntimeException;
+import at.gv.egiz.bku.utils.StreamUtil;
+
+/**
+ * Simple store for form parameters based on a byte[]
+ *
+ * @author wbauer
+ *
+ */
+public class FormParameterStore implements FormParameter {
+
+ private byte[] dataBuffer;
+ private String contentType;
+ private String parameterName;
+ private boolean initialized = false;
+ protected FileItemHeaders headers;
+
+ /**
+ * Make sure to call init after creating a new instance.
+ */
+ public FormParameterStore() {
+ }
+
+ public void init(InputStream dataSource, String paramName,
+ String contentType, FileItemHeaders header) throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ StreamUtil.copyStream(dataSource, os);
+ this.dataBuffer = os.toByteArray();
+ this.parameterName = paramName;
+ this.contentType = contentType;
+ initialized = true;
+ this.headers = header;
+ }
+
+ public void init(byte[] dataSource, String paramName,
+ String contentType, FileItemHeaders header) throws IOException {
+ this.dataBuffer = dataSource;
+ this.parameterName = paramName;
+ this.contentType = contentType;
+ initialized = true;
+ this.headers = header;
+ }
+
+ public void init(FormParameter fp) throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ StreamUtil.copyStream(fp.getFormParameterValue(), os);
+ this.dataBuffer = os.toByteArray();
+ this.parameterName = fp.getFormParameterName();
+ this.contentType = fp.getFormParameterContentType();
+ if (fp instanceof FormParameterImpl) {
+ headers = ((FormParameterImpl) fp).getHeaders();
+ } else {
+ FileItemHeadersImpl headersImpl = new FileItemHeadersImpl();
+ for (Iterator<String> i = fp.getHeaderNames(); i.hasNext();) {
+ String headerName = i.next();
+ headersImpl.addHeader(headerName, fp.getHeaderValue(headerName));
+ }
+ }
+ initialized = true;
+ }
+
+ protected void ensureInitialized() {
+ if (!initialized) {
+ throw new SLRuntimeException("FormParameterStore not initialized");
+ }
+ }
+
+ /**
+ * Reads all data from the stream and stores it internally. The stream will
+ * not be closed.
+ *
+ * @param datSource
+ * @param formName
+ * @param contentType
+ */
+ @Override
+ public String getFormParameterContentType() {
+ ensureInitialized();
+ return contentType;
+ }
+
+ @Override
+ public String getFormParameterName() {
+ ensureInitialized();
+ return parameterName;
+ }
+
+ /**
+ * May be called more than once.
+ */
+ @Override
+ public InputStream getFormParameterValue() {
+ return new ByteArrayInputStream(dataBuffer);
+ }
+
+ @Override
+ public String getHeaderValue(String name) {
+ if (headers == null) {
+ return null;
+ }
+ return headers.getHeader(name);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Iterator<String> getHeaderNames() {
+ if (headers == null) {
+ return Collections.EMPTY_LIST.iterator();
+ }
+ return headers.getHeaderNames();
+ }
+
+ public boolean isEmpty() {
+ ensureInitialized();
+ return dataBuffer.length == 0;
+ }
+
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessor.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessor.java
new file mode 100644
index 00000000..e39addb5
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessor.java
@@ -0,0 +1,844 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * 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 at.gv.egiz.bku.binding;
+
+import iaik.utils.Base64InputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.net.ssl.SSLHandshakeException;
+import javax.xml.transform.Templates;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.bku.slcommands.ErrorResult;
+import at.gv.egiz.bku.slcommands.SLCommand;
+import at.gv.egiz.bku.slcommands.SLCommandContext;
+import at.gv.egiz.bku.slcommands.SLCommandFactory;
+import at.gv.egiz.bku.slcommands.SLCommandInvoker;
+import at.gv.egiz.bku.slcommands.SLResult;
+import at.gv.egiz.bku.slcommands.SLSourceContext;
+import at.gv.egiz.bku.slcommands.SLTargetContext;
+import at.gv.egiz.bku.slcommands.impl.ErrorResultImpl;
+import at.gv.egiz.bku.slexceptions.SLBindingException;
+import at.gv.egiz.bku.slexceptions.SLException;
+import at.gv.egiz.bku.slexceptions.SLRuntimeException;
+import at.gv.egiz.bku.utils.StreamUtil;
+import at.gv.egiz.bku.utils.binding.Protocol;
+import at.gv.egiz.bku.utils.urldereferencer.FormDataURLSupplier;
+import at.gv.egiz.bku.utils.urldereferencer.SimpleFormDataContextImpl;
+import at.gv.egiz.bku.utils.urldereferencer.StreamData;
+import at.gv.egiz.bku.utils.urldereferencer.URIResolverAdapter;
+import at.gv.egiz.bku.utils.urldereferencer.URLDereferencer;
+import at.gv.egiz.bku.utils.urldereferencer.URLDereferencerContext;
+import at.gv.egiz.stal.QuitRequest;
+import at.gv.egiz.stal.STALRequest;
+
+/**
+ * Class performing the HTTP binding as defined by the CCE specification.
+ * Currently a huge monolithic class.
+ *
+ * @TODO refactor
+ */
+@SuppressWarnings("unchecked")
+public class HTTPBindingProcessor extends AbstractBindingProcessor implements
+ FormDataURLSupplier {
+
+ private static Log log = LogFactory.getLog(HTTPBindingProcessor.class);
+
+ private static enum State {
+ INIT, PROCESS, DATAURL, TRANSFORM, FINISHED
+ };
+
+ public final static Collection<String> XML_REQ_TRANSFER_ENCODING = Arrays
+ .asList(new String[] { "binary" });
+
+ protected static String XML_MIME_TYPE = "text/xml";
+ protected static String BINARY_MIME_TYPE = "application/octet-stream";
+
+ /**
+ * If null everything is ok and the result is taken from the command invoker.
+ */
+ protected SLException bindingProcessorError;
+ protected SLCommandInvoker commandInvoker;
+ protected DataUrlResponse dataUrlResponse;
+ protected Map<String, String> headerMap = Collections.EMPTY_MAP;
+ protected SLCommand slCommand;
+ protected Map<String, FormParameter> formParameterMap = new HashMap<String, FormParameter>();
+ protected SLSourceContext srcContex = new SLSourceContext();
+ protected SLTargetContext targetContext = new SLTargetContext();
+ protected URL srcUrl;
+ protected State currentState = State.INIT;
+ protected Templates templates = null;
+ protected String resultContentType = null;
+ protected SLResult slResult = null;
+ protected int responseCode = 200;
+ protected Map<String, String> responseHeaders = Collections.EMPTY_MAP;
+ protected Locale locale = Locale.getDefault();
+ protected boolean finished = false;
+
+ /**
+ *
+ * @param id
+ * may be null. In this case a new session id will be created.
+ * @param cmdInvoker
+ * must not be null;
+ */
+ public HTTPBindingProcessor(String id, SLCommandInvoker cmdInvoker, URL source) {
+ super(id);
+ this.srcUrl = source;
+ Protocol protocol = Protocol.fromString(source.getProtocol());
+ if ((protocol != Protocol.HTTP) && (protocol != Protocol.HTTPS)) {
+ throw new SLRuntimeException("Protocol not supported: " + protocol);
+ }
+ if (cmdInvoker == null) {
+ throw new NullPointerException("Commandinvoker cannot be set to null");
+ }
+ commandInvoker = cmdInvoker;
+ srcContex.setSourceUrl(source);
+ srcContex.setSourceIsDataURL(false);
+ }
+
+ //----------------------------------------------------------------------------
+ // ----------- BEGIN CONVENIENCE METHODS -----------
+
+ protected void sendSTALQuit() {
+ log.info("Sending QUIT command to STAL");
+ List<STALRequest> quit = new ArrayList<STALRequest>(1);
+ quit.add(new QuitRequest());
+ getSTAL().handleRequest(quit);
+ }
+
+ protected String getFormParameterAsString(String formParameterName) {
+ FormParameter fp = formParameterMap.get(formParameterName);
+ return getFormParameterAsString(fp);
+ }
+
+ protected String getFormParameterAsString(FormParameter fp) {
+ if (fp == null) {
+ return null;
+ }
+ try {
+ return StreamUtil.asString(fp.getFormParameterValue(), HttpUtil
+ .getCharset(fp.getFormParameterContentType(), true));
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ protected String getDataUrl() {
+ return getFormParameterAsString(FixedFormParameters.DATAURL);
+ }
+
+ protected String getStyleSheetUrl() {
+ return getFormParameterAsString(FixedFormParameters.STYLESHEETURL);
+ }
+
+ protected List<FormParameter> getFormParameters(String parameterNamePostfix) {
+ List<FormParameter> resultList = new ArrayList<FormParameter>();
+ for (Iterator<String> fpi = formParameterMap.keySet().iterator(); fpi
+ .hasNext();) {
+ String paramName = fpi.next();
+ if (paramName.endsWith(parameterNamePostfix)) {
+ resultList.add(formParameterMap.get(paramName));
+ }
+ }
+ return resultList;
+ }
+
+ protected List<FormParameter> getTransferHeaders() {
+ return getFormParameters("__");
+ }
+
+ protected List<FormParameter> getTransferForms() {
+ List<FormParameter> resultList = new ArrayList<FormParameter>();
+ for (Iterator<String> fpi = formParameterMap.keySet().iterator(); fpi
+ .hasNext();) {
+ String paramName = fpi.next();
+ if ((paramName.endsWith("_")) && (!paramName.endsWith("__"))) {
+ resultList.add(formParameterMap.get(paramName));
+ }
+ }
+ return resultList;
+ }
+
+ protected void closeDataUrlConnection() {
+ log.debug("Closing data url input stream");
+ if (dataUrlResponse == null) {
+ return;
+ }
+ InputStream is = dataUrlResponse.getStream();
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ log.info("Error closing input stream to dataurl server:" + e);
+ }
+ }
+ }
+
+ //----------------------------------------------------------------------------
+ // ----------- END CONVENIENCE METHODS -----------
+
+ //----------------------------------------------------------------------------
+ // -- BEGIN Methods that handle the http binding activities as defined in the
+ // activity diagram --
+
+ protected void init() {
+ log.info("Starting Bindingprocessor in Thread: "
+ + Thread.currentThread().getId());
+ if (bindingProcessorError != null) {
+ log.debug("Detected binding processor error, sending quit command");
+ // sendSTALQuit();
+ currentState = State.FINISHED;
+ } else if (slCommand == null) {
+ log.error("SLCommand not set (consumeRequest not called ??)");
+ bindingProcessorError = new SLException(2000);
+ // sendSTALQuit();
+ currentState = State.FINISHED;
+ } else {
+ currentState = State.PROCESS;
+ }
+ }
+
+ protected void processRequest() {
+ log.debug("Entered State: " + State.PROCESS);
+ log.debug("Processing command: " + slCommand);
+ commandInvoker.setCommand(slCommand);
+ responseCode = 200;
+ responseHeaders = Collections.EMPTY_MAP;
+ dataUrlResponse = null;
+ try {
+ commandInvoker.invoke(srcContex);
+ } catch (SLException e) {
+ log.info("Caught exception: " + e);
+ bindingProcessorError = e;
+ currentState = State.TRANSFORM;
+ }
+ if (getDataUrl() != null) {
+ log.debug("Data Url set to: " + getDataUrl());
+ currentState = State.DATAURL;
+ } else {
+ log.debug("No data url set");
+ currentState = State.TRANSFORM;
+ }
+ }
+
+ protected void handleDataUrl() {
+ log.debug("Entered State: " + State.DATAURL);
+ try {
+ DataUrl dataUrl = new DataUrl(getDataUrl());
+ DataUrlConnection conn = dataUrl.openConnection();
+
+ // set transfer headers
+ for (FormParameter fp : getTransferHeaders()) {
+ String paramString = getFormParameterAsString(fp);
+ if (paramString == null) {
+ log.error("Got empty transfer header, ignoring this");
+ } else {
+ String[] keyVal = paramString.split(":", 2);
+ String key = keyVal[0];
+ String val = null;
+ if (keyVal.length == 2) {
+ val = keyVal[1];
+ val = val.trim();
+ } else {
+ log.error("Invalid transfer header encoding: "+paramString);
+ throw new SLBindingException(2005);
+ }
+ log.debug("Setting header " + key + " to value " + val);
+ conn.setHTTPHeader(key, val);
+ }
+ }
+
+ // set transfer form parameters
+ for (FormParameter fp : getTransferForms()) {
+ String contentTransferEncoding = null;
+ String contentType = fp.getFormParameterContentType();
+ String charSet = HttpUtil.getCharset(contentType, false);
+ if (charSet != null) {
+ contentType = contentType.substring(0, contentType
+ .lastIndexOf(HttpUtil.SEPERATOR[0]));
+ }
+ for (Iterator<String> header = fp.getHeaderNames(); header.hasNext();) {
+ if (HttpUtil.CONTENT_TRANSFER_ENCODING
+ .equalsIgnoreCase(header.next())) {
+ contentTransferEncoding = getFormParameterAsString(fp);
+ }
+ }
+ log.debug("Setting form: " + fp.getFormParameterName()
+ + " contentType: " + contentType + " charset: " + charSet
+ + " contentTransferEncoding: " + contentTransferEncoding);
+ conn.setHTTPFormParameter(fp.getFormParameterName(), fp
+ .getFormParameterValue(), contentType, charSet,
+ contentTransferEncoding);
+ }
+
+ // connect
+ conn.connect();
+ // fetch and set SL result
+ targetContext.setTargetIsDataURL(true);
+ targetContext.setTargetCertificate(conn.getServerCertificate());
+ targetContext.setTargetUrl(conn.getUrl());
+ SLResult result = commandInvoker.getResult(targetContext);
+
+ // transfer result
+ conn.transmit(result);
+
+ // process Dataurl response
+ dataUrlResponse = conn.getResponse();
+ log.debug("Received data url response code: "
+ + dataUrlResponse.getResponseCode());
+
+ switch (dataUrlResponse.getResponseCode()) {
+ case 200:
+ String contentType = dataUrlResponse.getContentType();
+ log.debug("Got dataurl response content type: " + contentType);
+ if (contentType != null) {
+ if ((contentType.startsWith(HttpUtil.APPLICATION_URL_ENCODED))
+ || (contentType.startsWith(HttpUtil.MULTIPART_FOTMDATA))) {
+ log.debug("Detected SL Request in dataurl response");
+ // process headers and request
+ setHTTPHeaders(dataUrlResponse.getResponseHeaders());
+ consumeRequestStream(dataUrlResponse.getStream());
+ //TODO check for bindingProcessorError
+ closeDataUrlConnection();
+ srcContex.setSourceCertificate(conn.getServerCertificate());
+ srcContex.setSourceIsDataURL(true);
+ srcContex.setSourceUrl(conn.getUrl());
+ currentState = State.PROCESS;
+ } else if (((contentType.startsWith(HttpUtil.TXT_HTML))
+ || (contentType.startsWith(HttpUtil.TXT_PLAIN))
+ || (contentType.startsWith(HttpUtil.TXT_XML)))
+ && (dataUrlResponse.isHttpResponseXMLOK())) {
+ log.info("Dataurl response matches <ok/> with content type: "
+ + contentType);
+ currentState = State.TRANSFORM;
+
+ } else if ((contentType.startsWith(HttpUtil.TXT_XML))
+ && (!dataUrlResponse.isHttpResponseXMLOK())) {
+ log
+ .debug("Detected text/xml dataurl response with content != <ok/>");
+ headerMap.put(HttpUtil.HTTP_HEADER_CONTENT_TYPE, contentType);
+ assignXMLRequest(dataUrlResponse.getStream(), HttpUtil.getCharset(
+ contentType, true));
+ closeDataUrlConnection();
+ srcContex.setSourceCertificate(conn.getServerCertificate());
+ srcContex.setSourceIsDataURL(true);
+ srcContex.setSourceUrl(conn.getUrl());
+ currentState = State.PROCESS;
+ // just to be complete, actually not used
+ srcContex.setSourceHTTPReferer(dataUrlResponse.getResponseHeaders()
+ .get(HttpUtil.HTTP_HEADER_REFERER));
+ } else {
+ resultContentType = contentType;
+ responseHeaders = dataUrlResponse.getResponseHeaders();
+ responseCode = dataUrlResponse.getResponseCode();
+ currentState = State.FINISHED;
+ }
+ } else {
+ log.debug("Content type not set in dataurl response");
+ closeDataUrlConnection();
+ throw new SLBindingException(2007);
+ }
+
+ break;
+ case 307:
+ contentType = dataUrlResponse.getContentType();
+ if ((contentType != null) && (contentType.startsWith(HttpUtil.TXT_XML))) {
+ log.debug("Received dataurl response code 307 with XML content");
+ String location = dataUrlResponse.getResponseHeaders().get(
+ HttpUtil.HTTP_HEADER_LOCATION);
+ if (location == null) {
+ log
+ .error("Did not get a location header for a 307 data url response");
+ throw new SLBindingException(2003);
+ }
+ // consumeRequestStream(dataUrlResponse.getStream());
+ FormParameterStore fp = new FormParameterStore();
+ fp.init(location.getBytes(HttpUtil.DEFAULT_CHARSET),
+ FixedFormParameters.DATAURL, null, null);
+ formParameterMap.put(FixedFormParameters.DATAURL, fp);
+ headerMap.put(HttpUtil.HTTP_HEADER_CONTENT_TYPE, contentType);
+ assignXMLRequest(dataUrlResponse.getStream(), HttpUtil.getCharset(
+ dataUrlResponse.getContentType(), true));
+ closeDataUrlConnection();
+ srcContex.setSourceCertificate(conn.getServerCertificate());
+ srcContex.setSourceIsDataURL(true);
+ srcContex.setSourceUrl(conn.getUrl());
+ currentState = State.PROCESS;
+ // just to be complete, actually not used
+ srcContex.setSourceHTTPReferer(dataUrlResponse.getResponseHeaders()
+ .get(HttpUtil.HTTP_HEADER_REFERER));
+
+ } else {
+ log.debug("Received dataurl response code 307 non XML content: "
+ + dataUrlResponse.getContentType());
+ resultContentType = dataUrlResponse.getContentType();
+ currentState = State.FINISHED;
+ }
+ responseHeaders = dataUrlResponse.getResponseHeaders();
+ responseCode = dataUrlResponse.getResponseCode();
+ break;
+
+ case 301:
+ case 302:
+ case 303:
+ responseHeaders = dataUrlResponse.getResponseHeaders();
+ responseCode = dataUrlResponse.getResponseCode();
+ resultContentType = dataUrlResponse.getContentType();
+ currentState = State.FINISHED;
+ break;
+
+ default:
+ // issue error
+ log.info("Unexpected response code from dataurl server: "
+ + dataUrlResponse.getResponseCode());
+ throw new SLBindingException(2007);
+ }
+
+ } catch (SLException slx) {
+ bindingProcessorError = slx;
+ log.error("Error during dataurl communication");
+ resultContentType = HttpUtil.TXT_XML;
+ currentState = State.TRANSFORM;
+ } catch (SSLHandshakeException hx) {
+ bindingProcessorError = new SLException(2010);
+ log.info("Error during dataurl communication", hx);
+ resultContentType = HttpUtil.TXT_XML;
+ currentState = State.TRANSFORM;
+ } catch (IOException e) {
+ bindingProcessorError = new SLBindingException(2001);
+ log.error("Error while data url handling", e);
+ resultContentType = HttpUtil.TXT_XML;
+ currentState = State.TRANSFORM;
+ return;
+ }
+ }
+
+ protected void transformResult() {
+ log.debug("Entered State: " + State.TRANSFORM);
+ if (bindingProcessorError != null) {
+ resultContentType = HttpUtil.TXT_XML;
+ } else if (dataUrlResponse != null) {
+ resultContentType = dataUrlResponse.getContentType();
+ } else {
+ targetContext.setTargetIsDataURL(false);
+ targetContext.setTargetUrl(srcUrl);
+ try {
+ slResult = commandInvoker.getResult(targetContext);
+ resultContentType = slResult.getMimeType();
+ log
+ .debug("Successfully got SLResult from commandinvoker, setting mimetype to: "
+ + resultContentType);
+ } catch (SLException e) {
+ log.info("Cannot get result from invoker:", e);
+ bindingProcessorError = new SLException(6002);
+ resultContentType = HttpUtil.TXT_XML;
+ }
+ }
+ templates = getTemplates(getStyleSheetUrl());
+ if (templates != null) {
+ log.debug("Output transformation required");
+ resultContentType = templates.getOutputProperties().getProperty("media-type");
+ log.debug("Got media type from stylesheet: " + resultContentType);
+ if (resultContentType == null) {
+ log.debug("Setting to default text/xml result conent type");
+ resultContentType = "text/xml";
+ }
+ log.debug("Deferring sytylesheet processing");
+ }
+ currentState = State.FINISHED;
+ }
+
+ protected void finished() {
+ log.debug("Entered State: " + State.FINISHED);
+ if (bindingProcessorError != null) {
+ log.debug("Binding processor error, sending quit command");
+ resultContentType = HttpUtil.TXT_XML;
+ }
+ sendSTALQuit();
+ log.info("Terminating Bindingprocessor; Thread: "
+ + Thread.currentThread().getId());
+ finished = true;
+ }
+
+ // -- END Methods that handle the http binding activities as defined in the
+ // activity diagram --
+ //----------------------------------------------------------------------------
+
+ /**
+ * Sets the headers of the SL Request. IMPORTANT: make sure to set all headers
+ * before invoking {@link #consumeRequestStream(InputStream)}
+ *
+ * @param aHeaderMap
+ * if null all header will be cleared.
+ */
+ public void setHTTPHeaders(Map<String, String> aHeaderMap) {
+ headerMap = new HashMap<String, String>();
+ // ensure lowercase keys
+ if (aHeaderMap != null) {
+ for (String s : aHeaderMap.keySet()) {
+ if (s != null) {
+ headerMap.put(s.toLowerCase(), aHeaderMap.get(s));
+ if (s.equalsIgnoreCase(HttpUtil.HTTP_HEADER_REFERER)) {
+ String referer = aHeaderMap.get(s);
+ log.debug("Got referer header: " + referer);
+ srcContex.setSourceHTTPReferer(referer);
+ }
+ }
+ }
+ }
+ }
+
+ public void setSourceCertificate(X509Certificate aCert) {
+ srcContex.setSourceCertificate(aCert);
+ }
+
+ /**
+ * The HTTPBindingProcessor does not handle redirect URLs. It only provides
+ * the parameter.
+ *
+ * @return null if redirect url is not set.
+ */
+ public String getRedirectURL() {
+ return getFormParameterAsString(FixedFormParameters.REDIRECTURL);
+ }
+
+ public String getFormDataContentType(String aParameterName) {
+ FormParameter fp = formParameterMap.get(aParameterName);
+ if (fp != null) {
+ return fp.getFormParameterContentType();
+ }
+ return null;
+ }
+
+ public InputStream getFormData(String aParameterName) {
+ FormParameter fp = formParameterMap.get(aParameterName);
+ if (fp != null) {
+ final String enc = fp.getHeaderValue("Content-Transfer-Encoding");
+ if (enc == null || "binary".equals(enc)) {
+ return fp.getFormParameterValue();
+ } else if ("base64".equals(enc)) {
+ return new Base64InputStream(fp.getFormParameterValue());
+ } else {
+ return new InputStream() {
+ @Override
+ public int read() throws IOException {
+ throw new IOException("Content-Transfer-Encoding : " + enc
+ + " is not supported.");
+ }
+ };
+ }
+ }
+ return null;
+ }
+
+ protected void assignXMLRequest(InputStream is, String charset)
+ throws IOException, SLException {
+ Reader r = new InputStreamReader(is, charset);
+ StreamSource source = new StreamSource(r);
+ SLCommandContext commandCtx = new SLCommandContext();
+ commandCtx.setSTAL(getSTAL());
+ commandCtx.setURLDereferencerContext(new SimpleFormDataContextImpl(this));
+ commandCtx.setLocale(locale);
+ slCommand = SLCommandFactory.getInstance().createSLCommand(source,
+ commandCtx);
+ log.debug("Created new command: " + slCommand);
+ }
+
+ @Override
+ public void run() {
+ boolean done = false;
+ int hopcounter = 0;
+ if (bindingProcessorError != null) {
+ currentState = State.FINISHED;
+ }
+ try {
+ while (!done) {
+ try {
+ switch (currentState) {
+ case INIT:
+ init();
+ break;
+ case PROCESS:
+ processRequest();
+ break;
+ case DATAURL:
+ handleDataUrl();
+ if (++hopcounter > config.getMaxDataUrlHops()) {
+ log.error("Maximum number of dataurl hops reached");
+ bindingProcessorError = new SLBindingException(2000);
+ currentState = State.FINISHED;
+ }
+ break;
+ case TRANSFORM:
+ transformResult();
+ break;
+ case FINISHED:
+ done = true;
+ finished();
+ break;
+ }
+ } catch (RuntimeException rte) {
+ throw rte;
+ } catch (Exception t) {
+ log.error("Caught unexpected exception", t);
+ responseCode = 200;
+ resultContentType = HttpUtil.TXT_XML;
+ responseHeaders = Collections.EMPTY_MAP;
+ bindingProcessorError = new SLException(2000);
+ currentState = State.FINISHED;
+ }
+ }
+ } catch (Throwable t) {
+ log.error("Caught unexpected exception", t);
+ responseCode = 200;
+ resultContentType = HttpUtil.TXT_XML;
+ responseHeaders = Collections.EMPTY_MAP;
+ bindingProcessorError = new SLException(2000);
+ currentState = State.FINISHED;
+ }
+ log.debug("Terminated http binding processor");
+ finished = true;
+ }
+
+ @Override
+ public void consumeRequestStream(InputStream is) {
+ try {
+ log.debug("Start consuming request stream");
+ formParameterMap.clear();
+ String cl = headerMap
+ .get(HttpUtil.HTTP_HEADER_CONTENT_TYPE.toLowerCase());
+ if (cl == null) {
+ log.info("No content type set in http header");
+ throw new SLBindingException(2006);
+ }
+ InputDecoder id = InputDecoderFactory.getDecoder(cl, is);
+ if (id == null) {
+ log.error("Cannot get inputdecoder for is");
+ throw new SLException(2006);
+ }
+ for (Iterator<FormParameter> fpi = id.getFormParameterIterator(); fpi
+ .hasNext();) {
+ FormParameter fp = fpi.next();
+ log.debug("Got request parameter with name: "
+ + fp.getFormParameterName());
+ if (fp.getFormParameterName().equals(FixedFormParameters.XMLREQUEST)) {
+ log.debug("Creating XML Request");
+ for (Iterator<String> headerIterator = fp.getHeaderNames(); headerIterator
+ .hasNext();) {
+ String headerName = headerIterator.next();
+ if (HttpUtil.CONTENT_TRANSFER_ENCODING.equalsIgnoreCase(headerName)) {
+ String transferEncoding = fp.getHeaderValue(headerName);
+ log.debug("Got transfer encoding for xmlrequest: "
+ + transferEncoding);
+ if (XML_REQ_TRANSFER_ENCODING.contains(transferEncoding)) {
+ log.debug("Supported transfer encoding: " + transferEncoding);
+ } else {
+ log
+ .error("Transferencoding not supported: "
+ + transferEncoding);
+ throw new SLBindingException(2005);
+ }
+ }
+ }
+ String charset = HttpUtil.getCharset(cl, true);
+ assignXMLRequest(fp.getFormParameterValue(), charset);
+ } else {
+ FormParameterStore fps = new FormParameterStore();
+ fps.init(fp);
+ //if (!fps.isEmpty()) {
+ log.debug("Setting form parameter: " + fps.getFormParameterName());
+ formParameterMap.put(fps.getFormParameterName(), fps);
+ //}
+ }
+ }
+ if (slCommand == null) {
+ throw new SLBindingException(2004);
+ }
+ } catch (SLException slx) {
+ log.info("Error while consuming input stream " + slx);
+ bindingProcessorError = slx;
+ } catch (Throwable t) {
+ log.info("Error while consuming input stream " + t, t);
+ bindingProcessorError = new SLException(2000);
+ } finally {
+ try {
+ if (is.read() != -1) {
+ log.warn("Request input stream not completely read.");
+ while (is.read() != -1);
+ }
+ } catch (IOException e) {
+ log.error(e);
+ }
+ }
+ }
+
+ @Override
+ public String getResultContentType() {
+ return resultContentType;
+ }
+
+ protected Templates getTemplates(String styleSheetURL) {
+ if (styleSheetURL == null) {
+ log.debug("Stylesheet URL not set");
+ return null;
+ }
+ try {
+ URLDereferencerContext urlCtx = new SimpleFormDataContextImpl(this);
+ URIResolver resolver = new URIResolverAdapter(URLDereferencer
+ .getInstance(), urlCtx);
+ TransformerFactory factory = TransformerFactory.newInstance();
+ factory.setURIResolver(resolver);
+ StreamData sd = URLDereferencer.getInstance().dereference(styleSheetURL,
+ urlCtx);
+ return factory.newTemplates(new StreamSource(sd.getStream()));
+ } catch (Exception ex) {
+ log.info("Cannot instantiate transformer", ex);
+ bindingProcessorError = new SLException(2002);
+ return null;
+ }
+ }
+
+ protected void handleBindingProcessorError(OutputStream os, String encoding,
+ Templates templates) throws IOException {
+ log.debug("Writing error as result");
+ ErrorResultImpl error = new ErrorResultImpl(bindingProcessorError, locale);
+ Writer writer = writeXMLDeclarationAndProcessingInstruction(os, encoding);
+ error.writeTo(new StreamResult(writer), templates, true);
+ }
+
+ protected Writer writeXMLDeclarationAndProcessingInstruction(OutputStream os, String encoding) throws IOException {
+ if (encoding == null) {
+ encoding = HttpUtil.DEFAULT_CHARSET;
+ }
+ OutputStreamWriter writer = new OutputStreamWriter(os, encoding);
+ writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
+ writer.write("<?xml-stylesheet type=\"text/css\" href=\"errorresponse.css\"?>\n");
+ return writer;
+ }
+
+ @Override
+ public void writeResultTo(OutputStream os, String encoding)
+ throws IOException {
+ if (encoding == null) {
+ encoding = HttpUtil.DEFAULT_CHARSET;
+ }
+ if (bindingProcessorError != null) {
+ log.debug("Detected error in binding processor, writing error as result");
+ handleBindingProcessorError(os, encoding, templates);
+ return;
+ } else if (dataUrlResponse != null) {
+ log.debug("Writing data url response as result");
+ String charEnc = HttpUtil.getCharset(dataUrlResponse.getContentType(),
+ true);
+ InputStreamReader isr = new InputStreamReader(
+ dataUrlResponse.getStream(), charEnc);
+ OutputStreamWriter osw = new OutputStreamWriter(os, encoding);
+ if (templates == null) {
+ StreamUtil.copyStream(isr, osw);
+ } else {
+ try {
+ Transformer transformer = templates.newTransformer();
+ transformer.transform(new StreamSource(isr), new StreamResult(osw));
+ } catch (TransformerException e) {
+ log.fatal("Exception occured during result transformation", e);
+ // bindingProcessorError = new SLException(2008);
+ // handleBindingProcessorError(os, encoding, null);
+ return;
+ }
+ }
+ osw.flush();
+ isr.close();
+ } else if (slResult == null) {
+ // result not yet assigned -> must be a cancel
+ bindingProcessorError = new SLException(6001);
+ handleBindingProcessorError(os, encoding, templates);
+ return;
+ } else {
+ log.debug("Getting result from invoker");
+ boolean fragment = false;
+ Writer writer;
+ if (slResult instanceof ErrorResult) {
+ writer = writeXMLDeclarationAndProcessingInstruction(os, encoding);
+ fragment = true;
+ } else {
+ writer = new OutputStreamWriter(os, encoding);
+ }
+ slResult.writeTo(new StreamResult(writer), templates, fragment);
+ writer.flush();
+ }
+ }
+
+ /**
+ * The response code from the dataurl server or 200 if no dataurl server
+ * created the result
+ *
+ * @return
+ */
+ public int getResponseCode() {
+ return responseCode;
+ }
+
+ /**
+ * All headers from the data url server in case of a direct forward from the
+ * dataurl server.
+ *
+ * @return
+ */
+ public Map<String, String> getResponseHeaders() {
+ return responseHeaders;
+ }
+
+ @Override
+ public void setLocale(Locale locale) {
+ if (locale == null) {
+ throw new NullPointerException("Locale must not be set to null");
+ }
+ this.locale = locale;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return finished;
+ }
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/HttpUtil.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/HttpUtil.java
new file mode 100644
index 00000000..5ea7b25e
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/HttpUtil.java
@@ -0,0 +1,79 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.util.Map;
+
+import org.apache.commons.fileupload.ParameterParser;
+
+/**
+ * Placeholder for some HTTP related constants and helper method to extract the charset for a request.
+ *
+ */
+public class HttpUtil {
+
+ public final static String CHAR_SET = "charset";
+ public final static String DEFAULT_CHARSET = "ISO-8859-1";
+ public final static String HTTP_HEADER_CONTENT_TYPE = "Content-Type";
+ public static final String HTTP_HEADER_USER_AGENT = "User-Agent";
+ public static final String HTTP_HEADER_SERVER = "Server";
+ public final static String HTTP_HEADER_REFERER = "Referer";
+ public final static String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
+ public final static String MULTIPART_FOTMDATA = "multipart/form-data";
+ public final static String MULTIPART_FOTMDATA_BOUNDARY = "boundary";
+ public final static String TXT_XML = "text/xml";
+ public final static String TXT_PLAIN = "text/plain";
+ public final static String TXT_HTML = "text/html";
+ public final static String APPLICATION_URL_ENCODED = "application/x-www-form-urlencoded";
+ public final static String HTTP_HEADER_LOCATION = "Location";
+
+ public final static char[] SEPERATOR = { ';' };
+
+ /**
+ * Extracts charset from a content type header.
+ *
+ * @param contentType
+ * @param replaceNullWithDefault
+ * if true the method return the default charset if not set
+ * @return charset String or null if not present
+ */
+ @SuppressWarnings("unchecked")
+ public static String getCharset(String contentType,
+ boolean replaceNullWithDefault) {
+ ParameterParser pf = new ParameterParser();
+ pf.setLowerCaseNames(true);
+ Map map = pf.parse(contentType, SEPERATOR);
+ String retVal = (String) map.get(CHAR_SET);
+ if ((retVal == null) && (replaceNullWithDefault)) {
+ if (map.containsKey(APPLICATION_URL_ENCODED)) {
+ // default charset for url encoded data
+ return "UTF-8";
+ }
+ retVal = getDefaultCharset();
+ }
+ return retVal;
+ }
+
+ /**
+ *
+ * Not to be used for url encoded requests.
+ */
+ public static String getDefaultCharset() {
+ return DEFAULT_CHARSET;
+ }
+
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/Id.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/Id.java
new file mode 100644
index 00000000..fc7c35c3
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/Id.java
@@ -0,0 +1,31 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+/**
+ * The unique identifier for a BindingProcessor
+ * @author wbauer
+ *
+ */
+public interface Id {
+
+ @Override
+ public String toString();
+
+ @Override
+ public boolean equals(Object id);
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/IdFactory.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/IdFactory.java
new file mode 100644
index 00000000..60bf69a4
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/IdFactory.java
@@ -0,0 +1,106 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Creates or converts Ids for BindingProcessors.
+ * @author wbauer
+ *
+ */
+public class IdFactory {
+
+ public static int DEFAULT_NUMBER_OF_BITS = 168;
+
+ private static Log log = LogFactory.getLog(IdFactory.class);
+
+ private static IdFactory instance = new IdFactory();
+
+ private SecureRandom random;
+ private int numberOfBits = DEFAULT_NUMBER_OF_BITS;
+
+ private IdFactory() {
+ try {
+ random = SecureRandom.getInstance("SHA1PRNG");
+ } catch (NoSuchAlgorithmException e) {
+ log.error("Cannot instantiate secure random" + e);
+ }
+ }
+
+ public static IdFactory getInstance() {
+ return instance;
+ }
+
+
+ /**
+ * set the secure random number generator to create secure ids.
+ *
+ * @param random
+ * must not be null
+ */
+ public void setSecureRandom(SecureRandom random) {
+ if (random == null) {
+ throw new NullPointerException("Cannot set secure random to null");
+ }
+ this.random = random;
+ }
+
+ /**
+ * Don't use this method unless you know exactly what you do !
+ * Be sure to use a sufficient large entropy
+ * @param numberOfBits >=1 (although this small entropy does not make sense)
+ */
+ public void setNumberOfBits(int numberOfBits) {
+ if (numberOfBits <1) {
+ throw new IllegalArgumentException("Cannot set number of bits < 1");
+ }
+ this.numberOfBits = numberOfBits;
+ }
+
+ public int getNumberOfBits() {
+ return numberOfBits;
+ }
+
+ /**
+ * Creates a new Id object with the factory's secure RNG and the set number of
+ * bits.
+ *
+ * @return
+ */
+ public Id createId() {
+ return new IdImpl(numberOfBits, random);
+ }
+
+ /**
+ * Creates an Id object for the provided String
+ *
+ * @param idString
+ * may be null in this case the method call creates a new Id.
+ * @return
+ */
+ public Id createId(String idString) {
+ if (idString == null) {
+ return createId();
+ }
+ return new IdImpl(idString);
+ }
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/IdImpl.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/IdImpl.java
new file mode 100644
index 00000000..c8a76823
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/IdImpl.java
@@ -0,0 +1,83 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import iaik.utils.Base64OutputStream;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Implementation that uses a Base64 representation for self generated Ids.
+ * @author wbauer
+ *
+ */
+public class IdImpl implements at.gv.egiz.bku.binding.Id {
+ private static Log log = LogFactory.getLog(IdImpl.class);
+
+ private String idString;
+
+ public IdImpl(int bitNumber, SecureRandom random) {
+ int byteSize = bitNumber/8;
+ if (bitNumber % 8 != 0) {
+ byteSize++;
+ }
+ byte[] randomBytes = new byte[byteSize];
+ random.nextBytes(randomBytes);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ Base64OutputStream b64 = new Base64OutputStream(baos);
+ try {
+ b64.write(randomBytes);
+ b64.flush();
+ b64.close();
+ idString = new String(baos.toByteArray());
+ } catch (IOException e) {
+ log.error("Cannot create secure id: "+e);
+ }
+ }
+
+ public IdImpl(String idString) {
+ if (idString == null) {
+ throw new NullPointerException("Provided idstring must not be null");
+ }
+ this.idString = idString;
+ }
+
+ @Override
+ public String toString() {
+ return idString;
+ }
+
+ @Override
+ public int hashCode() {
+ return idString.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Id) {
+ Id otherId = (Id)other;
+ return otherId.toString().equals(idString);
+ } else {
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoder.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoder.java
new file mode 100644
index 00000000..e22e54f2
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoder.java
@@ -0,0 +1,41 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.InputStream;
+import java.util.Iterator;
+
+/**
+ * Decodes http input stream (either url encoded or multipart formdata)
+ * @author wbauer
+ *
+ */
+public interface InputDecoder {
+ /**
+ * Called from Factory.
+ * @param contentType
+ */
+ void setContentType(String contentType);
+
+ /**
+ * Called from Factory.
+ * @param is the input must not be null
+ */
+ void setInputStream(InputStream is);
+
+ Iterator<FormParameter> getFormParameterIterator();
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoderFactory.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoderFactory.java
new file mode 100644
index 00000000..211deee7
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoderFactory.java
@@ -0,0 +1,89 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Factory to get a matching instance for a encoded input stream when reading a http request.
+ *
+ */
+public class InputDecoderFactory {
+
+ public final static String MULTIPART_FORMDATA = "multipart/form-data";
+ public final static String URL_ENCODED = "application/x-www-form-urlencoded";
+
+ private static InputDecoderFactory instance = new InputDecoderFactory();
+ private static Log log = LogFactory.getLog(InputDecoderFactory.class);
+
+ private String defaultEncoding = URL_ENCODED;
+ private Map<String, Class<? extends InputDecoder>> decoderMap = new HashMap<String, Class<? extends InputDecoder>>();
+
+ private InputDecoderFactory() {
+ decoderMap.put(MULTIPART_FORMDATA, MultiPartFormDataInputDecoder.class);
+ decoderMap.put(URL_ENCODED, XWWWFormUrlInputDecoder.class);
+ }
+
+ public static InputDecoder getDefaultDecoder(InputStream is) {
+ return getDecoder(instance.defaultEncoding, is);
+ }
+
+ /**
+ *
+ * @param contentType
+ * @param is
+ * @return null if the content type is not supported
+ */
+ public static InputDecoder getDecoder(String contentType, InputStream is) {
+ String prefix = contentType.split(";")[0].trim().toLowerCase();
+ Class<? extends InputDecoder> dec = instance.decoderMap.get(prefix);
+ if (dec == null) {
+ log.info("Unknown encoding prefix " + contentType);
+ return null;
+ }
+ InputDecoder id;
+ try {
+ id = dec.newInstance();
+ id.setContentType(contentType);
+ id.setInputStream(is);
+ return id;
+ } catch (InstantiationException e) {
+ log.error(e);
+ throw new IllegalArgumentException(
+ "Cannot get an input decoder for content type: " + contentType);
+ } catch (IllegalAccessException e) {
+ log.error(e);
+ throw new IllegalArgumentException(
+ "Cannot get an input decoder for content type: " + contentType);
+ }
+ }
+
+ /**
+ * Allows to register decoders for special mime types.
+ * @param mimeType
+ * @param decoder
+ */
+ public static void registerDecoder(String mimeType,
+ Class<? extends InputDecoder> decoder) {
+ instance.decoderMap.put(mimeType.toLowerCase(), decoder);
+ }
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/MultiPartFormDataInputDecoder.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/MultiPartFormDataInputDecoder.java
new file mode 100644
index 00000000..f8b13553
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/MultiPartFormDataInputDecoder.java
@@ -0,0 +1,133 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileItemStream;
+import org.apache.commons.fileupload.FileUpload;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.RequestContext;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.bku.slexceptions.SLRuntimeException;
+
+/**
+ * The code to detect the multipart boundary is based on
+ * org.apache.commons.fileupload.FileUploadBase of
+ * http://commons.apache.org/fileupload/
+ *
+ * @author wbauer
+ *
+ */
+public class MultiPartFormDataInputDecoder implements InputDecoder,
+ RequestContext {
+
+ private static Log log = LogFactory
+ .getLog(MultiPartFormDataInputDecoder.class);
+
+ private String contentType;
+ private InputStream stream;
+
+ @Override
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return null;
+ }
+
+ @Override
+ public int getContentLength() {
+ return 0;
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return stream;
+ }
+
+ @Override
+ public Iterator<FormParameter> getFormParameterIterator() {
+ try {
+ FileUpload fup = new FileUpload();
+ FileItemIterator fit = fup.getItemIterator(this);
+ return new IteratorDelegator(fit);
+ } catch (Exception iox) {
+ log.error("Cannot decode multipart form data stream " + iox);
+ throw new SLRuntimeException(iox);
+ }
+ }
+
+ @Override
+ public void setInputStream(InputStream is) {
+ stream = is;
+ }
+
+ static class IteratorDelegator implements Iterator<FormParameter> {
+
+ private FileItemIterator fileItemIterator;
+
+ public IteratorDelegator(FileItemIterator fit) {
+ fileItemIterator = fit;
+ }
+
+ @Override
+ public boolean hasNext() {
+ try {
+ return fileItemIterator.hasNext();
+ } catch (FileUploadException e) {
+ log.error(e);
+ throw new SLRuntimeException(e);
+ } catch (IOException e) {
+ log.error(e);
+ throw new SLRuntimeException(e);
+ }
+ }
+
+ @Override
+ public FormParameter next() {
+ try {
+ FileItemStream item = fileItemIterator.next();
+ return new FormParameterImpl(item.getContentType(),
+ item.getFieldName(), item.openStream(), item.getHeaders());
+ } catch (FileUploadException e) {
+ log.error(e);
+ throw new SLRuntimeException(e);
+ } catch (IOException e) {
+ log.error(e);
+ throw new SLRuntimeException(e);
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("Remove not supported");
+ }
+ }
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/ProcessingContext.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/ProcessingContext.java
new file mode 100644
index 00000000..913259f6
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/ProcessingContext.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * 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 at.gv.egiz.bku.binding;
+
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.Future;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class ProcessingContext {
+
+ public static final String BINDING_PROCESSOR = "binding.processor";
+ public static final String FUTURE = "future";
+
+ protected static final Log log = LogFactory.getLog(ProcessingContext.class);
+
+ protected Map<String, Object> properties = new Hashtable<String, Object>();
+
+ public ProcessingContext(BindingProcessor bp, Future future) {
+ properties.put(BINDING_PROCESSOR, bp);
+ properties.put(FUTURE, future);
+ }
+
+ public BindingProcessor getBindingProcessor() {
+ return (BindingProcessor) properties.get(BINDING_PROCESSOR);
+ }
+
+ public Future getFuture() {
+ return (Future) properties.get(FUTURE);
+ }
+
+ public Object get(String key) {
+ return properties.get(key);
+ }
+
+ public void put(String key, Object value) {
+ properties.put(key, value);
+ }
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/RemovalStrategy.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/RemovalStrategy.java
new file mode 100644
index 00000000..6c2dcb9f
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/RemovalStrategy.java
@@ -0,0 +1,26 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+/**
+ * Could be used to remove expired BindingProcessor objects from a BindingProcessorManager.
+ *
+ */
+public interface RemovalStrategy {
+ public void execute();
+ public void setBindingProcessorManager(BindingProcessorManager bp);
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/SLCommandInvokerImpl.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/SLCommandInvokerImpl.java
new file mode 100644
index 00000000..a23d96e8
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/SLCommandInvokerImpl.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * 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 at.gv.egiz.bku.binding;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import at.gv.egiz.bku.accesscontroller.SecurityManagerFacade;
+import at.gv.egiz.bku.slcommands.SLCommand;
+import at.gv.egiz.bku.slcommands.SLCommandInvoker;
+import at.gv.egiz.bku.slcommands.SLResult;
+import at.gv.egiz.bku.slcommands.SLSourceContext;
+import at.gv.egiz.bku.slcommands.SLTargetContext;
+import at.gv.egiz.bku.slexceptions.SLException;
+
+/**
+ * This class implements the entry point for the CCEs security management.
+ *
+ */
+public class SLCommandInvokerImpl implements SLCommandInvoker {
+
+ private static Log log = LogFactory.getLog(SLCommandInvokerImpl.class);
+
+ protected SLCommand command;
+ protected SLResult result;
+ protected SecurityManagerFacade securityManager;
+
+ /**
+ * Invokes a sl command.
+ *
+ * @throws SLException
+ */
+ public void invoke(SLSourceContext aContext) throws SLException {
+ if (securityManager == null) {
+ log.warn("Security policy not implemented yet, invoking command: "
+ + command);
+ result = command.execute();
+ } else {
+ if (securityManager.mayInvokeCommand(command, aContext)) {
+ result = command.execute();
+ } else {
+ throw new SLException(6002);
+ }
+ }
+ }
+
+ public SLResult getResult(SLTargetContext aContext) throws SLException {
+ if (securityManager == null) {
+ log
+ .warn("Security policy not implemented yet, getting result of command: "
+ + command);
+ return result;
+ } else {
+ if (securityManager.maySendResult(command, aContext)) {
+ return result;
+ } else {
+ throw new SLException(6002);
+ }
+ }
+ }
+
+ public void setCommand(SLCommand aCmd) {
+ command = aCmd;
+ }
+
+ @Override
+ public SLCommandInvoker newInstance() {
+ SLCommandInvokerImpl cmdInv = new SLCommandInvokerImpl();
+ cmdInv.setSecurityManager(securityManager);
+ return cmdInv;
+ }
+
+ public SecurityManagerFacade getSecurityManager() {
+ return securityManager;
+ }
+
+ public void setSecurityManager(SecurityManagerFacade securityManager) {
+ this.securityManager = securityManager;
+ }
+
+} \ No newline at end of file
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputDecoder.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputDecoder.java
new file mode 100644
index 00000000..69c659e1
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputDecoder.java
@@ -0,0 +1,58 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding;
+
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.commons.fileupload.ParameterParser;
+
+public class XWWWFormUrlInputDecoder implements InputDecoder {
+
+ /**
+ * The MIME type 'application/x-www-form-urlencoded'.
+ */
+ public static final String CONTENT_TYPE = "application/x-www-form-urlencoded";
+
+ /**
+ * The form parameter iterator.
+ */
+ protected XWWWFormUrlInputIterator iterator;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void setContentType(String contentType) {
+ ParameterParser pp = new ParameterParser();
+ pp.setLowerCaseNames(true);
+ Map<String, String> params = pp.parse(contentType, new char[] { ':', ';' });
+ if (!params.containsKey(CONTENT_TYPE)) {
+ throw new IllegalArgumentException(
+ "not a url encoded content type specification: " + contentType);
+ }
+ }
+
+ @Override
+ public Iterator<FormParameter> getFormParameterIterator() {
+ return iterator;
+ }
+
+ @Override
+ public void setInputStream(InputStream is) {
+ iterator = new XWWWFormUrlInputIterator(is);
+ }
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputIterator.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputIterator.java
new file mode 100644
index 00000000..9279130d
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputIterator.java
@@ -0,0 +1,386 @@
+package at.gv.egiz.bku.binding;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+public class XWWWFormUrlInputIterator implements Iterator<FormParameter> {
+
+ public static final byte NAME_VALUE_SEP = '=';
+
+ public static final byte PARAM_SEP = '&';
+
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+ /**
+ * The default buffer size.
+ */
+ protected static final int DEFAULT_BUFFER_SIZE = 4096;
+
+ /**
+ * Are we done with parsing the input.
+ */
+ protected boolean done = false;
+
+ /**
+ * The x-www-formdata-urlencoded input stream to be parsed.
+ */
+ protected final InputStream in;
+
+ /**
+ * The buffer size.
+ */
+ protected int bufferSize = DEFAULT_BUFFER_SIZE;
+
+ /**
+ * The read buffer.
+ */
+ protected final byte[] buf = new byte[bufferSize];
+
+ /**
+ * The read position.
+ */
+ protected int pos;
+
+ /**
+ * The number of valid bytes in the buffer;
+ */
+ protected int count;
+
+ /**
+ * The parameter returned by the last call of {@link #next()};
+ */
+ protected XWWWFormUrlEncodedParameter currentParameter;
+
+ /**
+ * An IOException that cannot be reported immediately.
+ */
+ protected IOException deferredIOException;
+
+ /**
+ * Creates a new instance of this x-www-formdata-urlencoded input iterator
+ * with the given InputStream <code>in</code> to be parsed.
+ *
+ * @param in the InputStream to be parsed
+ */
+ public XWWWFormUrlInputIterator(InputStream in) {
+ this.in = in;
+ }
+
+ /* (non-Javadoc)
+ * @see java.util.Iterator#hasNext()
+ */
+ @Override
+ public boolean hasNext() {
+ if (done) {
+ return false;
+ }
+ try {
+ if (currentParameter != null) {
+ // we have to disconnect the current parameter
+ // to look for further parameters
+ currentParameter.formParameterValue.disconnect();
+ }
+ // fill buffer if empty
+ if (pos >= count) {
+ if ((count = in.read(buf)) == -1) {
+ // done
+ done = true;
+ return false;
+ }
+ pos = 0;
+ }
+ } catch (IOException e) {
+ deferredIOException = e;
+ // return true to be able to report error
+ return true;
+ }
+ return true;
+ }
+
+ @Override
+ public FormParameter next() {
+ if (hasNext()) {
+ // skip separator
+ if (buf[pos] == PARAM_SEP) {
+ pos++;
+ }
+ currentParameter = new XWWWFormUrlEncodedParameter();
+ return currentParameter;
+ } else {
+ throw new NoSuchElementException();
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public class XWWWFormUrlEncodedParameter implements FormParameter {
+
+ /**
+ * The list of header names.
+ */
+ // x-www-form-urlencoded parameters do not provide headers
+ protected final List<String> headers = Collections.emptyList();
+
+ /**
+ * The name of the form parameter.
+ */
+ protected String formParameterName;
+
+ /**
+ * The value of the form parameter.
+ */
+ protected URLDecodingInputStream formParameterValue;
+
+ public XWWWFormUrlEncodedParameter() {
+ // parse parameter name
+ URLDecodingInputStream urldec = new URLDecodingInputStream(in, NAME_VALUE_SEP);
+ InputStreamReader reader = new InputStreamReader(urldec, UTF_8);
+ try {
+ StringBuilder sb = new StringBuilder();
+ char[] b = new char[128];
+ for (int l = 0; (l = reader.read(b)) != -1;) {
+ sb.append(b, 0, l);
+ }
+ formParameterName = sb.toString();
+ // fill buffer if empty
+ if (pos >= count) {
+ if ((count = in.read(buf)) == -1) {
+ throw new IOException("Invalid URL encoding.");
+ }
+ pos = 0;
+ }
+ // skip separator
+ pos++;
+ } catch (IOException e) {
+ deferredIOException = e;
+ formParameterName = "";
+ }
+ formParameterValue = new URLDecodingInputStream(in, PARAM_SEP);
+ }
+
+ @Override
+ public String getFormParameterContentType() {
+ // x-www-form-urlencoded parameters do not specify a content type
+ return null;
+ }
+
+ @Override
+ public String getFormParameterName() {
+ return formParameterName;
+ }
+
+ @Override
+ public InputStream getFormParameterValue() {
+ if (deferredIOException != null) {
+ final IOException e = deferredIOException;
+ deferredIOException = null;
+ return new InputStream() {
+ @Override
+ public int read() throws IOException {
+ throw e;
+ }
+ };
+ } else {
+ return formParameterValue;
+ }
+ }
+
+ @Override
+ public Iterator<String> getHeaderNames() {
+ return headers.iterator();
+ }
+
+ @Override
+ public String getHeaderValue(String headerName) {
+ return null;
+ }
+
+ }
+
+ public class URLDecodingInputStream extends FilterInputStream {
+
+ /**
+ * Has this stream already been closed.
+ */
+ private boolean closed = false;
+
+ /**
+ * Has this stream been disconnected.
+ */
+ private boolean disconnected = false;
+
+ /**
+ * Read until this byte occurs.
+ */
+ protected final byte term;
+
+ /**
+ * Creates a new instance of this URLDecodingInputStream.
+ *
+ * @param in
+ * @param separator
+ */
+ protected URLDecodingInputStream(InputStream in, byte separator) {
+ super(in);
+ this.term = separator;
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.FilterInputStream#read()
+ */
+ @Override
+ public int read() throws IOException {
+ if (closed) {
+ throw new IOException("The stream has already been closed.");
+ }
+ if (disconnected) {
+ return in.read();
+ }
+
+ if (pos >= count) {
+ if ((count = in.read(buf)) == -1) {
+ return -1;
+ }
+ pos = 0;
+ } if (buf[pos] == term) {
+ return -1;
+ } else if (buf[pos] == '+') {
+ pos++;
+ return ' ';
+ } else if (buf[pos] == '%') {
+ if (++pos == count) {
+ if ((count = in.read(buf)) == -1) {
+ throw new IOException("Invalid URL encoding.");
+ }
+ pos = 0;
+ }
+ int c1 = Character.digit(buf[pos], 16);
+ if (++pos == count) {
+ if ((count = in.read(buf)) == -1) {
+ throw new IOException("Invalid URL encoding.");
+ }
+ pos = 0;
+ }
+ int c2 = Character.digit(buf[pos], 16);
+ return ((c1 << 4) | c2);
+ } else {
+ return buf[pos++];
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.FilterInputStream#read(byte[], int, int)
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (closed) {
+ throw new IOException("The stream has already been closed.");
+ }
+ if (disconnected) {
+ return in.read(b, off, len);
+ }
+
+ if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return 0;
+ }
+
+ if (pos >= count) {
+ if ((count = in.read(buf)) == -1) {
+ return -1;
+ }
+ pos = 0;
+ }
+ if (buf[pos] == term) {
+ return -1;
+ }
+
+ int l = 0;
+ for (;;) {
+ while (pos < count) {
+ if (l == len || buf[pos] == term) {
+ return l;
+ } else if (buf[pos] == '+') {
+ b[off] = ' ';
+ } else if (buf[pos] == '%') {
+ if (++pos == count) {
+ if ((count = in.read(buf)) == -1) {
+ throw new IOException("Invalid URL encoding.");
+ }
+ pos = 0;
+ }
+ int c1 = Character.digit(buf[pos], 16);
+ if (++pos == count) {
+ if ((count = in.read(buf)) == -1) {
+ throw new IOException("Invalid URL encoding.");
+ }
+ pos = 0;
+ }
+ int c2 = Character.digit(buf[pos], 16);
+ b[off] = (byte) ((c1 << 4) | c2);
+ } else {
+ b[off] = buf[pos];
+ }
+ pos++;
+ off++;
+ l++;
+ }
+ if ((count = in.read(buf)) == -1) {
+ return l;
+ }
+ pos = 0;
+ }
+ }
+
+ /**
+ * Disconnect from the InputStream and buffer all remaining data.
+ *
+ * @throws IOException
+ */
+ public void disconnect() throws IOException {
+ if (!disconnected) {
+ // don't waste space for a buffer if end of stream has already been
+ // reached
+ byte[] b = new byte[1];
+ if ((read(b)) != -1) {
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write(b);
+ b = new byte[1024];
+ for (int l; (l = read(b, 0, b.length)) != -1;) {
+ os.write(b, 0, l);
+ }
+ super.in = new ByteArrayInputStream(os.toByteArray());
+ }
+ disconnected = true;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see java.io.FilterInputStream#close()
+ */
+ @Override
+ public void close() throws IOException {
+ if (!hasNext()) {
+ // don't close the underlying stream until all parts are read
+ super.close();
+ }
+ disconnect();
+ closed = true;
+ }
+
+ }
+
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/InputStreamPartSource.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/InputStreamPartSource.java
new file mode 100644
index 00000000..1a22f787
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/InputStreamPartSource.java
@@ -0,0 +1,61 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding.multipart;
+
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.commons.httpclient.methods.multipart.PartSource;
+
+/**
+ * InputStream source for FilePart.
+ * DOES NOT RETURN A CORRECT LENGTH OF THE INPUT DATA. (but we don't care, since we use chunked encoding)
+ *
+ * @author clemens
+ */
+public class InputStreamPartSource implements PartSource {
+
+ protected String name;
+ protected InputStream data;
+
+ public InputStreamPartSource(String name, InputStream data) {
+ this.name = name;
+ this.data = data;
+ }
+
+ /**
+ * Just a dummy value to make Part work
+ * @return 42
+ */
+ @Override
+ public long getLength() {
+ //System.out.println("***********GETLENGTH");
+ return 42;
+ }
+
+ @Override
+ public String getFileName() {
+ return name;
+ }
+
+ @Override
+ public InputStream createInputStream() throws IOException {
+ if (data == null)
+ throw new IOException("Failed to get stream for part: no data was set.");
+ return data;
+ }
+
+}
diff --git a/bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/SLResultPart.java b/bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/SLResultPart.java
new file mode 100644
index 00000000..d896ea9f
--- /dev/null
+++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/SLResultPart.java
@@ -0,0 +1,71 @@
+/*
+* Copyright 2008 Federal Chancellery Austria and
+* Graz University of Technology
+*
+* 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 at.gv.egiz.bku.binding.multipart;
+
+import at.gv.egiz.bku.binding.DataUrlConnection;
+import at.gv.egiz.bku.slcommands.SLResult;
+import at.gv.egiz.bku.slcommands.SLResult.SLResultType;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.httpclient.methods.multipart.FilePart;
+import org.apache.commons.httpclient.methods.multipart.PartSource;
+
+public class SLResultPart extends FilePart {
+
+ protected SLResult slResult;
+ protected String encoding;
+
+ public SLResultPart(SLResult slResult, String encoding) {
+ super((slResult.getResultType() == SLResultType.XML)
+ ? DataUrlConnection.FORMPARAM_XMLRESPONSE
+ : DataUrlConnection.FORMPARAM_BINARYRESPONSE,
+ new PartSource() {
+
+ @Override
+ public long getLength() {
+ // may return null, as sendData() is overridden
+ return 0;
+ }
+
+ @Override
+ public String getFileName() {
+ // return null, to prevent content-disposition header
+ return null;
+ }
+
+ @Override
+ public InputStream createInputStream() throws IOException {
+ // may return null, as sendData() is overridden below
+ return null;
+ }
+ }
+ );
+ this.slResult = slResult;
+ this.encoding = encoding;
+ }
+
+ @Override
+ protected void sendData(OutputStream out) throws IOException {
+ slResult.writeTo(new StreamResult(new OutputStreamWriter(out, encoding)), false);
+ }
+}