From 32d17447a258188b2d534bcb0bf65a659ba7b7d0 Mon Sep 17 00:00:00 2001 From: mcentner Date: Fri, 29 Aug 2008 12:11:34 +0000 Subject: Initial import. git-svn-id: https://joinup.ec.europa.eu/svn/mocca/trunk@1 8a26b1a7-26f0-462f-b9ef-d0e30c41f5a4 --- .../egiz/bku/binding/AbstractBindingProcessor.java | 86 +++ .../at/gv/egiz/bku/binding/BindingProcessor.java | 75 ++ .../egiz/bku/binding/BindingProcessorManager.java | 102 +++ .../bku/binding/BindingProcessorManagerImpl.java | 258 +++++++ .../main/java/at/gv/egiz/bku/binding/DataUrl.java | 62 ++ .../at/gv/egiz/bku/binding/DataUrlConnection.java | 79 ++ .../gv/egiz/bku/binding/DataUrlConnectionImpl.java | 216 ++++++ .../gv/egiz/bku/binding/DataUrlConnectionSPI.java | 42 ++ .../at/gv/egiz/bku/binding/DataUrlResponse.java | 98 +++ .../java/at/gv/egiz/bku/binding/ExpiryRemover.java | 67 ++ .../gv/egiz/bku/binding/FixedFormParameters.java | 28 + .../java/at/gv/egiz/bku/binding/FormParameter.java | 39 + .../at/gv/egiz/bku/binding/FormParameterImpl.java | 93 +++ .../at/gv/egiz/bku/binding/FormParameterStore.java | 146 ++++ .../gv/egiz/bku/binding/HTTPBindingProcessor.java | 820 +++++++++++++++++++++ .../main/java/at/gv/egiz/bku/binding/HttpUtil.java | 78 ++ .../src/main/java/at/gv/egiz/bku/binding/Id.java | 27 + .../java/at/gv/egiz/bku/binding/IdFactory.java | 106 +++ .../main/java/at/gv/egiz/bku/binding/IdImpl.java | 80 ++ .../java/at/gv/egiz/bku/binding/InputDecoder.java | 41 ++ .../gv/egiz/bku/binding/InputDecoderFactory.java | 89 +++ .../bku/binding/MultiPartFormDataInputDecoder.java | 133 ++++ .../at/gv/egiz/bku/binding/RemovalStrategy.java | 26 + .../gv/egiz/bku/binding/SLCommandInvokerImpl.java | 66 ++ .../egiz/bku/binding/XWWWFormUrlInputDecoder.java | 101 +++ .../binding/multipart/InputStreamPartSource.java | 66 ++ .../egiz/bku/binding/multipart/SLResultPart.java | 57 ++ 27 files changed, 3081 insertions(+) create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/AbstractBindingProcessor.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessor.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManager.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManagerImpl.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrl.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnection.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionImpl.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionSPI.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlResponse.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/ExpiryRemover.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/FixedFormParameters.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameter.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterImpl.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/FormParameterStore.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessor.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/HttpUtil.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/Id.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/IdFactory.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/IdImpl.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoder.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/InputDecoderFactory.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/MultiPartFormDataInputDecoder.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/RemovalStrategy.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/SLCommandInvokerImpl.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputDecoder.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/InputStreamPartSource.java create mode 100644 bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/SLResultPart.java (limited to 'bkucommon/src/main/java/at/gv/egiz/bku/binding') 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..17ce29ce --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/AbstractBindingProcessor.java @@ -0,0 +1,86 @@ +/* +* 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.Date; + +import at.gv.egiz.bku.slcommands.SLCommandInvoker; +import at.gv.egiz.stal.STAL; + +public abstract class AbstractBindingProcessor implements BindingProcessor { + protected Id id; + 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) { + 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"); + } + 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..c386508d --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessor.java @@ -0,0 +1,75 @@ +/* +* 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.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); + + /** + * 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); +} \ 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..a4e5bd90 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManager.java @@ -0,0 +1,102 @@ +/* +* 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.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 protcol + * the transport binding protocol + * @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 protcol, + String aSessionId, Locale locale); + + /** + * FactoryMethod creating a new BindingProcessor object. + * The created binding processor must be passed to the process method to execute. + * + * @param protcol + * the transport binding protocol + * @param aSessionId + * optional an external sessionId (e.g. http session) could be + * provided. This parameter may be null. + */ + public BindingProcessor createBindingProcessor(String protcol, + String aSessionId); + + + /** + * 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); + + /** + * Schedules the provided binding processor for processing and immediately returns. + * + * @param aBindingProcessor + */ + public void 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 getManagedIds(); + + public void shutdown(); +} \ 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..7a3b1bb9 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/BindingProcessorManagerImpl.java @@ -0,0 +1,258 @@ +/* +* 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.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); + + private RemovalStrategy removalStrategy; + private STALFactory stalFactory; + private SLCommandInvoker commandInvokerClass; + private ExecutorService executorService; + private Map bindingProcessorMap = Collections + .synchronizedMap(new HashMap()); + + /** + * 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) { + 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; + 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(); + } + + /** + * Uses the default locale + */ + public BindingProcessor createBindingProcessor(String protocol, + String aSessionId) { + return createBindingProcessor(protocol, aSessionId, null); + } + + /** + * FactoryMethod creating a new BindingProcessor object. + * + * @param protocol + * must not be null + */ + public BindingProcessor createBindingProcessor(String protocol, + String aSessionId, Locale locale) { + String low = protocol.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(), proto); + STAL stal = stalFactory.createSTAL(); + bindingProcessor.init(stal, commandInvokerClass.newInstance()); + 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. + */ + public BindingProcessor getBindingProcessor(Id aId) { + if (bindingProcessorMap.get(aId) != null) { + return bindingProcessorMap.get(aId).getBindingProcessor(); + } else { + return null; + } + } + + /** + * + */ + 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 + * @param aBindingProcessor must not be null + */ + public void process(BindingProcessor aBindingProcessor) { + 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()); + } + Future f = executorService.submit(aBindingProcessor); + bindingProcessorMap.put(aBindingProcessor.getId(), new MapEntityWrapper(f, + aBindingProcessor)); + } + + @Override + public void setSLCommandInvoker(SLCommandInvoker invoker) { + commandInvokerClass = invoker; + } + + @Override + public void removeBindingProcessor(Id sessionId) { + MapEntityWrapper wrapper = bindingProcessorMap + .get(sessionId); + if (wrapper == null) { + return; + } + Future f = wrapper.getFuture(); + if (!f.isDone()) { + f.cancel(true); + } + bindingProcessorMap.remove(sessionId); + } + + @Override + public Set getManagedIds() { + Set result = new HashSet(); + synchronized (bindingProcessorMap) { + for (Iterator 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..8eaeacbd --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrl.java @@ -0,0 +1,62 @@ +/* +* 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.net.URL; + +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 DataUrlConnectionSPI defaultDataUrlConnection = new DataUrlConnectionImpl(); + private static Log log = LogFactory.getLog(DataUrl.class); + + private URL url; + + /** + * Sets the default DataUrlConnection implementation + * @param aClass must not be null + */ + public static void setDataUrlConnectionClass(DataUrlConnectionSPI dataUrlConnection) { + if (dataUrlConnection == null) { + throw new NullPointerException("Default dataurlconnection must not be set to null"); + } + defaultDataUrlConnection = dataUrlConnection; + } + + public DataUrl(String aUrlString) throws MalformedURLException { + url = new URL(aUrlString); + } + + public DataUrlConnection openConnection() { + try { + DataUrlConnectionSPI retVal = defaultDataUrlConnection.newInstance(); + retVal.init(url); + return retVal; + } catch (Exception e) { + log.error(e); + throw new SLRuntimeException("Cannot instantiate a dataurlconnection:",e); + } + } +} \ 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..e6d5e075 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnection.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.io.IOException; +import java.io.InputStream; +import java.net.SocketTimeoutException; +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 citizen-card-environment/1.2 BKU2 1.0. + * The form-parameter ResponseType is set to HTTP-Security-Layer-RESPONSE. + * All other headers/parameters are set by the caller. + * + * @author clemens + */ +public interface DataUrlConnection { + + public static final String DEFAULT_USERAGENT = "citizen-card-environment/1.2 BKU2 1.0"; + 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(); + + /** + * 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..134d765e --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionImpl.java @@ -0,0 +1,216 @@ +/* +* 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.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.SocketTimeoutException; +import java.net.URL; +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.Set; + +import javax.net.ssl.HttpsURLConnection; + +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 at.gv.egiz.bku.binding.multipart.InputStreamPartSource; +import at.gv.egiz.bku.binding.multipart.SLResultPart; +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.StreamUtil; +import at.gv.egiz.bku.utils.binding.Protocol; + +/** + * not thread-safe thus newInsance always returns a new object + * + */ +public class DataUrlConnectionImpl implements DataUrlConnectionSPI { + + public final static Protocol[] SUPPORTED_PROTOCOLS = { Protocol.HTTP, + Protocol.HTTPS }; + protected X509Certificate serverCertificate; + protected Protocol protocol; + protected URL url; + private HttpURLConnection connection; + protected Map requestHttpHeaders; + protected ArrayList formParams; + protected String boundary; + + protected DataUrlResponse result; + + public String getProtocol() { + if (protocol == null) { + return null; + } + return protocol.toString(); + } + + /** + * opens a connection sets the headers gets the server certificate + * + * @throws java.net.SocketTimeoutException + * @throws java.io.IOException + * @pre url != null + * @pre httpHeaders != null + */ + public void connect() throws SocketTimeoutException, IOException { + connection = (HttpURLConnection) url.openConnection(); + + // FIXXME move this to config. + HttpURLConnection.setFollowRedirects(false); + + + connection.setDoOutput(true); + Set headers = requestHttpHeaders.keySet(); + Iterator headerIt = headers.iterator(); + while (headerIt.hasNext()) { + String name = headerIt.next(); + connection.setRequestProperty(name, requestHttpHeaders.get(name)); + } + connection.connect(); + if (connection instanceof HttpsURLConnection) { + HttpsURLConnection ssl = (HttpsURLConnection) connection; + X509Certificate[] certs = (X509Certificate[]) ssl.getServerCertificates(); + if ((certs != null) && (certs.length >= 1)) { + serverCertificate = certs[0]; + } + } + } + + public X509Certificate getServerCertificate() { + return serverCertificate; + } + + public void setHTTPHeader(String name, String value) { + if (name != null && value != null) { + requestHttpHeaders.put(name, value); + } + } + + public void setHTTPFormParameter(String name, InputStream data, + String contentType, String charSet, String transferEncoding) { + InputStreamPartSource source = new InputStreamPartSource(null, data); + FilePart formParam = new FilePart(name, source, contentType, charSet); + if (transferEncoding != null) { + formParam.setTransferEncoding(transferEncoding); + } else { + formParam.setTransferEncoding(null); + } + formParams.add(formParam); + } + + /** + * send all formParameters + * + * @throws java.io.IOException + */ + public void transmit(SLResult slResult) throws IOException { + 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()); + } + formParams.add(slResultPart); + + OutputStream os = connection.getOutputStream(); + + Part[] parts = new Part[formParams.size()]; + Part.sendParts(os, formParams.toArray(parts), boundary.getBytes()); + os.close(); + // MultipartRequestEntity PostMethod + result = new DataUrlResponse(url.toString(), connection.getResponseCode(), + connection.getInputStream()); + + Map responseHttpHeaders = new HashMap(); + Map> httpHeaders = connection.getHeaderFields(); + for (Iterator 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(); + requestHttpHeaders.put(HttpUtil.HTTP_HEADER_USER_AGENT, DEFAULT_USERAGENT); + requestHttpHeaders.put(HttpUtil.HTTP_HEADER_CONTENT_TYPE, + HttpUtil.MULTIPART_FOTMDATA + HttpUtil.SEPERATOR[0] + + HttpUtil.MULTIPART_FOTMDATA_BOUNDARY + "=" + boundary); + + formParams = new ArrayList(); + StringPart responseType = new StringPart(FORMPARAM_RESPONSETYPE, + DEFAULT_RESPONSETYPE); + responseType.setCharSet("UTF-8"); + responseType.setTransferEncoding(null); + formParams.add(responseType); + } + + @Override + public DataUrlConnectionSPI newInstance() { + return new DataUrlConnectionImpl(); + } +} \ 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..9e5a66f8 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/DataUrlConnectionSPI.java @@ -0,0 +1,42 @@ +/* +* 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; + +/** + * 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); + + +} 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 = ""; + + protected Map 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 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 responseHttpHeaders) { + this.responseHttpHeaders = responseHttpHeaders; + } + + public Map getResponseHeaders() { + return responseHttpHeaders; + } + + public int getResponseCode() { + return responseCode; + } + + /** + * Checks if the http response equals "" + * + * @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 managedIds = bindingProcessorManager.getManagedIds(); + for (Iterator 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 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 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 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 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..b79f7d55 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/HTTPBindingProcessor.java @@ -0,0 +1,820 @@ +/* +* 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.io.Reader; +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.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.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.SLCanceledException; +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 XML_REQ_TRANSFER_ENCODING = Arrays + .asList(new String[] { "binary" }); + + /** + * Defines the maximum number of dataurl connects that are allowed within a + * single SL Request processing. + */ + protected static int MAX_DATAURL_HOPS = 10; + + 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 headerMap = Collections.EMPTY_MAP; + protected SLCommand slCommand; + protected Map formParameterMap = new HashMap(); + protected SLSourceContext srcContex = new SLSourceContext(); + protected SLTargetContext targetContext = new SLTargetContext(); + protected Protocol protocol; + protected State currentState = State.INIT; + protected Transformer transformer = null; + protected String resultContentType = null; + protected SLResult slResult = null; + protected int responseCode = 200; + protected Map responseHeaders = Collections.EMPTY_MAP; + protected Locale locale = Locale.getDefault(); + + /** + * + * @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, + Protocol protocol) { + super(id); + 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; + this.protocol = protocol; + srcContex.setSourceProtocol(protocol); + srcContex.setSourceIsDataURL(false); + } + + //---------------------------------------------------------------------------- + // ----------- BEGIN CONVENIENCE METHODS ----------- + + protected void sendSTALQuit() { + log.info("Sending QUIT command to STAL"); + List quit = new ArrayList(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 getFormParameters(String parameterNamePostfix) { + List resultList = new ArrayList(); + for (Iterator fpi = formParameterMap.keySet().iterator(); fpi + .hasNext();) { + String paramName = fpi.next(); + if (paramName.endsWith(parameterNamePostfix)) { + resultList.add(formParameterMap.get(paramName)); + } + } + return resultList; + } + + protected List getTransferHeaders() { + return getFormParameters("__"); + } + + protected List getTransferForms() { + List resultList = new ArrayList(); + for (Iterator 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; + try { + commandInvoker.invoke(srcContex); + } catch (SLCanceledException e) { + log.info("Caught exception: " + e); + bindingProcessorError = e; + currentState = State.TRANSFORM; + } + dataUrlResponse = null; + 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(); + 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 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.setTargetProtocol(conn.getProtocol()); + 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()); + protocol = Protocol.fromString(conn.getProtocol()); + + 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()); + closeDataUrlConnection(); + srcContex.setSourceCertificate(conn.getServerCertificate()); + srcContex.setSourceIsDataURL(true); + srcContex + .setSourceProtocol(Protocol.fromString(conn.getProtocol())); + 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 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 != "); + headerMap.put(HttpUtil.HTTP_HEADER_CONTENT_TYPE, contentType); + assignXMLRequest(dataUrlResponse.getStream(), HttpUtil.getCharset( + contentType, true)); + closeDataUrlConnection(); + srcContex.setSourceCertificate(conn.getServerCertificate()); + srcContex.setSourceIsDataURL(true); + srcContex + .setSourceProtocol(Protocol.fromString(conn.getProtocol())); + 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.setSourceProtocol(Protocol.fromString(conn.getProtocol())); + 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.setTargetProtocol(protocol.toString()); + try { + slResult = commandInvoker.getResult(targetContext); + resultContentType = slResult.getMimeType(); + log + .debug("Successfully got SLResult from commandinvoker, setting mimetype to: " + + resultContentType); + } catch (SLCanceledException e) { + log.info("Cannot get result from invoker:", e); + bindingProcessorError = new SLException(6002); + resultContentType = HttpUtil.TXT_XML; + } + } + transformer = getTransformer(getStyleSheetUrl()); + if (transformer != null) { + log.debug("Output transformation required"); + resultContentType = transformer.getOutputProperty("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()); + } + + // -- 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 aHeaderMap) { + headerMap = new HashMap(); + // 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) { + return fp.getFormParameterValue(); + } + 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)); + 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 > MAX_DATAURL_HOPS) { + 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"); + } + + @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); + id.setContentType(cl); + if (id == null) { + log.error("Cannot get inputdecoder for is"); + throw new SLException(2006); + } + for (Iterator 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 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 from parameter: " + fps.getFormParameterName()); + formParameterMap.put(fps.getFormParameterName(), fps); + } + } + } + if (slCommand == null) { + throw new SLBindingException(2004); + } + if (is.read() != -1) { + log.error("Request input stream not completely read"); + // consume rest of stream, should never occur + throw new SLRuntimeException( + "request input stream not consumed till end"); + } + } 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 { + while (is.read() != -1) + ; + } catch (IOException e) { + log.error(e); + } + } + } + + @Override + public String getResultContentType() { + return resultContentType; + } + + protected Transformer getTransformer(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(); + StreamData sd = URLDereferencer.getInstance().dereference(styleSheetURL, + urlCtx); + Transformer t = factory.newTransformer(new StreamSource(sd.getStream())); + t.setURIResolver(resolver); + return t; + } catch (Exception ex) { + log.info("Cannot instantiate transformer", ex); + bindingProcessorError = new SLException(2002); + return null; + } + } + + protected void handleBindingProcessorError(OutputStream os, String encoding, + Transformer transformer) throws IOException { + log.debug("Writing error as result"); + ErrorResultImpl error = new ErrorResultImpl(bindingProcessorError); + try { + error.writeTo(new StreamResult(new OutputStreamWriter(os, encoding)), + transformer); + } catch (TransformerException e) { + log.fatal("Cannot write error result to stream", e); + } + } + + @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, transformer); + 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 (transformer == null) { + StreamUtil.copyStream(isr, osw); + } else { + try { + 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, transformer); + return; + } else { + log.debug("Getting result from invoker"); + OutputStreamWriter osw = new OutputStreamWriter(os, encoding); + try { + slResult.writeTo(new StreamResult(osw), transformer); + } catch (TransformerException e) { + log.fatal("Cannot write result to stream", e); + // bindingProcessorError = new SLException(2008); + // handleBindingProcessorError(os, encoding, transformer); + } + osw.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 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; + } + +} \ 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..b11a4d85 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/HttpUtil.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 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 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..93ab2e8b --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/Id.java @@ -0,0 +1,27 @@ +/* +* 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 { + + public String toString(); +} \ 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..5523992a --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/IdImpl.java @@ -0,0 +1,80 @@ +/* +* 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; + } + + public String toString() { + return idString; + } + + public int hashCode() { + return idString.hashCode(); + } + + 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 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> decoderMap = new HashMap>(); + + 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 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 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 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 { + + 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/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..ef2affd1 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/SLCommandInvokerImpl.java @@ -0,0 +1,66 @@ +/* +* 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.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; + +/** + * This class implements the entry point for the CCEs security management. + * + * TODO the secuirty management is currently not implemented. + */ +public class SLCommandInvokerImpl implements SLCommandInvoker { + + private static Log log = LogFactory.getLog(SLCommandInvokerImpl.class); + + protected SLCommand command; + protected SLResult result; + + /** + * Invokes a sl command. + */ + public void invoke(SLSourceContext aContext) { + // FIXXME add security policy here. + log.warn("Security policy not implemented yet, invoking command: "+command); + result = command.execute(); + } + + public SLResult getResult(SLTargetContext aContext) { + // FIXXME + log.warn("Security policy not implemented yet, getting result of command: "+command); + return result; + } + + public void setCommand(SLCommand aCmd) { + command = aCmd; + } + + @Override + public SLCommandInvoker newInstance() { + SLCommandInvokerImpl cmdInv = new SLCommandInvokerImpl(); + return cmdInv; + } + + +} \ 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..f4ebe288 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/XWWWFormUrlInputDecoder.java @@ -0,0 +1,101 @@ +/* +* 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.net.URLDecoder; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.fileupload.ParameterParser; + +import at.gv.egiz.bku.slexceptions.SLRuntimeException; +import at.gv.egiz.bku.utils.StreamUtil; + +/** + * Implementation based on Java's URLDecoder class + * + */ +// FIXME replace this code by a streaming variant +public class XWWWFormUrlInputDecoder implements InputDecoder { + + public final static String CHAR_SET = "charset"; + public final static String NAME_VAL_SEP = "="; + public final static String SEP = "\\&"; + + private String contentType; + private InputStream dataStream; + private String charset = "UTF-8"; + + protected List decodeInput(InputStream is) throws IOException { + List result = new LinkedList(); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + StreamUtil.copyStream(is, bos); + String inputString = new String(bos.toByteArray()); + String[] nameValuePairs = inputString.split(SEP); + //inputString = URLDecoder.decode(inputString, charset); + for (int i = 0; i < nameValuePairs.length; i++) { + String[] fields = nameValuePairs[i].split(NAME_VAL_SEP, 2); + if (fields.length != 2) { + throw new SLRuntimeException("Invalid form encoding, missing value"); + } + String name = URLDecoder.decode(fields[0], charset); + String value =URLDecoder.decode(fields[1], charset); + ByteArrayInputStream bais = new ByteArrayInputStream(value + .getBytes(charset)); + FormParameterImpl fpi = new FormParameterImpl(contentType, name, bais, null); + result.add(fpi); + } + return result; + } + + @SuppressWarnings("unchecked") + @Override + public void setContentType(String contentType) { + ParameterParser pp = new ParameterParser(); + pp.setLowerCaseNames(true); + Map params = pp.parse(contentType, new char[] { ':', ';' }); + if (!params.containsKey("application/x-www-form-urlencoded")) { + throw new IllegalArgumentException( + "not a url encoded content type specification: " + contentType); + } + String cs = params.get(CHAR_SET); + if (cs != null) { + charset = cs; + } + this.contentType = contentType; + } + + @Override + public Iterator getFormParameterIterator() { + try { + return decodeInput(dataStream).iterator(); + } catch (IOException e) { + throw new SLRuntimeException(e); + } + } + + @Override + public void setInputStream(InputStream is) { + dataStream = is; + } +} 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..253f8ff5 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/InputStreamPartSource.java @@ -0,0 +1,66 @@ +/* +* 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. +*/ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +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..566b77b3 --- /dev/null +++ b/bkucommon/src/main/java/at/gv/egiz/bku/binding/multipart/SLResultPart.java @@ -0,0 +1,57 @@ +/* +* 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. +*/ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package at.gv.egiz.bku.binding.multipart; + +import at.gv.egiz.bku.slcommands.SLResult; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; + +import javax.xml.transform.stream.StreamResult; + +import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource; +import org.apache.commons.httpclient.methods.multipart.FilePart; + +/** + * + * @author clemens + */ +public class SLResultPart extends FilePart { + + protected SLResult slResult; + protected String encoding; + + public SLResultPart(SLResult slResult, String encoding) { + super("XMLResponse", + new ByteArrayPartSource(null, "dummySource".getBytes())); + this.slResult = slResult; + this.encoding = encoding; + } + + @Override + protected void sendData(OutputStream out) throws IOException { + slResult.writeTo(new StreamResult(new OutputStreamWriter(out, encoding))); + // slResult.writeTo(new StreamResult(new OutputStreamWriter(System.out, + // encoding))); + // super.sendData(out); + } +} -- cgit v1.2.3